module ActiveFedora module Reflection # :nodoc: extend ActiveSupport::Concern included do class_attribute :reflections self.reflections = {} end module ClassMethods def create_reflection(macro, name, options, active_fedora) case macro when :has_many, :belongs_to, :has_and_belongs_to_many klass = AssociationReflection reflection = klass.new(macro, name, options, active_fedora) end self.reflections = self.reflections.merge(name => reflection) reflection end # Returns a hash containing all AssociationReflection objects for the current class. # Example: # # Invoice.reflections # Account.reflections # def reflections read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {}) end # Returns the AssociationReflection object for the +association+ (use the symbol). # # Account.reflect_on_association(:owner) # returns the owner AssociationReflection # Invoice.reflect_on_association(:line_items).macro # returns :has_many # def reflect_on_association(association) reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil end class MacroReflection # Returns the target association's class. # # class Author < ActiveRecord::Base # has_many :books # end # # Author.reflect_on_association(:books).klass # # => Book # # Note: Do not call +klass.new+ or +klass.create+ to instantiate # a new association object. Use +build_association+ or +create_association+ # instead. This allows plugins to hook into association object creation. def klass #@klass ||= active_record.send(:compute_type, class_name) @klass ||= class_name end def initialize(macro, name, options, active_fedora) @macro, @name, @options, @active_fedora = macro, name, options, active_fedora end # Returns a new, unsaved instance of the associated class. +options+ will # be passed to the class's constructor. def build_association(*options) klass.new(*options) end # Returns the name of the macro. # # composed_of :balance, :class_name => 'Money' returns :balance # has_many :clients returns :clients attr_reader :name # Returns the hash of options used for the macro. # # composed_of :balance, :class_name => 'Money' returns { :class_name => "Money" } # has_many :clients returns +{}+ attr_reader :options attr_reader :macro # Returns the class for the macro. # # composed_of :balance, :class_name => 'Money' returns the Money class # has_many :clients returns the Client class def klass @klass ||= class_name.constantize end # Returns the class name for the macro. # # composed_of :balance, :class_name => 'Money' returns 'Money' # has_many :clients returns 'Client' def class_name @class_name ||= options[:class_name] || derive_class_name end # Returns whether or not this association reflection is for a collection # association. Returns +true+ if the +macro+ is either +has_many+ or # +has_and_belongs_to_many+, +false+ otherwise. def collection? @collection end private def derive_class_name class_name = name.to_s.camelize class_name = class_name.singularize if collection? class_name end end # Holds all the meta-data about an association as it was specified in the # Active Record class. class AssociationReflection < MacroReflection #:nodoc: def initialize(macro, name, options, active_record) super @collection = [:has_many, :has_and_belongs_to_many].include?(macro) end def primary_key_name @primary_key_name ||= options[:foreign_key] || derive_primary_key_name end # Creates a new instance of the associated class, and immediately saves it # with ActiveRecord::Base#save. +options+ will be passed to the class's # creation method. Returns the newly created object. def create_association(*options) klass.create(*options) end private def derive_primary_key_name 'pid' end end end end end