module MongoMapper module Associations module ClassMethods ## # This macro allows you define a "belongs-to" relationship between one # document and some other document. # # == Requirements # # Usage of this macro requires that your document define a key that can # be used to store the ID of the target document that is the parent of # this document. # # == Conventions # # The following is a list of the conventions used by MongoMapper in # defining a belongs-to relationship. Each can likely be overridden via # the +options+ parameter. # # * The name of your belongs-to association is the lowercase, singular # name of the target class # * A key with the name of your association exists with an "_id" suffix # to store the ID of the target of this relationship # # @param [Symbol] association_id The name of this association # @param [Hash] options Optional parameters that define the # characteristics of this relationship. These are often used to # override MongoMapper conventions. # @option options [Boolean] :polymorphic (false) Set this option to # true to define a relationship that can be between this # document and any other type of document. Note that you *must* also # have a key on your document to store the type of document in this # relationship. # @option options [String] :class_name If your relationship doesn't use # the name of some class, you *must* use this option to indicate the # target class for this relationship. # @option options [Symbol] :foreign_key Use this option to specify a # non-conventional key that stores the ID of the parent in this # relationship # # @example Conventional, and simple, usage of belongs_to # class Novel # include MongoMapper::Document # # key :author_id, String # our "foreign key" # # belongs_to :author # end # # @example Using :foreign_key and :class_name # class Pet # include MongoMapper::Document # # key :person_id, String # # belongs_to :owner, # :foreign_key => :person_id, # :class_name => "Person" # end # # @example Defining a polymorphic belongs-to relationship # class Vehicle # include MongoMapper::Document # # key :owner_id, String # key :owner_type, String # # belongs_to :owner, # :polymorphic => true # end # # @example Non-standard polymorphic belongs-to relationship # class Vehicle # include MongoMapper::Document # # key :person_id, String # key :person_type, String # # belongs_to :owner, # :polymorphic => true, # :foreign_key => "person_id", # :type_key_name => "person_type" # end def belongs_to(association_id, options={}, &extension) create_association(:belongs_to, association_id, options, &extension) self end ## # This macro allows you to define a "has-many" relationship between a # document, and numerous child documents. # # == Conventions # # The following is a list of the conventions used by MongoMapper in # defining this relationship. Each can likely be overridden via the # +options+ parameter. # # * The name of your association is the lowercase, *plural* name of the # target class # * Your target class must have a "foreign key" bearing the name of this # class suffixed by "_id" # # @param [Symbol] association_id The name of this association # @param [Hash] options Optional parameters that define the # characteristics of this relationship. These are often used to # override MongoMapper conventions. # @option options [String] :class_name If your relationship doesn't use # the name of some class, you *must* use this option to indicate the # target class for this relationship. # @option options [Symbol] :foreign_key Use this option to specify a # non-conventional key that stores the ID of the parent in this # relationship # @option options [#to_s] :as Used when the target relationship is # polymorphic (i.e. the +belongs_to+ has set :polymorphic to # +true+). See examples for usage. def many(association_id, options={}, &extension) create_association(:many, association_id, options, &extension) self end def associations @associations ||= self.superclass.respond_to?(:associations) ? self.superclass.associations : HashWithIndifferentAccess.new end private def create_association(type, name, options, &extension) association = Associations::Base.new(type, name, options, &extension) associations[association.name] = association define_association_methods(association) define_dependent_callback(association) association end def define_association_methods(association) define_method(association.name) do get_proxy(association) end define_method("#{association.name}=") do |value| get_proxy(association).replace(value) value end end def define_dependent_callback(association) if association.options[:dependent] if association.many? define_dependent_callback_for_many(association) end end end def define_dependent_callback_for_many(association) after_destroy do |doc| if !association.embeddable? case association.options[:dependent] when :destroy doc.get_proxy(association).destroy_all when :delete_all doc.get_proxy(association).delete_all when :nullify doc.get_proxy(association).nullify end end end end end module InstanceMethods def get_proxy(association) unless proxy = self.instance_variable_get(association.ivar) proxy = association.proxy_class.new(self, association) self.instance_variable_set(association.ivar, proxy) if !frozen? end proxy end end end end