lib/cistern/associations.rb
# 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