Upload_column is a plugin for the Ruby on Rails framework that enables easy uploading of files, especially images.
Suppose you have a list of users, and you would like to associate a picture to each of them. You could upload the image to a database, or you could use upload_column for simple storage to the file system.
Let’s create our database first, generate a migration (if you’re not using migrations yet you are missing out!) and add
create_table "users" do |t| t.column "name", :string t.column "picture", :string end
Now generate a scaffold for your user class
Create your model class and add the upload_column method call:
class User < ActiveRecord::Base upload_column :picture end
Have a look at UploadColumn::ClassMethods.upload_column to find out more about specialized options. Note that the picture column in the database will not store the actual picture, it will only store the filename.
Now just open up your _form.rhtml partial, and edit it so it looks like the following:
<p><label for="user_picture">Picture</label><br/> <%= upload_column_field 'user', 'picture' %></p>
Here we’re making a call to UploadColumnHelper.upload_column_field, this will create an input field of the file type.
We’ll need to set the form to multipart, so that the picture will actually be sent as well. You can use the upload_form_tag helper:
<%= upload_form_tag( :action => 'create' ) %>
And that’s it! Your uploads are up and running (hopefully) and you should now be able to add pictures to your users. The madness doesn’t stop there of course!
You won’t always want to store the pictures in the directory that upload_column selects for you, but that’s not a problem, because changing that directory is trivial. You can pass a :store_dir
key to the upload_column declaration, this will override the default mechanism and always use that directory as the basis.
upload_column :picture, :store_dir => "pictures"
might be sensible in our case. Note that this way, all files will be stored in the same directory.
If you need more refined control over the storage path (maybe you need to store it by the id of an association?) then you can use a callback method. In our case the method would be called picture_store_dir
. Just append _store_dir
to your upload_column field.
def picture_store_dir "images/#{self.category.name}/#{self.id}" end
A shorter way to do something like this is to pass a Proc to :store_dir, like so:
upload_column :picture, :store_dir => proc{|inst, attr| "images/#{inst.category.name}/#{inst.id}"}
The proc will be passed two parameters, the first is the current instance of your model class, the second is the name of the attribute that is being uploaded to (in our case attr would be :picture).
You can change the :tmp_dir in the same way. For reference: the default for :store_dir is the following proc:
proc{|inst, attr| File.join(Inflector.underscore(inst.class).to_s, attr.to_s, inst.id.to_s) }
Note how it uses File.join, if you plan to use your code on Windows systems you should use File.join, since Windows uses backslashes instead of forwardslashes. File.join takes care of that automatically.
By default, UploadColumn will keep the name of the original file, however this might be inconvenient in some cases. You can pass a :filename directive to your upload_column declaration:
upload_column :picture, :filename => "donkey.png"
In which case all files will be named donkey.png
. This is not desirable if the file in question is a jpeg file of course. Usually it is more sensible to pass a Proc to :filename.
upload_column :picture, :filename => proc{|inst, orig, ext| "avatar#{inst.id}.#{ext}"}
The Proc will be passed three parameters, the current instance, the basename of the original file (without the extension) and the properly corrected extension.
You can also use the picture_filename
callback, which must take two arguments, the original basename and the corrected extension.
Say you would want (for whatever reason) to have a funky solarize effect on your users’ images. Manipulating images with upload_column can be done either at runtime or after the image is saved, let’s look at some possibilities:
class User < ActiveRecord::Base upload_column :picture def picture_after_assign picture.process! do |img| img.solarize end end end
Or maybe we want different versions of our image, then we could simply specify:
class User < ActiveRecord::Base upload_column :picture, :versions => [ :solarized, :sepiatoned ] def picture_after_assign picture.solarized.process! do |img| img.solarize end picture.sepiatoned.process! do |img| img.sepiatone end end end
If you only want to upload images, then UploadColumn comes with a convenient method, image_column
works basically the same way as upload_column
but it is especially trimmed for work with images.
The mime_extensions and extensions parameters are restricted to those used for images, so validates_integrity_of
can be used to easily restrict uploads to images only.
Most importantly if you use image_column you can resize the images automagickaly (sorry) when they are assigned, just pass a Hash, like in the following example:
class User < ActiveRecord::Base image_column :picture, :versions => { :thumb => "100x100", :large => "200x300" } end
If you need the image to be cropped to the exact dimensions, you can pass :crop => true
.
You can manipulate images at runtime (it’s a huge performance hit though!). In your controller add an action and use UploadColumnRenderHelper.render_image.
def sepiatone @user = User.find(parms[:id]) render_image @user.picture do |img| img.sepiatone end end
And that’s it!
In your view, you can use UploadColumnHelper.image to easily create an image tag for your action:
<%= image :action => "sepiatone", :id => 5 %>
If your uploaded file is an image you would most likely want to display it in your view, if it’s another kind of file you’ll want to link to it. Both of these are easy using UploadColumn::BaseUploadedFile.url.
<%= link_to "Guitar Tablature", @song.tab.url %> <%= image_tag @user.picture.url %>
UploadColumn allows you to add ‘magic’ columns to your model, which will be automatically filled with the appropriate data. Just add the column, for example via migrations:
add_column :users, :picture_mime_type
And if our model looks like this:
class User < ActiveRecord::Base upload_column :picture end
The column picture_mime_type
will now automatically be filled with the file’s mime-type (or at least with UploadColumn’s best guess ;).
The following are available for upload_column fields:
_mime_type
-
Will contain the file’s mime_type
_filesize
-
Will contain the file’s size in bytes
If you use image_column you can even add:
_width
-
Will contain the image’s width
_height
-
Will contain the image’s height
You can also do picture_exif_date_time
or picture_exif_model
, etc. if picture is an image_column and the uploaded image i a JPEG file. This requires EXIFR, which you can get by installing the gem via gem install exifr
.
You can use SOME of Rails validations with UploadColumn
validates_presence_of and validates_size_of have been verified to work.
validates_size_of :image, :maximum => 200000, :message => "is too big, must be smaller than 200kB!"
Remember to change the error message, the default one sounds a bit stupid with UploadColumn.
validates_uniqueness_of does NOT work, this is because validates_uniqueness_of will send(:your_upload_column) instead of asking for the instance variable, thus it will get an UploadedFile object, which it can’t really compare to other values in the database, this is rather difficult to work around without messing with Rails internals (if you manage, please let me know!). Meanwhile you could do
validates_each :your_upload_column do |record, attr, value| record.errors.add attr, 'already exists!' if YourModel.find( :first, :conditions => ["#{attr.to_s} = ?", value ] ) end
It’s not elegant I know, but it should work.