Class: InternalId::InternalIdGenerator

Inherits:
Object
  • Object
show all
Defined in:
app/models/internal_id.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(subject, scope, usage) ⇒ InternalIdGenerator

Returns a new instance of InternalIdGenerator.

Raises:

  • (ArgumentError)

107
108
109
110
111
112
113
114
115
116
117
# File 'app/models/internal_id.rb', line 107

def initialize(subject, scope, usage)
  @subject = subject
  @scope = scope
  @usage = usage

  raise ArgumentError, 'Scope is not well-defined, need at least one column for scope (given: 0)' if scope.empty?

  unless InternalId.usages.has_key?(usage.to_s)
    raise ArgumentError, "Usage '#{usage}' is unknown. Supported values are #{InternalId.usages.keys} from InternalId.usages"
  end
end

Instance Attribute Details

#scopeObject (readonly)

Generate next internal id for a given scope and usage.

For currently supported usages, see #usage enum.

The method implements a locking scheme that has the following properties: 1) Generated sequence of internal ids is unique per (scope and usage) 2) The method is thread-safe and may be used in concurrent threads/processes. 3) The generated sequence is gapless. 4) In the absence of a record in the internal_ids table, one will be created

and last_value will be calculated on the fly.

subject: The instance we're generating an internal id for. Gets passed to init if called. scope: Attributes that define the scope for id generation. usage: Symbol to define the usage of the internal id, see InternalId.usages


105
106
107
# File 'app/models/internal_id.rb', line 105

def scope
  @scope
end

#scope_attrsObject (readonly)

Generate next internal id for a given scope and usage.

For currently supported usages, see #usage enum.

The method implements a locking scheme that has the following properties: 1) Generated sequence of internal ids is unique per (scope and usage) 2) The method is thread-safe and may be used in concurrent threads/processes. 3) The generated sequence is gapless. 4) In the absence of a record in the internal_ids table, one will be created

and last_value will be calculated on the fly.

subject: The instance we're generating an internal id for. Gets passed to init if called. scope: Attributes that define the scope for id generation. usage: Symbol to define the usage of the internal id, see InternalId.usages


105
106
107
# File 'app/models/internal_id.rb', line 105

def scope_attrs
  @scope_attrs
end

#subjectObject (readonly)

Generate next internal id for a given scope and usage.

For currently supported usages, see #usage enum.

The method implements a locking scheme that has the following properties: 1) Generated sequence of internal ids is unique per (scope and usage) 2) The method is thread-safe and may be used in concurrent threads/processes. 3) The generated sequence is gapless. 4) In the absence of a record in the internal_ids table, one will be created

and last_value will be calculated on the fly.

subject: The instance we're generating an internal id for. Gets passed to init if called. scope: Attributes that define the scope for id generation. usage: Symbol to define the usage of the internal id, see InternalId.usages


105
106
107
# File 'app/models/internal_id.rb', line 105

def subject
  @subject
end

#usageObject (readonly)

Generate next internal id for a given scope and usage.

For currently supported usages, see #usage enum.

The method implements a locking scheme that has the following properties: 1) Generated sequence of internal ids is unique per (scope and usage) 2) The method is thread-safe and may be used in concurrent threads/processes. 3) The generated sequence is gapless. 4) In the absence of a record in the internal_ids table, one will be created

and last_value will be calculated on the fly.

subject: The instance we're generating an internal id for. Gets passed to init if called. scope: Attributes that define the scope for id generation. usage: Symbol to define the usage of the internal id, see InternalId.usages


105
106
107
# File 'app/models/internal_id.rb', line 105

def usage
  @usage
end

Instance Method Details

#generate(init) ⇒ Object

Generates next internal id and returns it init: Block that gets called to initialize InternalId record if not present

Make sure to not throw exceptions in the absence of records (if this is expected).

122
123
124
125
126
127
128
129
130
# File 'app/models/internal_id.rb', line 122

def generate(init)
  subject.transaction do
    # Create a record in internal_ids if one does not yet exist
    # and increment its last value
    #
    # Note this will acquire a ROW SHARE lock on the InternalId record
    (lookup || create_record(init)).increment_and_save!
  end
end

#reset(value) ⇒ Object

Reset tries to rewind to `value-1`. This will only succeed, if `value` stored in database is equal to `last_value`. value: The expected last_value to decrement


135
136
137
138
139
140
141
142
143
144
145
# File 'app/models/internal_id.rb', line 135

def reset(value)
  return false unless value

  updated =
    InternalId
      .where(**scope, usage: usage_value)
      .where(last_value: value)
      .update_all('last_value = last_value - 1')

  updated > 0
end

#track_greatest(init, new_value) ⇒ Object

Create a record in internal_ids if one does not yet exist and set its new_value if it is higher than the current last_value

Note this will acquire a ROW SHARE lock on the InternalId record


151
152
153
154
155
# File 'app/models/internal_id.rb', line 151

def track_greatest(init, new_value)
  subject.transaction do
    (lookup || create_record(init)).track_greatest_and_save!(new_value)
  end
end