# encoding: utf-8 module Mongoid module Relations # The "Grand Poobah" of information about any relation is this class. It # contains everything you could ever possible want to know. class Metadata < Hash delegate :foreign_key_default, :stores_foreign_key?, to: :relation # Returns the as option of the relation. # # @example Get the as option. # metadata.as # # @return [ true, false ] The as option. # # @since 2.1.0 def as self[:as] end # Tells whether an as option exists. # # @example Is the as option set? # metadata.as? # # @return [ true, false ] True if an as exists, false if not. # # @since 2.0.0.rc.1 def as? !!as end # Is the relation autobuilding if accessed via the getter and the # document is new. # # @example Is the relation autobuilding? # metadata.autobuilding? # # @return [ true, false ] If the relation autobuilds. # # @since 3.0.0 def autobuilding? !!self[:autobuild] end # Returns the autosave option of the relation. # # @example Get the autosave option. # metadata.autosave # # @return [ true, false ] The autosave option. # # @since 2.1.0 def autosave self[:autosave] end # Does the metadata have a autosave option? # # @example Is the relation autosaving? # metadata.autosave? # # @return [ true, false ] If the relation autosaves. # # @since 2.1.0 def autosave? !!autosave end # Gets a relation builder associated with the relation this metadata is # for. # # @example Get the builder. # metadata.builder(document) # # @param [ Document ] base The base document. # @param [ Object ] object A document or attributes to give the builder. # # @return [ Builder ] The builder for the relation. # # @since 2.0.0.rc.1 def builder(base, object) relation.builder(base, self, object) end # Returns the name of the strategy used for handling dependent relations. # # @example Get the strategy. # metadata.cascade_strategy # # @return [ Object ] The cascading strategy to use. # # @since 2.0.0.rc.1 def cascade_strategy if dependent? "Mongoid::Relations::Cascading::#{dependent.to_s.classify}".constantize end end # Is this an embedded relations that allows callbacks to cascade down to # it? # # @example Does the relation have cascading callbacks? # metadata.cascading_callbacks? # # @return [ true, false ] If the relation cascades callbacks. # # @since 2.3.0 def cascading_callbacks? !!self[:cascade_callbacks] end # Returns the name of the class that this relation contains. If the # class_name was provided as an option this will return that, otherwise # it will determine the name from the name property. # # @example Get the class name. # metadata.class_name # # @return [ String ] The name of the relation's proxied class. # # @since 2.0.0.rc.1 def class_name @class_name ||= (self[:class_name] || classify).sub(/\A::/,"") end # Get the foreign key contraint for the metadata. # # @example Get the constaint. # metadata.constraint # # @return [ Constraint ] The constraint. # # @since 2.0.0.rc.1 def constraint @constraint ||= Constraint.new(self) end # Does the metadata have a counter cache? # # @example Is the metadata counter_cached? # metadata.counter_cached? # # @return [ true, false ] If the metadata has counter_cache # # @since 3.1.0 def counter_cached? !!self[:counter_cache] end # Returns the counter cache column name # # @example Get the counter cache column. # metadata.counter_cache_column_name # # @return [ String ] The counter cache column # # @since 3.1.0 def counter_cache_column_name if self[:counter_cache] == true "#{inverse || inverse_class_name.demodulize.underscore.pluralize}_count" else self[:counter_cache].to_s end end # Get the criteria that is used to query for this metadata's relation. # # @example Get the criteria. # metadata.criteria([ id_one, id_two ], Person) # # @param [ Object ] object The foreign key used for the query. # @param [ Class ] type The base class. # # @return [ Criteria ] The criteria. # # @since 2.1.0 def criteria(object, type) relation.criteria(self, object, type) end # Returns the cyclic option of the relation. # # @example Get the cyclic option. # metadata.cyclic # # @return [ true, false ] The cyclic option. # # @since 2.1.0 def cyclic self[:cyclic] end # Does the metadata have a cyclic option? # # @example Is the metadata cyclic? # metadata.cyclic? # # @return [ true, false ] If the metadata is cyclic. # # @since 2.1.0 def cyclic? !!cyclic end # Returns the dependent option of the relation. # # @example Get the dependent option. # metadata.dependent # # @return [ Symbol ] The dependent option. # # @since 2.1.0 def dependent self[:dependent] end # Does the metadata have a dependent option? # # @example Is the metadata performing cascades? # metadata.dependent? # # @return [ true, false ] If the metadata cascades. # # @since 2.1.0 def dependent? !!dependent end # Will determine if the relation is an embedded one or not. Currently # only checks against embeds one and many. # # @example Is the document embedded. # metadata.embedded? # # @return [ true, false ] True if embedded, false if not. # # @since 2.0.0.rc.1 def embedded? @embedded ||= (macro == :embeds_one || macro == :embeds_many) end # Returns the extension of the relation. # # @example Get the relation extension. # metadata.extension # # @return [ Module ] The extension or nil. # # @since 2.0.0.rc.1 def extension self[:extend] end # Tells whether an extension definition exist for this relation. # # @example Is an extension defined? # metadata.extension? # # @return [ true, false ] True if an extension exists, false if not. # # @since 2.0.0.rc.1 def extension? !!extension end # Does this metadata have a forced nil inverse_of defined. (Used in many # to manies) # # @example Is this a forced nil inverse? # metadata.forced_nil_inverse? # # @return [ true, false ] If inverse_of has been explicitly set to nil. # # @since 2.3.3 def forced_nil_inverse? @forced_nil_inverse ||= has_key?(:inverse_of) && inverse_of.nil? end # Handles all the logic for figuring out what the foreign_key is for each # relations query. The logic is as follows: # # 1. If the developer defined a custom key, use that. # 2. If the relation stores a foreign key, # use the class_name_id strategy. # 3. If the relation does not store the key, # use the inverse_class_name_id strategy. # # @example Get the foreign key. # metadata.foreign_key # # @return [ String ] The foreign key for the relation. # # @since 2.0.0.rc.1 def foreign_key @foreign_key ||= determine_foreign_key end # Get the name of the method to check if the foreign key has changed. # # @example Get the foreign key check method. # metadata.foreign_key_check # # @return [ String ] The foreign key check. # # @since 2.1.0 def foreign_key_check @foreign_key_check ||= "#{foreign_key}_changed?" end # Returns the name of the method used to set the foreign key on a # document. # # @example Get the setter for the foreign key. # metadata.foreign_key_setter # # @return [ String ] The foreign_key plus =. # # @since 2.0.0.rc.1 def foreign_key_setter @foreign_key_setter ||= "#{foreign_key}=" end # Returns the index option of the relation. # # @example Get the index option. # metadata.index # # @return [ true, false ] The index option. # # @since 2.1.0 def index self[:index] end # Tells whether a foreign key index exists on the relation. # # @example Is the key indexed? # metadata.indexed? # # @return [ true, false ] True if an index exists, false if not. # # @since 2.0.0.rc.1 def indexed? !!index end # Instantiate new metadata for a relation. # # @example Create the new metadata. # Metadata.new(:name => :addresses) # # @param [ Hash ] properties The relation options. # # @since 2.0.0.rc.1 def initialize(properties = {}) Options.validate!(properties) merge!(properties) end # Since a lot of the information from the metadata is inferred and not # explicitly stored in the hash, the inspection needs to be much more # detailed. # # @example Inspect the metadata. # metadata.inspect # # @return [ String ] Oodles of information in a nice format. # # @since 2.0.0.rc.1 def inspect %Q{#<Mongoid::Relations::Metadata autobuild: #{autobuilding?} class_name: #{class_name} cyclic: #{cyclic.inspect} counter_cache:#{counter_cached?} dependent: #{dependent.inspect} inverse_of: #{inverse_of.inspect} key: #{key} macro: #{macro} name: #{name} order: #{order.inspect} polymorphic: #{polymorphic?} relation: #{relation} setter: #{setter}> } end # Get the name of the inverse relations if they exists. If this is a # polymorphic relation then just return the :as option that was defined. # # @example Get the names of the inverses. # metadata.inverses # # @param [ Document ] other The document to aid in the discovery. # # @return [ Array<Symbol> ] The inverse name. def inverses(other = nil) if self[:polymorphic] lookup_inverses(other) else @inverses ||= determine_inverses end end # Get the name of the inverse relation if it exists. If this is a # polymorphic relation then just return the :as option that was defined. # # @example Get the name of the inverse. # metadata.inverse # # @param [ Document ] other The document to aid in the discovery. # # @return [ Symbol ] The inverse name. # # @since 2.0.0.rc.1 def inverse(other = nil) invs = inverses(other) invs.first if invs.count == 1 end # Returns the inverse_class_name option of the relation. # # @example Get the inverse_class_name option. # metadata.inverse_class_name # # @return [ true, false ] The inverse_class_name option. # # @since 2.1.0 def inverse_class_name self[:inverse_class_name] end # Returns the if the inverse class name option exists. # # @example Is an inverse class name defined? # metadata.inverse_class_name? # # @return [ true, false ] If the inverse if defined. # # @since 2.1.0 def inverse_class_name? !!inverse_class_name end # Used for relational many to many only. This determines the name of the # foreign key field on the inverse side of the relation, since in this # case there are keys on both sides. # # @example Find the inverse foreign key # metadata.inverse_foreign_key # # @return [ String ] The foreign key on the inverse. # # @since 2.0.0.rc.1 def inverse_foreign_key @inverse_foreign_key ||= determine_inverse_foreign_key end # Used for relational many to many only. This determines the name of the # foreign key field setter on the inverse side of the relation, since in this # case there are keys on both sides. # # @example Find the inverse foreign key setter. # metadata.inverse_foreign_key_setter # # @return [ String ] The foreign key setter on the inverse. # # @since 2.0.0.rc.1 def inverse_foreign_key_setter @inverse_foreign_key_setter ||= "#{inverse_foreign_key}=" end # Returns the inverse class of the proxied relation. # # @example Get the inverse class. # metadata.inverse_klass # # @return [ Class ] The class of the inverse of the relation. # # @since 2.0.0.rc.1 def inverse_klass @inverse_klass ||= inverse_class_name.constantize end # Get the metadata for the inverse relation. # # @example Get the inverse metadata. # metadata.inverse_metadata(doc) # # @param [ Document, Class ] object The document or class. # # @return [ Metadata ] The inverse metadata. # # @since 2.1.0 def inverse_metadata(object) object.reflect_on_association(inverse(object)) end # Returns the inverse_of option of the relation. # # @example Get the inverse_of option. # metadata.inverse_of # # @return [ true, false ] The inverse_of option. # # @since 2.1.0 def inverse_of self[:inverse_of] end # Does the metadata have a inverse_of option? # # @example Is an inverse_of defined? # metadata.inverse_of? # # @return [ true, false ] If the relation has an inverse_of defined. # # @since 2.1.0 def inverse_of? !!inverse_of end # Returns the setter for the inverse side of the relation. # # @example Get the inverse setter. # metadata.inverse_setter # # @param [ Document ] other A document to aid in the discovery. # # @return [ String ] The inverse setter name. # # @since 2.0.0.rc.1 def inverse_setter(other = nil) inverse(other).__setter__ end # Returns the name of the field in which to store the name of the class # for the polymorphic relation. # # @example Get the name of the field. # metadata.inverse_type # # @return [ String ] The name of the field for storing the type. # # @since 2.0.0.rc.1 def inverse_type @inverse_type ||= determine_inverse_for(:type) end # Gets the setter for the field that sets the type of document on a # polymorphic relation. # # @example Get the inverse type setter. # metadata.inverse_type_setter # # @return [ String ] The name of the setter. # # @since 2.0.0.rc.1 def inverse_type_setter @inverse_type_setter ||= inverse_type.__setter__ end # This returns the key that is to be used to grab the attributes for the # relation or the foreign key or id that a referenced relation will use # to query for the object. # # @example Get the lookup key. # metadata.key # # @return [ String ] The association name, foreign key name, or _id. # # @since 2.0.0.rc.1 def key @key ||= determine_key end # Returns the class of the proxied relation. # # @example Get the class. # metadata.klass # # @return [ Class ] The class of the relation. # # @since 2.0.0.rc.1 def klass @klass ||= class_name.constantize end # Is this metadata representing a one to many or many to many relation? # # @example Is the relation a many? # metadata.many? # # @return [ true, false ] If the relation is a many. # # @since 2.1.6 def many? @many ||= (relation.macro.to_s =~ /many/) end # Returns the macro for the relation of this metadata. # # @example Get the macro. # metadata.macro # # @return [ Symbol ] The macro. # # @since 2.0.0.rc.1 def macro relation.macro end # Get the name associated with this metadata. # # @example Get the name. # metadata.name # # @return [ Symbol ] The name. # # @since 2.1.0 def name self[:name] end # Is the name defined? # # @example Is the name defined? # metadata.name? # # @return [ true, false ] If the name is defined. # # @since 2.1.0 def name? !!name end # Does the relation have a destructive dependent option specified. This # is true for :dependent => :delete and :dependent => :destroy. # # @example Is the relation destructive? # metadata.destructive? # # @return [ true, false ] If the relation is destructive. # # @since 2.1.0 def destructive? @destructive ||= (dependent == :delete || dependent == :destroy) end # Gets a relation nested builder associated with the relation this metadata # is for. Nested builders are used in conjunction with nested attributes. # # @example Get the nested builder. # metadata.nested_builder(attributes, options) # # @param [ Hash ] attributes The attributes to build the relation with. # @param [ Hash ] options Options for the nested builder. # # @return [ NestedBuilder ] The nested builder for the relation. # # @since 2.0.0.rc.1 def nested_builder(attributes, options) if polymorphic? && options[:class_name] self[:class_name] = options[:class_name] end relation.nested_builder(self, attributes, options) end # Get the path calculator for the supplied document. # # @example Get the path calculator. # metadata.path(document) # # @param [ Document ] document The document to calculate on. # # @return [ Object ] The atomic path calculator. # # @since 2.1.0 def path(document) relation.path(document) end # Returns true if the relation is polymorphic. # # @example Is the relation polymorphic? # metadata.polymorphic? # # @return [ true, false ] True if the relation is polymorphic, false if not. # # @since 2.0.0.rc.1 def polymorphic? @polymorphic ||= (!!self[:as] || !!self[:polymorphic]) end # Get the primary key field for finding the related document. # # @example Get the primary key. # metadata.primary_key # # @return [ String ] The primary key field. # # @since 3.1.0 def primary_key @primary_key ||= (self[:primary_key] || "_id").to_s end # Get the relation associated with this metadata. # # @example Get the relation. # metadata.relation # # @return [ Proxy ] The relation proxy class. # # @since 2.1.0 def relation self[:relation] end # Gets the method name used to set this relation. # # @example Get the setter. # metadata = Metadata.new(:name => :person) # metadata.setter # => "person=" # # @return [ String ] The name plus "=". # # @since 2.0.0.rc.1 def setter @setter ||= "#{name}=" end # Returns the name of the field in which to store the name of the class # for the polymorphic relation. # # @example Get the name of the field. # metadata.inverse_type # # @return [ String ] The name of the field for storing the type. # # @since 2.0.0.rc.1 def type @type ||= polymorphic? ? "#{as}_type" : nil end # Gets the setter for the field that sets the type of document on a # polymorphic relation. # # @example Get the inverse type setter. # metadata.inverse_type_setter # # @return [ String ] The name of the setter. # # @since 2.0.0.rc.1 def type_setter @type_setter ||= type.__setter__ end # Key where embedded document is save. # By default is the name of relation # # @return [ String ] the name of key where save # # @since 3.0.0 def store_as @store_as ||= (self[:store_as].try(:to_s) || name.to_s) end # Are we validating this relation automatically? # # @example Is automatic validation on? # metadata.validate? # # @return [ true, false ] True unless explictly set to false. # # @since 2.0.0.rc.1 def validate? if self[:validate].nil? self[:validate] = relation.validation_default else self[:validate] end end # Returns the metadata itself. Here for compatibility with Rails # association metadata. # # @example Get the options. # metadata.options # # @return [ Metadata ] self. # # @since 2.4.6 def options self end # Returns default order for this association. # # @example Get default order # metadata.order # # @return [ Criterion::Complex, nil] nil if doesn't set # # @since 2.1.0 def order self[:order] end # Is a default order set? # # @example Is the order set? # metadata.order? # # @return [ true, false ] If the order is set. # # @since 2.1.0 def order? !!order end # Is this relation touchable? # # @example Is the relation touchable? # metadata.touchable? # # @return [ true, false ] If the relation can be touched. # # @since 3.0.0 def touchable? !!self[:touch] end # Returns the metadata class types. # # @example Get the relation class types. # metadata.type_relation # # @return [ Hash ] The hash with relation class types. # # @since 3.1.0 def type_relation { _type: { "$in" => klass._types }} end private # Returns the class name for the relation. # # @example Get the class name. # metadata.classify # # @return [ String ] The classified name. # # @since 2.0.0.rc.1 def classify @classify ||= "#{find_module}::#{name.to_s.classify}" end # Get the name for the inverse field. # # @api private # # @example Get the inverse field name. # metadata.determine_inverse_for(:type) # # @param [ Symbol ] field The inverse field name. # # @return [ String ] The name of the field. # # @since 3.0.0 def determine_inverse_for(field) relation.stores_foreign_key? && polymorphic? ? "#{name}_#{field}" : nil end # Deterimene the inverses that can be memoized. # # @api private # # @example Determin the inverses. # metadata.determine_inverses # # @return [ Array<Symbol> ] The inverses. # # @since 3.0.0 def determine_inverses return [ inverse_of ] if has_key?(:inverse_of) return [ as ] if has_key?(:as) return [ cyclic_inverse ] if self[:cyclic] [ inverse_relation ] end # Find the module the class with the specific name is in. # This is done by starting at the inverse_class_name's # module and stepping down to see where it is defined. # # @api private # # @example Find the module. # metadata.find_module # # @return [ String ] The module. # # @since 3.0.0 def find_module if inverse_class_name.present? parts = inverse_class_name.split('::') modules = parts.size.times.map { |i| parts.first(i).join('::') }.reverse find_from_parts(modules) end end # Find the modules from a reversed list. # # @api private # # @example Find the module from the parts. # metadata.find_from_parts([ "Namespace", "Module" ]) # # @param [ Array<String> ] The modules. # # @return [ String ] The matching module. # # @since 3.0.0 def find_from_parts(modules) modules.find do |mod| if mod.blank? false else ActiveSupport::Inflector.constantize(mod).constants.include?( name.to_s.classify.to_sym ) end end end # Get the name of the inverse relation in a cyclic relation. # # @example Get the cyclic inverse name. # # class Role # include Mongoid::Document # embedded_in :parent_role, :cyclic => true # embeds_many :child_roles, :cyclic => true # end # # metadata = Metadata.new(:name => :parent_role) # metadata.cyclic_inverse # => "child_roles" # # @return [ String ] The cyclic inverse name. # # @since 2.0.0.rc.1 def cyclic_inverse @cyclic_inverse ||= determine_cyclic_inverse end # Determine the cyclic inverse. Performance improvement with the # memoization. # # @example Determine the inverse. # metadata.determine_cyclic_inverse # # @return [ String ] The cyclic inverse name. # # @since 2.0.0.rc.1 def determine_cyclic_inverse underscored = class_name.demodulize.underscore klass.relations.each_pair do |key, meta| if key =~ /#{underscored.singularize}|#{underscored.pluralize}/ && meta.relation != relation return key.to_sym end end end # Determine the value for the relation's foreign key. Performance # improvement. # # @example Determine the foreign key. # metadata.determine_foreign_key # # @return [ String ] The foreign key. # # @since 2.0.0.rc.1 def determine_foreign_key return self[:foreign_key].to_s if self[:foreign_key] return nil if relation.embedded? if relation.stores_foreign_key? relation.foreign_key(name) else suffix = relation.foreign_key_suffix "#{inverse}#{suffix}" end end # Determine the inverse foreign key of the relation. # # @example Determine the inverse foreign key. # metadata.determine_inverse_foreign_key # # @return [ String ] The inverse. # # @since 2.3.2 def determine_inverse_foreign_key if has_key?(:inverse_of) inverse_of ? "#{inverse_of.to_s.singularize}#{relation.foreign_key_suffix}" : nil else "#{inverse_class_name.demodulize.underscore}#{relation.foreign_key_suffix}" end end # Determine the inverse relation. Memoizing #inverse_relation and adding # this method dropped 5 seconds off the test suite as a performance # improvement. # # @example Determine the inverse. # metadata.determine_inverse_relation # # @return [ Symbol ] The name of the inverse. # # @since 2.0.0.rc.1 def determine_inverse_relation default = foreign_key_match || klass.relations[inverse_klass.name.underscore] return default.name if default names = inverse_relation_candidate_names if names.size > 1 raise Errors::AmbiguousRelationship.new(klass, inverse_klass, name, names) end names.first end # Return metadata where the foreign key matches the foreign key on this # relation. # # @api private # # @example Return a foreign key match. # meta.foreign_key_match # # @return [ Metadata ] A match, if any. # # @since 2.4.11 def foreign_key_match if fk = self[:foreign_key] relations_metadata.detect do |meta| fk == meta.foreign_key if meta.stores_foreign_key? end end end # Get the inverse relation candidates. # # @api private # # @example Get the inverse relation candidates. # metadata.inverse_relation_candidates # # @return [ Array<Metdata> ] The candidates. # # @since 3.0.0 def inverse_relation_candidates relations_metadata.select do |meta| next if meta.name == name (meta.class_name == inverse_class_name) && !meta.forced_nil_inverse? end end # Get the candidates for inverse relations. # # @api private # # @example Get the candidates. # metadata.inverse_relation_candidates # # @return [ Array<Symbol> ] The candidates. # # @since 3.0.0 def inverse_relation_candidate_names @candidate_names ||= inverse_relation_candidates.map(&:name) end # Determine the key for the relation in the attributes. # # @example Get the key. # metadata.determine_key # # @return [ String ] The key in the attributes. # # @since 2.0.0.rc.1 def determine_key return store_as.to_s if relation.embedded? relation.stores_foreign_key? ? foreign_key : primary_key end # Determine the name of the inverse relation. # # @example Get the inverse name. # metadata.inverse_relation # # @return [ Symbol ] The name of the inverse relation. # # @since 2.0.0.rc.1 def inverse_relation @inverse_relation ||= determine_inverse_relation end # Infer the name of the inverse relation from the class. # # @example Get the inverse name # metadata.inverse_name # # @return [ String ] The inverse class name underscored. # # @since 2.0.0.rc.1 def inverse_name @inverse_name ||= inverse_klass.name.underscore end # For polymorphic children, we need to figure out the inverse from the # actual instance on the other side, since we cannot know the exact class # name to infer it from at load time. # # @example Find the inverses. # metadata.lookup_inverses(other) # # @param [ Document ] : The inverse document. # # @return [ Array<String> ] The inverse names. def lookup_inverses(other) return [ inverse_of ] if inverse_of if other matches = [] other.class.relations.values.each do |meta| if meta.as == name && meta.class_name == inverse_class_name matches.push(meta.name) end end matches end end # For polymorphic children, we need to figure out the inverse from the # actual instance on the other side, since we cannot know the exact class # name to infer it from at load time. # # @example Find the inverse. # metadata.lookup_inverse(other) # # @param [ Document ] : The inverse document. # # @return [ String ] The inverse name. # # @since 2.0.0.rc.1 def lookup_inverse(other) if invs = lookup_inverses(other) && invs.count == 1 invs.first end end # Get the relation metadata only. # # @api private # # @example Get the relation metadata. # metadata.relations_metadata # # @return [ Array<Metadata> ] The metadata. # # @since 3.0.0 def relations_metadata klass.relations.values end end end end