lib/active_fedora/reflection.rb



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
        #
        # <b>Note:</b> 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.
        #
        # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
        # <tt>has_many :clients</tt> returns <tt>:clients</tt>
        attr_reader :name


        # Returns the hash of options used for the macro.
        #
        # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
        # <tt>has_many :clients</tt> returns +{}+
        attr_reader :options


        # Returns the class for the macro.
        #
        # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
        # <tt>has_many :clients</tt> returns the Client class
        def klass
          @klass ||= class_name.constantize
        end

        # Returns the class name for the macro.
        #
        # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
        # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
        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

        private

        def derive_primary_key_name
          'pid'
        end        

      end
    end
  end
end