1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
|
module Sequenced
class Generator
attr_reader :record, :scope, :column, :start_at, :skip
def initialize(record, options = {})
@record = record
@scope = options[:scope]
@column = options[:column].to_sym
@start_at = options[:start_at]
@skip = options[:skip]
end
def set
return if skip? || id_set?
lock_table
record.send(:"#{column}=", next_id)
end
def id_set?
!record.send(column).nil?
end
def skip?
skip&.call(record)
end
def next_id
next_id_in_sequence.tap do |id|
id += 1 until unique?(id)
end
end
def next_id_in_sequence
start_at = self.start_at.respond_to?(:call) ? self.start_at.call(record) : self.start_at
if (last_record = find_last_record)
max(last_record.send(column) + 1, start_at)
else
start_at
end
end
def unique?(id)
build_scope(*scope) do
rel = base_relation
rel = rel.where.not(record.class.primary_key => record.id) if record.persisted?
rel.where(column => id)
end.count == 0
end
private
def lock_table
if postgresql?
record.class.connection.execute("LOCK TABLE #{record.class.table_name} IN EXCLUSIVE MODE")
end
end
def postgresql?
defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) &&
record.class.connection.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
end
def base_relation
record.class.base_class.unscoped
end
def find_last_record
build_scope(*scope) do
base_relation
.where("#{column} IS NOT NULL")
.order("#{column} DESC")
end.first
end
def build_scope(*columns)
rel = yield
columns.each { |c| rel = rel.where(c => record.send(c.to_sym)) }
rel
end
def max(*values)
values.to_a.max
end
end
end
|