Module: ConcernsOnRails::Models::Sequenceable
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/concerns_on_rails/models/sequenceable.rb
Overview
Generates ordered, human-friendly sequential reference numbers — invoice numbers, order numbers, ticket numbers, support cases. Unlike Hashable / Tokenizable (which produce random identifiers), Sequenceable produces ordered ones backed by an integer column that is the source of truth.
class Invoice < ApplicationRecord
include ConcernsOnRails::Sequenceable
sequenceable_by :sequence, # integer column — source of truth
into: :number, # optional string column for the formatted value
prefix: "INV-",
padding: 5,
scope: :account_id, # one counter per account
reset: :year # restart numbering each calendar year
end
invoice = Invoice.create!(account_id: 1)
invoice.sequence # => 1, 2, 3 ... (per account, per year)
invoice.number # => "INV-2026-00001"
invoice.formatted_sequence # => "INV-2026-00001"
Invoice.next_sequence(account_id: 1) # peek the next value without creating
The integer is computed as MAX(field) within the scope (+ period) + 1, so numbering is dense and ordered. Generation is best-effort under concurrency — pair the column(s) with a scoped unique DB index for a real guarantee.
Constant Summary collapse
- RESET_PERIODS =
%i[never year month day].freeze
- NAME =
"ConcernsOnRails::Models::Sequenceable".freeze
Instance Method Summary collapse
-
#assign_sequenceable_value(field) ⇒ Object
Assigns the sequence (and, when configured, the formatted string) only when the integer column is blank, so callers can pass an explicit value.
Instance Method Details
#assign_sequenceable_value(field) ⇒ Object
Assigns the sequence (and, when configured, the formatted string) only when the integer column is blank, so callers can pass an explicit value. MAX+1 (or start_at on an empty scope) cannot already be taken within the same consistent read — the pre-1.26 exists? probe re-verified that tautology with an extra query on EVERY create, and could not close the concurrent-insert race anyway. Concurrency is the scoped unique index's job (pair with Support::UniqueRetry around the create).
116 117 118 119 120 121 122 123 124 125 |
# File 'lib/concerns_on_rails/models/sequenceable.rb', line 116 def assign_sequenceable_value(field) cfg = self.class.sequenceable_config.fetch(field) sequenceable_pin_created_at(cfg) self[field] = self.class.send(:sequence_base_value, field, self, {}) if self[field].blank? return unless cfg[:into] && self[cfg[:into]].blank? self[cfg[:into]] = self.class.send(:format_sequence, field, self[field], self) end |