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