atomic_internal_id.rb 1.9 KB
Newer Older
1 2
# Include atomic internal id generation scheme for a model
#
A
Andreas Brandl 已提交
3
# This allows us to atomically generate internal ids that are
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
# unique within a given scope.
#
# For example, let's generate internal ids for Issue per Project:
# ```
# class Issue < ActiveRecord::Base
#   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.
24 25 26
module AtomicInternalId
  extend ActiveSupport::Concern

A
Andreas Brandl 已提交
27
  module ClassMethods
28
    def has_internal_id(column, scope:, init:, presence: true) # rubocop:disable Naming/PredicateName
29 30 31 32
      # We require init here to retain the ability to recalculate in the absence of a
      # InternaLId record (we may delete records in `internal_ids` for example).
      raise "has_internal_id requires a init block, none given." unless init

33
      before_validation :"ensure_#{scope}_#{column}!", on: :create
34
      validates column, presence: presence
35

36
      define_method("ensure_#{scope}_#{column}!") do
37
        scope_value = association(scope).reader
38

39 40
        if read_attribute(column).blank? && scope_value
          scope_attrs = { scope_value.class.table_name.singularize.to_sym => scope_value }
A
Andreas Brandl 已提交
41
          usage = self.class.table_name.to_sym
42

A
Andreas Brandl 已提交
43
          new_iid = InternalId.generate_next(self, scope_attrs, usage, init)
A
Andreas Brandl 已提交
44
          write_attribute(column, new_iid)
A
Andreas Brandl 已提交
45
        end
46 47

        read_attribute(column)
48 49
      end
    end
50 51
  end
end