require 'mongoid' ## various patches module Mongoid #:nodoc: module Document def update_child_with_noname(child, clear = false) name = child.association_name return if name.blank? # fix a weird bug with mongoid-acts-as-tree update_child_without_noname(child, clear) end alias_method_chain :update_child, :noname end end module Mongoid #:nodoc: module Validations #:nodoc: class AssociatedValidator < ActiveModel::EachValidator def validate_each(document, attribute, value) values = value.is_a?(Array) ? value : [ value ] return if values.collect { |doc| doc.nil? || doc.valid? }.all? document.errors.add(attribute, :invalid, options.merge(:value => value)) # was causing "can't modify frozen hash" end end end end # http://github.com/emk/mongoid/blob/503e346b1b7b250d682a12332ad9d5872f1575e6/lib/mongoid/atomicity.rb module Mongoid #:nodoc: module Atomicity #:nodoc: extend ActiveSupport::Concern def _updates processed = {} _children.inject({ "$set" => _sets, "$pushAll" => {}, :other => {} }) do |updates, child| changes = child._sets updates["$set"].update(changes) unless changes.empty? processed[child._conficting_modification_key] = true end # MongoDB does not allow "conflicting modifications" to be # performed in a single operation. Conflicting modifications are # detected by the 'haveConflictingMod' function in MongoDB. # Examination of the code suggests that two modifications (a $set # and a $pushAll, for example) conflict if (1) the key paths being # modified are equal or (2) one key path is a prefix of the other. # So a $set of 'addresses.0.street' will conflict with a $pushAll # to 'addresses', and we will need to split our update into two # pieces. We do not, however, attempt to match MongoDB's logic # exactly. Instead, we assume that two updates conflict if the # first component of the two key paths matches. if processed.has_key?(child._conficting_modification_key) target = :other else target = "$pushAll" end child._pushes.each do |attr, val| if updates[target].has_key?(attr) updates[target][attr] << val else updates[target].update({attr => [val]}) end end updates end.delete_if do |key, value| value.empty? end end protected # Get the key used to check for conflicting modifications. For now, we # just use the first component of _path, and discard the first period # and everything that follows. def _conficting_modification_key _path.sub(/\..*/, '') end end end