public
Last active — forked from zerobearing2/embedded_callbacks.rb

bubble down callbacks to embedded associations with mongoid

  • Download Gist
embedded_callbacks.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
# encoding: utf-8
module Mongoid #:nodoc:
module Associations #:nodoc:
module EmbeddedCallbacks
 
# bubble callbacks to embedded associations
def run_callbacks(kind, *args, &block)
# now bubble callbacks down
self.associations.each_pair do |name, meta|
if meta.association == Mongoid::Associations::EmbedsMany
self.send(name).each { |doc| doc.send(:run_callbacks, kind, *args, &block) }
elsif meta.association == Mongoid::Associations::EmbedsOne
self.send(name).send(:run_callbacks, kind, *args, &block)
end
end
super(kind, *args, &block) # defer to parent
end
 
end
end
end
photo.rb
Ruby
1 2 3 4 5 6 7 8
class Photo
include Mongoid::Document
include Mongoid::Timestamps
 
mount_uploader :image, ImageFileUploader
embedded_in :some_model, :inverse_of => :photos
validates_presence_of :image
end
some_model.rb
Ruby
1 2 3 4 5 6
class SomeModel
include Mongoid::Document
include Mongoid::Associations::EmbeddedCallbacks
include Mongoid::Timestamps
embeds_many :photos
end

As discussed in these issues, Mongoid's current designed behavior (as of 2.0.0.beta.20) is to not bubble down firing of callbacks to unchanged child documents in embedded associations when the parent is saved:

https://github.com/mongoid/mongoid/issues/237

https://github.com/jnicklas/carrierwave/issues/81

https://github.com/mongoid/mongoid/issues/194

This (among other things, probably), goes against Carrierwave's assumptions and breaks saving of uploads attached to the children. This is likely to be addressed in Mongoid very soon, but in the meantime this gist forces the callbacks to run in this arrangement and thus fixes Carrierwave.

I don't think this works after the recent relations refactor. Try this instead:

module Mongoid #:nodoc:
  module Associations #:nodoc:
    module EmbeddedCallbacks

      # bubble callbacks to embedded associations
      def run_callbacks(kind, *args, &block)
        # now bubble callbacks down
        self.relations.each_pair do |name, meta|
          if meta.relation == Mongoid::Relations::Embedded::Many
            self.send(name).each { |doc| doc.send(:run_callbacks, kind, *args, &block) }
          elsif meta.association == Mongoid::Relations::Embedded::One
            self.send(name).send(:run_callbacks, kind, *args, &block)
          end
        end
        super(kind, *args, &block) # defer to parent
      end

    end
  end
end

Cheers -- I haven't tested the RCs/master yet (optional support for firing the callbacks without this hack ought to be in there soon, if not already), but that looks like it should do the job.

Yes, I posted about it and they were positive given that someone would make a proper pull request and make it an option on the association (instead of the default as it is now). If you have time and want to look at it I'm sure they'll appreciate the help. I don't have git so I can't do it myself, unfortunately. Will try to get git some day and look at it though.

https://github.com/mongoid/mongoid/issues/issue/684

Just noticed the code I posted above don't work that well, it will result in three inserts in some cases. Haven't tracked down the exact cause, don't know enough about how callbacks work yet. Manually doing:

before_save lambda { |r| 
    r.embedded_model.trigger_callback_manually #works for now
}

You're getting multiple inserts on a sporadic basis because each run_callbacks call will trigger the block, which contains a persistence operation. (If you call it in a loop, watch out!) I ran into the same issue.

I submitted a pull request that resolves this problem. If it is still a blocker for you, please try out my patch and provide feedback!

https://github.com/mongoid/mongoid/pull/845

For the record I'm now using @paulrosania's patch with success, thanks Paul! Here's hoping it's merged soon.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.