carrierwaveuploader/carrierwave

Mongoid Embedded Documents don't save images

anlek opened this issue · 31 comments

When having a CarrierWave uploader mounted in an embedded document, it doesn't fire (save) at all, but when I move it to the parent, it saves correctly.

There is no error or message in the embedded document saving.

any ETA on a solution to this? This will be a blocker bug for me really soon :(

I ran into this as well. I'd be happy to contribute a fix, but right now Rails3, Mongoid, and CarrierWave are new to me, so I need to be pointed in the right direction :)

I'm pretty sure it's the way Mongoid saves the document, you need the ability to call save on the embedded object during save of parent object so that CarrierWave can be triggered to do it's work. I'm not 100% sure if that correct but it's a good place to start.

That's what I'm thinking as well, but it doesn't make sense to have to call "save" on an object that's already being saved. What about another carrierwave method in the mongoid adapter, something like:

accepts_nested_file_uploads_for :children, :class=>ChildClass

And then the before_save, after_save hooks could be wired up to the parent, but act on the children.

I'm not entirely sure how to accomplish this though, this is just a thought.

Well, why not add an before save hook to the parent object that calls all the embedded children objects save command, which really should do nothing (maybe run validations) except that's what I'm assuming CarrierWave alias. Not really sure how CarrierWave works when it comes to saving an object.

It adds 3 hooks to the model's save/delete events:

  after_save "store_#{column}!".to_sym
  before_save "write_#{column}_identifier".to_sym
  after_destroy "remove_#{column}!".to_sym

The parent could execute these on it's children directly, you'd just need to loop over them & send those particular messages to each instance.

I'm not sure what you mean by this. What is "The Model" you're referring to, a parent or child?

In our case, this is the child object. But if we added another method on the parent that would execute these methods "store_#{column}!", etc on each child I think it would work.

I would rather see it in the child as the parent shouldn't care that a child would have a file or not (also, developers won't know to look in the parent for a child's function). So we need a way for the parent to always fire a method when it's being saved and embedding it's children, then attached save_#{column}! to that?

...or ask the mongoid folks if there's a way for an embedded document to get notified when the parent is saved.

(actually I think we're saying the same thing)

The main issue here is that the callbacks on the children are not firing in mongoid anymore if you called save on the parent... If you call save on the embedded document it will properly save as callbacks bubble up, but not down. (this is by design.)

I am trying to figure out a workaround for this that I can put in Mongoid... More info as I get there.

Makes sense, but hoping for a workaround for these scenarios. Thanks, durran!

While I am looking into this - your current workaround is to make the save call on the embedded document itself. That will save the file. For example, if you have a person that embeds many addresses, and you have an uploader mounted on address, calling save on the address will save the file. Calling save on person will not.

I created a hack to bubble down the callbacks from the parent model to the embedded associations of a mongoid document. I created this to get Carrierwave working correctly. Here is quick example of what I did: http://gist.github.com/566242 -- CAUTION, only tested it locally; using Rails 3.0.

Hack from zerobearing2 doesn't work for me since the prototype of function run_callbacks in the snippet is different from run_callbacks(kind, *args, &block) in ActiveSupport::Callbacks. Also validation callbacks are not supported this way since they are triggered directly by the valid? method. Here i've changed the gist from zerobearing2 to fit my needs: http://gist.github.com/574705. Rails 3 + MongoID 2.0.0beta17

mcasimir: you should be able to add 'validates_associated :name_of_assocation' to the parent model and have it then validate each association when the parent is validated.

thanks for fixing the run_callback method signature too -- i've updated my hack locally and works nicely. cheers! :-)

It seems i've discovered another issue. When i use mount_uploader in embedded documents upload works nice and attachments are saved correctly, but for some reason filename remains blank.. is there anyone that has experienced the same issue?

Is this still an issue? I'd like to close if possible...? Let me know. mcasimir, it sounds like you've identified another separate issue - could be good to create a new ticket. Thanks!

Yes, i think it's a different issue, i'm tring to understand if this affects only s3 uploaders or not. I'll open a new ticket anyway.

I've changed idea:
I'think the new issue identified depends strictly on this issue and on the solution purposed in zerobearing gist, is it ok to reference this issue into the new ticket?

i've solved that with an after_save callback in embedded document model:

    after_save :extract_filename

    def extract_filename
      self.class.skip_callback(:save, :after, :extract_filename)

      @photo_original_filename = photo.instance_variable_get("@original_filename")
      if @photo_original_filename.present?
        self.update_attributes :photo_filename => @photo_original_filename
      end

      self.class.set_callback(:save, :after, :extract_filename)
    end

..but i suppose that this still remains an issue, is it correct?

It seems that carrierwave actually writes nil into the filename attribute since if i use before_save instead of after_save filename is set correctly at the end of the callback but then it turns back to blank after save.

I can't find a better solution since looking into the sources i can't figure out where and how uploader filenames are set.

That's all, please tell me if it's needed to open another ticket.
Maurizio

Lewy commented

After 5 months it is still issue for me. Uploader doesent work in embeded document but if I save object manualy upload works but dosen't set proper filename

Does the author have any input on this issue?

I haven't had a need to get this fixed myself. IMHO it's more about an architectural decision in mongoid that may not be the best. If someone can find an elegant workaround, I'd love to accept a patch, but it's not likely that I'll invest my time to fix this. Sorry.

I'm closing this issue as it seems settled.

From what I gather, there is a workaround for this known limitation as mentioned by durran. I'm adding that information to the wiki now. I'd appreciate it if someone would look at the wiki, and possibly provide some code examples etc to help others who may be hit with the same limitation.

If there's concern about this information being easily found, I can add a note to the readme as well. I'm adding a link to the wiki to the top of the readme now.

If someone would like take a shot at providing a pull request to improve Carrierwave, we can have another look.

Since this issue ranks pretty highly on Google I'd like to add that this gem https://github.com/alexeypetrushin/mongoid_misc patches this perfectly and gets embedded Mongoid documents to play nice with CarrierWave. Hope this helps someone.

@scomma please update the wiki!

This issue is pretty easily solved using cascade_callbacks. See https://github.com/mongoid/mongoid/pull/1058 for the pull request. All you need to do is:

embeds_many :photos, :cascade_callbacks => true

the cascading callbacks only work if fields in the embedded document have changed. so if you change the attached file by uploading a new one and at the same time changing other fields, then the new attachment is saved.

BUT if you only upload a new file, the file is saved in the uploads directory but not in the embedded document. after reloading the object, it still points to the old file. i think there is still something wrong, mongoid seems not to regognize a change in the model if only the file changes

is this issue fixed?..can i get it in the latest version of carrierwave?