# encoding: utf-8
module Mongoid # :nodoc:
module Relations #:nodoc:
module Builders #:nodoc:
module NestedAttributes #:nodoc:
class Many < NestedBuilder
# Builds the relation depending on the attributes and the options
# passed to the macro.
#
# This attempts to perform 3 operations, either one of an update of
# the existing relation, a replacement of the relation with a new
# document, or a removal of the relation.
#
# Example:
#
# many.build(person)
#
# Options:
#
# parent: The parent document of the relation.
def build(parent)
@existing = parent.send(metadata.name)
if over_limit?(attributes)
raise Errors::TooManyNestedAttributeRecords.new(existing, options[:limit])
end
attributes.each do |attrs|
if attrs.respond_to?(:with_indifferent_access)
process(attrs)
else
process(attrs[1])
end
end
end
# Create the new builder for nested attributes on one-to-many
# relations.
#
# Example:
#
# One.new(metadata, attributes, options)
#
# Options:
#
# metadata: The relation metadata
# attributes: The attributes hash to attempt to set.
# options: The options defined.
#
# Returns:
#
# A new builder.
def initialize(metadata, attributes, options = {})
if attributes.respond_to?(:with_indifferent_access)
@attributes = attributes.with_indifferent_access.sort do |a, b|
a[0].to_i <=> b[0].to_i
end
else
@attributes = attributes
end
@metadata = metadata
@options = options
end
private
# Can the existing relation potentially be deleted?
#
# Example:
#
# destroyable?({ :_destroy => "1" })
#
# Options:
#
# attributes: The attributes to pull the flag from.
#
# Returns:
#
# True if the relation can potentially be deleted.
def destroyable?(attributes)
destroy = attributes.delete(:_destroy)
[ 1, "1", true, "true" ].include?(destroy) && allow_destroy?
end
# Are the supplied attributes of greater number than the supplied
# limit?
#
# Example:
#
# builder.over_limit?({ "street" => "Bond" })
#
# Options:
#
# attributes: The attributes being set.
#
# Returns:
#
# True if a limit supplied and the attributes are of greater number.
def over_limit?(attributes)
limit = options[:limit]
limit ? attributes.size > limit : false
end
# Process each set of attributes one at a time for each potential
# new, existing, or ignored document.
#
# Example:
#
# builder.process({ "id" => 1, "street" => "Bond" })
#
# Options:
#
# attrs: The single document attributes to process.
def process(attrs)
return if reject?(attrs)
if id = attrs[:id] || attrs["id"] || attrs["_id"]
document = existing.find(convert_id(id))
destroyable?(attrs) ? document.destroy : document.update_attributes(attrs)
else
existing.push(metadata.klass.new(attrs)) unless destroyable?(attrs)
end
end
end
end
end
end
end