Module: AtomicInternalId

Overview

Include atomic internal id generation scheme for a model

This allows us to atomically generate internal ids that are unique within a given scope.

For example, let's generate internal ids for Issue per Project:

class Issue < ApplicationRecord
  has_internal_id :iid, scope: :project, init: ->(s) { s.project.issues.maximum(:iid) }
end

This generates unique internal ids per project for newly created issues. The generated internal id is saved in the iid attribute of Issue.

This concern uses InternalId records to facilitate atomicity. In the absence of a record for the given scope, one will be created automatically. In this situation, the init block is called to calculate the initial value. In the example above, we calculate the maximum iid of all issues within the given project.

Note that a model may have more than one internal id associated with possibly different scopes.

Defined Under Namespace

Classes: Supply

Constant Summary collapse

MissingValueError =
Class.new(StandardError)

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.group_init(klass, column_name = :iid) ⇒ Object



234
235
236
237
238
239
240
241
242
# File 'app/models/concerns/atomic_internal_id.rb', line 234

def self.group_init(klass, column_name = :iid)
  ->(instance, scope) do
    if instance
      klass.where(group_id: instance.group_id).maximum(column_name)
    elsif scope.present?
      klass.where(group: scope[:namespace]).maximum(column_name)
    end
  end
end

.namespace_init(klass, column_name = :iid) ⇒ Object



244
245
246
247
248
249
250
251
252
# File 'app/models/concerns/atomic_internal_id.rb', line 244

def self.namespace_init(klass, column_name = :iid)
  ->(instance, scope) do
    if instance
      klass.where(namespace_id: instance.namespace_id).maximum(column_name)
    elsif scope.present?
      klass.where(**scope).maximum(column_name)
    end
  end
end

.project_init(klass, column_name = :iid) ⇒ Object



224
225
226
227
228
229
230
231
232
# File 'app/models/concerns/atomic_internal_id.rb', line 224

def self.project_init(klass, column_name = :iid)
  ->(instance, scope) do
    if instance
      klass.default_scoped.where(project_id: instance.project_id).maximum(column_name)
    elsif scope.present?
      klass.default_scoped.where(**scope).maximum(column_name)
    end
  end
end

.scope_attrs(scope_value) ⇒ Object



206
207
208
# File 'app/models/concerns/atomic_internal_id.rb', line 206

def self.scope_attrs(scope_value)
  { scope_value.class.table_name.singularize.to_sym => scope_value } if scope_value
end

.scope_usage(klass) ⇒ Object



220
221
222
# File 'app/models/concerns/atomic_internal_id.rb', line 220

def self.scope_usage(klass)
  klass.respond_to?(:internal_id_scope_usage) ? klass.internal_id_scope_usage : klass.table_name.to_sym
end

Instance Method Details

#internal_id_read_scope(scope) ⇒ Object



254
255
256
# File 'app/models/concerns/atomic_internal_id.rb', line 254

def internal_id_read_scope(scope)
  association(scope).reader
end

#internal_id_scope_attrs(scope) ⇒ Object



210
211
212
213
214
# File 'app/models/concerns/atomic_internal_id.rb', line 210

def internal_id_scope_attrs(scope)
  scope_value = internal_id_read_scope(scope)

  ::AtomicInternalId.scope_attrs(scope_value)
end

#internal_id_scope_usageObject



216
217
218
# File 'app/models/concerns/atomic_internal_id.rb', line 216

def internal_id_scope_usage
  ::AtomicInternalId.scope_usage(self.class)
end