# encoding: utf-8 module Mongoid #:nodoc: module Associations #:nodoc: class HasMany include Proxy attr_accessor :association_name, :klass # Appends the object to the +Array+, setting its parent in # the process. def <<(*objects) objects.flatten.each do |object| object.parentize(@parent, @association_name) @target << object object.notify end end alias :concat :<< alias :push :<< # Clears the association, and notifies the parents of the removal. def clear unless @target.empty? object = @target.first object.changed(true) object.notify_observers(object, true) @target.clear end end # Builds a new Document and adds it to the association collection. The # document created will be of the same class as the others in the # association, and the attributes will be passed into the constructor. # # Returns: # # The newly created Document. def build(attrs = {}, type = nil) object = type ? type.instantiate : @klass.instantiate object.parentize(@parent, @association_name) object.write_attributes(attrs) @target << object object end # Creates a new Document and adds it to the association collection. The # document created will be of the same class as the others in the # association, and the attributes will be passed into the constructor and # the new object will then be saved. # # Returns: # # Rhe newly created Document. def create(attrs = {}, type = nil) object = build(attrs, type) object.save object end # Finds a document in this association. # # If :all is passed, returns all the documents # # If an id is passed, will return the document for that id. # # Returns: # # Array or single Document. def find(param) return @target if param == :all return detect { |document| document.id == param } end # Creates the new association by finding the attributes in # the parent document with its name, and instantiating a # new document for each one found. These will then be put in an # internal array. # # This then delegated all methods to the array class since this is # essentially a proxy to an array itself. # # Options: # # parent: The parent document to the association. # options: The association options. def initialize(parent, options) @parent, @association_name = parent, options.name @klass, @options = options.klass, options initialize_each(parent.raw_attributes[@association_name]) extends(options) end # If the target array does not respond to the supplied method then try to # find a named scope or criteria on the class and send the call there. # # If the method exists on the array, use the default proxy behavior. def method_missing(name, *args, &block) unless @target.respond_to?(name) object = @klass.send(name, *args) object.documents = @target return object end super end # Used for setting associations via a nested attributes setter from the # parent +Document+. # # Options: # # attributes: A +Hash+ of integer keys and +Hash+ values. # # Returns: # # The newly build target Document. def nested_build(attributes) attributes.values.each do |attrs| build(attrs) end end # Paginate the association. Will create a new criteria, set the documents # on it and execute in an enumerable context. # # Options: # # options: A +Hash+ of pagination options. # # Returns: # # A +WillPaginate::Collection+. def paginate(options) criteria = Mongoid::Criteria.translate(@klass, options) criteria.documents = @target criteria.paginate end protected # Initializes each of the attributes in the hash. def initialize_each(attributes) @target = attributes ? attributes.collect do |attrs| klass = attrs.klass child = klass ? klass.instantiate(attrs) : @klass.instantiate(attrs) child.parentize(@parent, @association_name) child end : [] end class << self # Preferred method of creating a new +HasMany+ association. It will # delegate to new. # # Options: # # document: The parent +Document+ # options: The association options def instantiate(document, options) new(document, options) end # Returns the macro used to create the association. def macro :has_many end # Perform an update of the relationship of the parent and child. This # is initialized by setting the has_many to the supplied +Enumerable+ # and setting up the parentization. def update(children, parent, options) parent.remove_attribute(options.name) children.assimilate(parent, options) instantiate(parent, options) end end end end end