summaryrefslogtreecommitdiff
path: root/app/models/concerns/atomic_internal_id.rb
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/concerns/atomic_internal_id.rb')
-rw-r--r--app/models/concerns/atomic_internal_id.rb46
1 files changed, 46 insertions, 0 deletions
diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb
new file mode 100644
index 00000000000..4b66725a3e6
--- /dev/null
+++ b/app/models/concerns/atomic_internal_id.rb
@@ -0,0 +1,46 @@
+# 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 < 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.
+module AtomicInternalId
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def has_internal_id(column, scope:, init:) # rubocop:disable Naming/PredicateName
+ before_validation(on: :create) do
+ if read_attribute(column).blank?
+ scope_attrs = { scope => association(scope).reader }
+ usage = self.class.table_name.to_sym
+
+ new_iid = InternalId.generate_next(self, scope_attrs, usage, init)
+ write_attribute(column, new_iid)
+ end
+ end
+
+ validates column, presence: true, numericality: true
+ end
+ end
+
+ def to_param
+ iid.to_s
+ end
+end