Protect the original version

93 views
Skip to first unread message

erik.da...@gmail.com

unread,
Jan 5, 2016, 3:20:16 PM1/5/16
to ruby-shrine

Hi,

I need to find a way to store and protect the original version of the very first uploaded image to a record. Consecutive uploads to the same record should regenerate the small, medium and large versions but not overwrite the original version.

The reason I want this behaviour is that the user can annotate the original image in the browser using a js-based image editor. The annotated image is rendered client-side with the original version as source and then re-uploaded to the record. I want the small, medium and large versions to be regenerated from the annotated image but the original to stay "untouched".

I'm not sure where to put this logic or how to do it? I'm thinking about some conditionals in the process method but maybe there is a better approach?

(I'm using Rails for my application)

erik.da...@gmail.com

unread,
Jan 6, 2016, 3:53:56 AM1/6/16
to ruby-shrine, erik.da...@gmail.com
After thinking about it some more maybe it would be better to have two methods on the model with different uploaders? The first method `master_image` to hold the original image (only one version) and a second method `image` to hold the image (possibly annotated) with the small, medium and large versions.

So a user creates a record by uploading an image to the `master_image` method. If it pass validations I would like the same image to be sent to the `image` method to set a default because the user doesn't necessarily have to annotate the image later.

If the user chose to annotate, the consecutive uploads will be made to the `image` method.

I'm not sure if this is a better approach or if I'm over complicating this? I'm also uncertain about whose responsibility it is to send the `master_image` to the `image` after successful validation upon creation? An after_validation hook in the model or can the image be sent from one uploader to another?

/Erik

Janko Marohnić

unread,
Jan 7, 2016, 5:37:11 AM1/7/16
to erik.da...@gmail.com, ruby-shrine
That's an interesting problem, thank you for a very clear explanation. I think your idea of having two attachments is the right approach, because it seems they should indeed be treated separately. As I understood, the user first uploads the "master image", and afterwards they can edit? I'm assuming that the js-based image editor sends some kind of information to the server about which edits have been applied? I think I would do something like this:

class MasterUploader < Shrine
  # ...
end

class AnnotatedUploader < Shrine
  def process(io, context)
    context[:record].annotations #=> annotations data
    # processing logic
  end
end

class Photo < ActiveRecord::Base
  include MasterUploader[:master_image]
  include AnnotatedUploader[:image]

  attr_accessor :annotations
end

It might also be useful to make AnnotatedUploader a subclass of MasterUploader, depending on your situation. Then, after you've uploaded the master image, and you're handling the request for the annotated image, you could do:

# photo.annotations are assigned
photo.image = photo.master_image

This works because "photo.master_image" returns a Shrine::UploadedFile, which conforms to the IO interface needed for uploading.

Let me know if this helped :)
Janko


-- 
You received this message because you are subscribed to the Google Groups "ruby-shrine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine...@googlegroups.com.
To post to this group, send email to ruby-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-shrine/21e8e3ad-e6bc-4bed-afde-61479e82bb12%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

erik.da...@nyarp.se

unread,
Jan 8, 2016, 7:27:25 AM1/8/16
to ruby-shrine, erik.da...@gmail.com

Hi!

Many thanks for helping me Janko,

yes, that's correct. The user first uploads the master_image and afterwards he/she may or may not chose to annotate that image. The master_image
is only used as source in the js-based image editor. Everywhere else in the application I use the image attachment and the image_url helper to display the image, annotated or not.

So it's very important that there is an image-attachment from the very beginning, not after the user has annotated for the first time.

A primitive solution is this: 

  before_create(
   
-> { self.image = master_image }
 
)

But what if I would like to do some processing and validations in the MasterUploader first, and then send the processed file to the AnnotatedUploader. Would that be possible?

An alternative would be to do as you suggested and make the AnnotatedUploader a subclass of MasterUploader. But what if I would like to store all master images in PNG format? That would cause the format conversion to occur twice, one time in each uploader. But maybe that's ok.


The client side js-based image editor is using Fabric.js behind the scenes. So when the user saves the annotated image I export a PNG blob and Json meta data of the annotation from Fabric.js and attach it to a FormData object, before sending the request to the Rails backend.

/Erik

Janko Marohnić

unread,
Jan 8, 2016, 10:27:44 PM1/8/16
to ruby-shrine, erik.da...@nyarp.se
Maybe in your case it won't make much sense to use uploader inheritance, just wanted to mention it so that you know it's possible.

If you want to apply validations and processing to the master image before, I think the following should work:

before_save do
 
if master_image_data_changed? && master_image && master_image.storage_key == "store"
   
self.image = master_image
 
end
end

Here basically you check if master image has been "promoted" (moved from cache to store), and then you assign that processed image to the annotated uploader. This should also work well with backgrounding, if you later decide to add it.

Cheers,
Janko

erik.da...@gmail.com

unread,
Jan 11, 2016, 4:30:23 PM1/11/16
to ruby-shrine, erik.da...@nyarp.se
Works perfectly Janko, thanks a lot. I'm really happy with how this turned out.

/Erik

Erik Dahlstrand

unread,
Apr 15, 2016, 7:32:26 AM4/15/16
to Shrine

Hi Janko,

After upgrading to v1.4.0 the following code doesn't work any more.

before_save :set_image_from_master


 
def set_image_from_master
   
if master_image_data_changed? &&

       master_image
&&
       master_image
.storage_key == 'store'
     
self.image = master_image
   
end
 
end


It seems like the storage_key is still 'cache'. Has it been any changes to when the file is persisted?

/Erik



Den lördag 9 januari 2016 kl. 04:27:44 UTC+1 skrev Janko Marohnić:

Janko Marohnić

unread,
Apr 15, 2016, 7:39:00 AM4/15/16
to Erik Dahlstrand, Shrine
Hmm, are you migrating from 1.3.0, and does only 1.4.0 break this (just so that I can narrow my search)? 

Looking from the history of the conversation, I'm not sure I know the steps to reproduce it. It would be really helpful if you could help reproduce the issue, making a self-contained script that I can run (or anything close to it), there is a template that you can use in CONTRIBUTING.md.

You received this message because you are subscribed to the Google Groups "Shrine" group.

To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine...@googlegroups.com.
To post to this group, send email to ruby-...@googlegroups.com.

Janko Marohnić

unread,
Apr 15, 2016, 7:44:57 AM4/15/16
to Erik Dahlstrand, Shrine
Ah, dang, I know what's the problem. I changed the way the file is promoted, now it doesn't trigger callbacks anymore. I didn't have tests which indicate that we're relying on this behaviour. I will see how to fix it, and make a regression release.

Erik Dahlstrand

unread,
Apr 15, 2016, 7:46:33 AM4/15/16
to Shrine

yep, migrating from 1.3.0, it works if I downgrade.

I'll have a look in CONTRIBUTING.md and try to put something together.

/Erik

Janko Marohnić

unread,
Apr 15, 2016, 7:50:19 AM4/15/16
to Erik Dahlstrand, Shrine
No need, I know where is the problem :). FYI it's here, where I'm updating via an SQL query instead of through the record instance. There was a tiny race condition reason for this, but it's an extremely minor chance, and people should implement some kind of generic optimistic/pessimistic locking anyway if that's an issue.

Thanks,
Janko


Janko Marohnić

unread,
Apr 18, 2016, 6:56:55 AM4/18/16
to Shrine
Ok, I've released Shrine 1.4.1 with the fix. Sorry it took me a while, I was investigating how to eliminate that tiny chance of a race condition (which is pretty harmless anyway), I should've just reverted the change to how it was in 1.3.0, and then investigate this for the next release. Anyway, it's fixed now.

Regards,
Janko
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine+unsubscribe@googlegroups.com.

Erik Dahlstrand

unread,
Apr 20, 2016, 2:41:11 AM4/20/16
to Shrine

No problem Janko. Thank you!

/Erik
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-shrine...@googlegroups.com.

To post to this group, send email to ruby-...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages