# frozen_string_literal: true module Cistern::Associations def self.extended(klass) def klass.association_overlay @association_overlay ||= const_set(:Associations, Module.new) end klass.send(:include, klass.association_overlay) super end # Lists the associations defined on the resource # @return [Hash{Symbol=>Array}] mapping of association type to name def associations @associations ||= Hash.new { |h, k| h[k] = [] } end # Define an assocation that references a collection. # @param name [Symbol] name of association and corresponding reader and writer. # @param scope [Proc] returning {Cistern::Collection} instance to load models into. {#scope} is evaluated within the # context of the model. # @return [Cistern::Collection] as defined by {#scope} # @example # class Firm < Law::Model # identity :registration_id # has_many :lawyers, -> { cistern.associates(firm_id: identity) } # end def has_many(name, *args, &block) name_sym = name.to_sym reader_method = name writer_method = "#{name}=" options = args.last.is_a?(::Hash) ? args.pop : {} scope = args.first || block attribute name_sym, options.merge(writer: false, reader: false, type: :array) association_overlay.module_eval do define_method reader_method do collection = instance_exec(&scope) records = attributes[name_sym] || [] collection.load(records) if records.any? collection end end association_overlay.module_eval do define_method writer_method do |models| previous_value = attributes[name_sym] new_value = attributes[name_sym] = Array(models).map do |model| model.respond_to?(:attributes) ? model.attributes : model end changed!(name_sym, previous_value, new_value) new_value end end associations[:has_many] << name_sym end # Define an assocation that references a model. # @param name [Symbol] name of association and corresponding reader. # @param scope [Proc] returning a {Cistern::Model} that is evaluated within the context of the model. # @return [Cistern::Model] as defined by {#scope} # @example # class Firm < Law::Model # identity :registration_id # belongs_to :leader, -> { cistern.employees.get(:ceo) } # end def belongs_to(name, *args, &block) name_sym = name.to_sym reader_method = name writer_method = "#{name}=" options = args.last.is_a?(::Hash) ? args.pop : {} scope = args.first || block attribute name_sym, options.merge(writer: false, reader: false) association_overlay.module_eval do define_method reader_method do model = instance_exec(&scope) attributes[name_sym] = model.attributes model end end association_overlay.module_eval do define_method writer_method do |model| previous_value = attributes[name_sym] new_value = model.respond_to?(:attributes) ? model.attributes : model attributes[name_sym] = new_value changed!(name_sym, previous_value, new_value) new_value end end associations[:belongs_to] << name_sym end end