Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: Paperclip & previews?

You know on some sites when you're asked to upload, say, an avatar, you click on the button, select your file, then hit OK, but before you Submit the page (as in, no record is created/updated), a little preview of the image shows up?

How would I accomplish this using Paperclip for Rails?

Bonus points for anyone who can point me towards a tutorial or something that might tell me how to do a Javascript crop on the image before saving the record.

I haven't been able to find much on Google on the subject... thanks for the help!

like image 471
neezer Avatar asked Feb 09 '10 19:02

neezer


People also ask

What is Paperclip in Rails?

Paperclip is an easy file attachment library for Rails Applications. Attached files are saved to the file system, database or cloud and referenced in the browser by an easily understandable specification.


2 Answers

This kind of thing is problematic from a Rails perspective because of the way image uploads work. One strategy to make it work better is:

  • Make a sub-form for your image upload, possibly using swfupload to make it more streamlined.
  • Create a model to receive your uploads that includes some randomized access key used to retrieve and link it. Paperclip handles the attached file.
  • Use AJAX to populate your main form by inserting a hidden field, or potentially a visible check-box element with the appropriate unique_key, when the subform finishes.

A typical model looks something like this:

class Avatar < ActiveRecord::Base
  has_attached_file :image
  # ... Additional Paperclip options here

  before_validation :assign_unique_key

  belongs_to :user

  def to_param
    self.unique_key
  end

protected
  def assign_unique_key
    return if (self.unique_key.present?)

    self.unique_key = Digest::SHA1.hexdigest(ActiveSupport::SecureRandom.random_number(1<<512).to_s)
  end
end

The reason for the unique_key field is so that you can link this in to the form of a potentially unsaved record. It is advantageous to use the unique_key instead of id when putting it in URLs since it is hard to tell if a user should be able to see this picture or not when it is uploaded since the owner user may not yet be assigned.

This also prevents curious people from altering some kind of sequential, easily guessable ID in your URL and seeing other avatars that have been uploaded.

You can retrieve the final resized thumbnail URL for the Avatar as you would any model at this point.

You can easily strip out the parameters on receipt and translate back to Avatar ID numbers:

# If an avatar_id parameter has been assigned...
if (params[:user][:avatar_id])
  # ...resolve this as if it were a unique_key value...
  avatar = Avatar.find_by_unique_key(params[:user][:avatar_id])
  # ...and repopulate the parameters if it has been found.
  params[:user][:avatar_id] = (avatar && avatar.id)
end

# ... params[:user] used as required for create or update

As people upload and re-upload images, you will eventually have a large number of orphaned records that are not actually used anywhere. It is simple to write a rake task to purge all of these after a reasonable amount of time has passed. For example:

task :purge_orphan_avatars => :environment do
  # Clear out any Avatar records that have not been assigned to a particular
  # user within the span of two days.
  Avatar.destroy_all([ 'created_at<? AND user_id IS NULL', 2.days.ago ])
end

Using destroy_all should have the effect of purging all Paperclip material as well.

like image 135
tadman Avatar answered Nov 11 '22 18:11

tadman


I found the solution posted here useful, you just need to modify it to be only one file (if you are doing single file upload):

<%= image_tag @upload.image, id:"something_unique"%>
<div class="row">
  <%= form_for @upload, :html => { :multipart => true } do |f| %>
    <%= f.file_field :image, id:"something_else_unique" %>
    <%= f.submit "Add photo" %>
  <% end %>
</div>

<script>
  function handleFileSelect(evt) {
    var files = evt.target.files; // FileList object
      f=files[0]
      // Only process image files.
      if (f.type.match('image.*')) {
        var reader = new FileReader();
        reader.onload = (function(theFile) {
          return function(e) {
            // alert(e.target.result);
            document.getElementById("something_unique").src=e.target.result;
          };
        })(f);

      // Read in the image file as a data URL.
      reader.readAsDataURL(f);
      }
    }
  document.getElementById('something_else_unique').addEventListener('change', handleFileSelect, false);
</script>

Note: I used a separate model for paperclip, an upload model that has the image attribute. You can add styling to the image preview tag to format the picture size (otherwise it will be the original size).

like image 33
hodale Avatar answered Nov 11 '22 18:11

hodale