# encoding: utf-8 require "mongoid/relations/marshalable" module Mongoid module Relations # This class is the superclass for all relation proxy objects, and contains # common behaviour for all of them. class Proxy alias :extend_proxy :extend # We undefine most methods to get them sent through to the target. instance_methods.each do |method| undef_method(method) unless method =~ /^(__.*|send|object_id|equal\?|respond_to\?|tap|public_send|extend_proxy|extend_proxies)$/ end include Threaded::Lifecycle include Marshalable attr_accessor :base, :__metadata, :target alias :relation_metadata :__metadata # Backwards compatibility with Mongoid beta releases. delegate :foreign_key, :inverse_foreign_key, to: :__metadata delegate :bind_one, :unbind_one, to: :binding delegate :collection_name, to: :base # Convenience for setting the target and the metadata properties since # all proxies will need to do this. # # @example Initialize the proxy. # proxy.init(person, name, metadata) # # @param [ Document ] base The base document on the proxy. # @param [ Document, Array ] target The target of the proxy. # @param [ Metadata ] metadata The relation's metadata. # # @since 2.0.0.rc.1 def init(base, target, metadata) @base, @target, @__metadata = base, target, metadata yield(self) if block_given? extend_proxies(metadata.extension) if metadata.extension? end # Allow extension to be an array and extend each module def extend_proxies(*extension) extension.flatten.each {|ext| extend_proxy(ext) } end # Get the class from the metadata, or return nil if no metadata present. # # @example Get the class. # proxy.klass # # @return [ Class ] The relation class. # # @since 3.0.15 def klass __metadata ? __metadata.klass : nil end # Resets the criteria inside the relation proxy. Used by many to many # relations to keep the underlying ids array in sync. # # @example Reset the relation criteria. # person.preferences.reset_relation_criteria # # @since 3.0.14 def reset_unloaded target.reset_unloaded(criteria) end # The default substitutable object for a relation proxy is the clone of # the target. # # @example Get the substitutable. # proxy.substitutable # # @return [ Object ] A clone of the target. # # @since 2.1.6 def substitutable target end protected # Get the collection from the root of the hierarchy. # # @example Get the collection. # relation.collection # # @return [ Collection ] The root's collection. # # @since 2.0.0 def collection root = base._root root.collection unless root.embedded? end # Takes the supplied document and sets the metadata on it. # # @example Set the metadata. # proxt.characterize_one(name) # # @param [ Document ] document The document to set on. # # @since 2.0.0.rc.4 def characterize_one(document) document.__metadata = __metadata unless document.__metadata end # Default behavior of method missing should be to delegate all calls # to the target of the proxy. This can be overridden in special cases. # # @param [ String, Symbol ] name The name of the method. # @param [ Array ] args The arguments passed to the method. # def method_missing(name, *args, &block) target.send(name, *args, &block) end # When the base document illegally references an embedded document this # error will get raised. # # @example Raise the error. # relation.raise_mixed # # @raise [ Errors::MixedRelations ] The error. # # @since 2.0.0 def raise_mixed raise Errors::MixedRelations.new(base.class, __metadata.klass) end # When the base is not yet saved and the user calls create or create! # on the relation, this error will get raised. # # @example Raise the error. # relation.raise_unsaved(post) # # @param [ Document ] doc The child document getting created. # # @raise [ Errors::UnsavedDocument ] The error. # # @since 2.0.0.rc.6 def raise_unsaved(doc) raise Errors::UnsavedDocument.new(base, doc) end # Return the name of defined callback method # # @example returns the before_add callback method name # callback_method(:before_add) # # @param [ Symbol ] callback_name Which callback # # @return [ Array ] with callback methods to be executed, the array may have symbols and Procs # # @since 3.1.0 def callback_method(callback_name) methods = [] metadata = __metadata[callback_name] if metadata if metadata.is_a?(Array) methods.concat(metadata) else methods << metadata end end methods end # Executes a callback method # # @example execute the before add callback # execute_callback(:before_add) # # @param [ Symbol ] callback to be executed # # @since 3.1.0 def execute_callback(callback, doc) callback_method = callback_method(callback) if callback_method callback_method.each do |c| if c.is_a? Proc c.call(base, doc) else base.send c, doc end end end end class << self # Apply ordering to the criteria if it was defined on the relation. # # @example Apply the ordering. # Proxy.apply_ordering(criteria, metadata) # # @param [ Criteria ] criteria The criteria to modify. # @param [ Metadata ] metadata The relation metadata. # # @return [ Criteria ] The ordered criteria. # # @since 3.0.6 def apply_ordering(criteria, metadata) metadata.order ? criteria.order_by(metadata.order) : criteria end end end end end