module Uuids
# Creates required `#uuids` attribute of the ActiveRecord model.
module Base
extend ActiveSupport::Concern
# Methods added to the ActiveRecord model.
module ClassMethods
private
# Declares:
#
# +uuids+:: association attribute;
# +uuid+:: method (string);
# uuid:: relation scope.
#
# @example
# class City < ActiveRecord::Base
# has_uuids
# end
#
# city = City.create!
#
# city.uuids.map(&:value)
# # => 51f50391-fcd2-4f69-aab7-6ef31b29c379
#
# city.uuid
# # => 51f50391-fcd2-4f69-aab7-6ef31b29c379
#
# City.by_uuid("51f50391-fcd2-4f69-aab7-6ef31b29c379").to_a == [city]
# # => true
#
def has_uuids
define_uuids
define_uuid_getter
define_scope
end
# Defines the +uuids+ association attribute;
def define_uuids
has_many :uuids, class_name: "Uuids::Uuid", as: :record, validate: false
before_create :add_default_uuid
before_destroy :prevent_destruction
validates :uuids, presence: true, on: :update
end
# Defines the +uuid+ method
def define_uuid_getter
class_eval "def uuid; uuids.map(&:value).sort.first; end"
end
# Defines the uuid relation scope.
def define_scope
scope(
:by_uuid,
->(value) { joins(:uuids).where(uuids_uuids: { value: value }).uniq }
)
end
end
private
# Prevents the module usage outside an ActiveRecord model.
def self.included(klass)
unless klass.ancestors.include? ActiveRecord::Base
fail TypeError.new("#{ klass.name } isn't an ActiveRecord model.")
end
end
# Creates the uuids by default preventing a record from being ureferrable
def add_default_uuid
uuids.present? || uuids.new
end
# Prevents destruction of a record before its uuids assigned to another one
def prevent_destruction
return true if uuids.blank?
errors.add :base, :uuids_present
false
end
end
end