moduleActiveFedoramoduleReflection# :nodoc:extendActiveSupport::Concernincludeddoclass_attribute:reflectionsself.reflections={}endmoduleClassMethodsdefcreate_reflection(macro,name,options,active_fedora)klass=casemacrowhen:has_many,:belongs_to,:has_and_belongs_to_many,:contains,:directly_contains,:directly_contains_one,:indirectly_containsAssociationReflectionwhen:rdf,:singular_rdfRDFPropertyReflectionendreflection=klass.new(macro,name,options,active_fedora)add_reflectionname,reflectionreflectionenddefadd_reflection(name,reflection)# FIXME: this is where the problem with association_spec is caused (key is string now)self.reflections=reflections.merge(name=>reflection)end# Returns a hash containing all AssociationReflection objects for the current class.# Example:## Invoice.reflections# Account.reflections#defreflectionsread_inheritable_attribute(:reflections)||write_inheritable_attribute(:reflections,{})enddefoutgoing_reflectionsreflections.select{|_,reflection|reflection.is_a?RDFPropertyReflection}enddefchild_resource_reflectionsreflections.select{|_,reflection|reflection.is_a?(AssociationReflection)&&reflection.macro==:contains&&reflection.klass<=ActiveFedora::File}enddefcontained_rdf_source_reflectionsreflections.select{|_,reflection|reflection.is_a?(AssociationReflection)&&reflection.macro==:contains&&!(reflection.klass<=ActiveFedora::File)}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)val=reflections[association].is_a?(AssociationReflection)?reflections[association]:nilunlessval# When a has_many is paired with a has_and_belongs_to_many the assocation will have a plural nameassociation=association.to_s.pluralize.to_symval=reflections[association].is_a?(AssociationReflection)?reflections[association]:nilendvalenddefreflect_on_all_autosave_associationsreflections.values.select{|reflection|reflection.options[:autosave]}endendclassMacroReflection# Returns the name of the macro.## <tt>has_many :clients</tt> returns <tt>:clients</tt>attr_reader:name# Returns the macro type.## <tt>has_many :clients</tt> returns <tt>:has_many</tt>attr_reader:macro# Returns the hash of options used for the macro.## <tt>has_many :clients</tt> returns +{}+attr_reader:optionsattr_reader:active_fedora# 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.defklass@klass||=class_name.constantizeenddefinitialize(macro,name,options,active_fedora)@macro=macro@name=name@options=options@active_fedora=active_fedora@automatic_inverse_of=nilenddefautosave=(autosave)@options[:autosave]=autosaveparent_reflection=self.parent_reflectionparent_reflection.autosave=autosaveifparent_reflectionend# Returns a new, unsaved instance of the associated class. +options+ will# be passed to the class's constructor.defbuild_association(*options,&block)klass.new(*options,&block)end# Returns the class name for the macro.## <tt>has_many :clients</tt> returns <tt>'Client'</tt>defclass_name@class_name||=options[:class_name]||derive_class_nameend# 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.defcollection?@collectionend# Returns +true+ if +self+ is a +belongs_to+ reflection.defbelongs_to?macro==:belongs_toenddefhas_many?macro==:has_manyenddefhas_and_belongs_to_many?macro==:has_and_belongs_to_manyendprivatedefderive_class_nameclass_name=name.to_s.camelizeclass_name=class_name.singularizeifcollection?class_nameenddefderive_foreign_keyifbelongs_to?"#{name}_id"elsifhas_and_belongs_to_many?"#{name.to_s.singularize}_ids"elsifoptions[:as]"#{options[:as]}_id"elsifinverse_of&&inverse_of.collection?# for a has_many that is the inverse of a has_and_belongs_to_many"#{options[:inverse_of].to_s.singularize}_ids"else# for a has_many that is the inverse of a belongs_toactive_fedora.name.foreign_keyendendend# Holds all the meta-data about an association as it was specified in the# Active Record class.classAssociationReflection<MacroReflection#:nodoc:attr_accessor:parent_reflection# Reflectiondefinitialize(macro,name,options,active_fedora)super@collection=[:has_many,:has_and_belongs_to_many,:directly_contains,:indirectly_contains].include?(macro)end# Creates a new instance of the associated class, and immediately saves it# with ActiveFedora::Base#save. +options+ will be passed to the class's# creation method. Returns the newly created object.defcreate_association(*options)klass.create(*options)enddefforeign_key@foreign_key||=options[:foreign_key]||derive_foreign_keyend# Returns the RDF predicate as defined by the :predicate optiondefpredicateoptions[:predicate]enddefpredicate_for_solrpredicate.fragment||predicate.to_s.rpartition(/\//).lastenddefsolr_key@solr_key||=beginActiveFedora.index_field_mapper.solr_name(predicate_for_solr,:symbol)endenddefcheck_validity!check_validity_of_inverse!enddefcheck_validity_of_inverse!returnifoptions[:polymorphic]||!(has_inverse?&&inverse_of.nil?)raiseInverseOfAssociationNotFoundError,selfend# A chain of reflections from this one back to the owner. For more see the explanation in# ThroughReflection.defchain[self]endaliassource_macromacrodefhas_inverse?inverse_nameenddefinverse_ofreturnunlessinverse_name@inverse_of||=klass._reflect_on_associationinverse_nameend# Returns whether or not the association should be validated as part of# the parent's validation.## Unless you explicitly disable validation with# <tt>:validate => false</tt>, validation will take place when:## * you explicitly enable validation; <tt>:validate => true</tt># * you use autosave; <tt>:autosave => true</tt># * the association is a +has_many+ associationdefvalidate?!options[:validate].nil??options[:validate]:(options[:autosave]==true||macro==:has_many)enddefassociation_classcasemacrowhen:containsAssociations::BasicContainsAssociationwhen:belongs_toAssociations::BelongsToAssociationwhen:has_and_belongs_to_manyAssociations::HasAndBelongsToManyAssociationwhen:has_manyAssociations::HasManyAssociationwhen:singular_rdfAssociations::SingularRDFwhen:rdfAssociations::RDFwhen:directly_containsAssociations::DirectlyContainsAssociationwhen:directly_contains_oneAssociations::DirectlyContainsOneAssociationwhen:indirectly_containsAssociations::IndirectlyContainsAssociationendendVALID_AUTOMATIC_INVERSE_MACROS=[:has_many,:has_and_belongs_to_many,:belongs_to].freezeINVALID_AUTOMATIC_INVERSE_OPTIONS=[:conditions,:through,:polymorphic,:foreign_key].freezeprivatedefinverse_nameoptions.fetch(:inverse_of)doif@automatic_inverse_of==falsenilelse@automatic_inverse_of||=automatic_inverse_ofendendend# Checks if the inverse reflection that is returned from the# +automatic_inverse_of+ method is a valid reflection. We must# make sure that the reflection's active_record name matches up# with the current reflection's klass name.## Note: klass will always be valid because when there's a NameError# from calling +klass+, +reflection+ will already be set to false.defvalid_inverse_reflection?(reflection)reflection&&klass.name==reflection.active_fedora.name&&can_find_inverse_of_automatically?(reflection)end# returns either false or the inverse association name that it finds.defautomatic_inverse_ofifcan_find_inverse_of_automatically?(self)inverse_name=ActiveSupport::Inflector.underscore(options[:as]||active_fedora.name.demodulize).to_symbeginreflection=klass._reflect_on_association(inverse_name)rescueNameError# Give up: we couldn't compute the klass type so we won't be able# to find any associations either.reflection=falseendreturninverse_nameifvalid_inverse_reflection?(reflection)endfalseend# Checks to see if the reflection doesn't have any options that prevent# us from being able to guess the inverse automatically. First, the# <tt>inverse_of</tt> option cannot be set to false. Second, we must# have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.# Third, we must not have options such as <tt>:polymorphic</tt> or# <tt>:foreign_key</tt> which prevent us from correctly guessing the# inverse association.## Anything with a scope can additionally ruin our attempt at finding an# inverse, so we exclude reflections with scopes.defcan_find_inverse_of_automatically?(reflection)reflection.options[:inverse_of]!=false&&VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro)&&!INVALID_AUTOMATIC_INVERSE_OPTIONS.any?{|opt|reflection.options[opt]}# && !reflection.scopeendendclassRDFPropertyReflection<AssociationReflectiondefderive_foreign_keynameenddefderive_class_nameclass_name=name.to_s.sub(/_ids?$/,'').camelizeclass_name=class_name.singularizeifcollection?class_nameendendendend