I just updated a gem I wrote called View Mapper that will generate all of the code I describe below… you can use View Mapper to generate working scaffolding code that uploads/downloads files using Paperclip, or only view scaffolding code that works with an existing model in your app; for more details see: https://patshaughnessy.net/2009/10/16/paperclip-scaffolding
I love scaffolding. Many experienced Rails developers scoff at the idea of using scaffolding to generate Rails code: it’s ugly; it probably means you don’t understand how to write the code yourself; it generates a lot more code than you need, etc., etc. However, for a beginning Rails developer working on her/his own like me who isn’t surrounded by a team of Ruby experts, scaffolding is an essential tool and can help to get started in the right direction. Also, even for experienced Rubyists scaffolding can be a great way to quickly (minutes, not hours or days) get a simple app up and running to use for demos, UI wireframes, spiking some technical issue, etc.
This post will demonstrate how to use scaffolding to create a new Rails app from scratch that uses the Paperclip plugin to upload and display an image file. Feel free to copy/paste pieces of code from the narrative below and use them in your app, or you can just skip to the chase and get the finished version from github and run that on your machine.
There are a lot of other good tutorials out there about this; see:
I’ll take on the risk of repeating material that’s already out there in order to show how easy it is to get a working Paperclip application up and running using scaffolding. The fact that just a few commands and lines of code are required illustrates just how simple and powerful Paperclip’s design is. In my next post, I’ll proceed to change this sample app to demonstrate how to save the uploaded files in a database column instead of on the web server’s file system, using my modified version of Paperclip.
FYI At the time I wrote this, Rails was at version 2.3.2:
$ rails --version Rails 2.3.2
Let’s get started by creating a new Rails application:
$ rails paperclip-sample-app create create app/controllers create app/helpers create app/models create app/views/layouts create config/environments create config/initializers create config/locales create db create doc create lib create lib/tasks create log etc...
Before we go any farther, let’s setup our database.yml file and create a new MySQL database to use with the sample app. Replace the contents of config/database.yml with this:
development: adapter: mysql database: paperclip_sample_app_development username: root password: host: localhost
Enter the proper username and password for MySQL if they are not “root” and null. And then run this from the command line:
$ cd paperclip-sample-app $ rake db:create (in /Users/pat/rails-apps/paperclip-sample-app)
Ok, now we have a MySQL database to work with. Next, let’s go ahead and install the Paperclip plugin. The best thing to do is just to get the latest version from github; Thoughtbot frequently updates it with bug fixes, enhancements, etc.:
$ ./script/plugin install git://github.com/thoughtbot/paperclip.git Initialized empty Git repository in /Users/pat/rails-apps/paperclip-sample-app/vendor/plugins/paperclip/.git/ remote: Counting objects: 62, done. remote: Compressing objects: 100% (50/50), done. remote: Total 62 (delta 6), reused 39 (delta 4) Unpacking objects: 100% (62/62), done. From git://github.com/thoughtbot/paperclip * branch HEAD -> FETCH_HEAD
Now that we have an empty, shell application created and the Paperclip plugin installed, we can use scaffolding to add some working code to it. Let’s use the same “user” and “avatar” example Thoughtbot does on the Paperclip project page. The idea is that the sample will contain a table of users, and each user will have an avatar image displayed in the web site. So to get started, I’ll just create a new “user” model with string columns for the name and email address:
$ ./script/generate scaffold user name:string email:string exists app/models/ exists app/controllers/ exists app/helpers/ create app/views/users exists app/views/layouts/ exists test/functional/ exists test/unit/ create test/unit/helpers/ exists public/stylesheets/ create app/views/users/index.html.erb create app/views/users/show.html.erb etc...
Now we need to generate the database columns necessary for Paperclip on our new model object using script/generate:
$ ./script/generate paperclip user avatar exists db/migrate create db/migrate/20090430084151_add_attachments_avatar_to_user.rb
And let’s go ahead and create the users table using db:migrate:
$ rake db:migrate (in /Users/pat/rails-apps/paperclip-sample-app) == CreateUsers: migrating ==================================================== -- create_table(:users) -> 0.0031s == CreateUsers: migrated (0.0032s) ============================================= AddAttachmentsAvatarToUser: migrating ===================================== -- add_column(:users, :avatar_file_name, :string) -> 0.0063s -- add_column(:users, :avatar_content_type, :string) -> 0.0069s -- add_column(:users, :avatar_file_size, :integer) -> 0.0085s -- add_column(:users, :avatar_updated_at, :datetime) -> 0.0081s == AddAttachmentsAvatarToUser: migrated (0.0311s) ============================
You can see that the Paperclip generator created columns in the users table called “avatar_file_name,” “avatar_content_type,” “avatar_file_size” and “avatar_updated_at.” Now we have our database schema setup. The next step is to just modify the code that was generated for us by the scaffolding and make the changes necessary for Paperclip. The first thing to do is to add a line to the user model and indicate that it has a file attachment called “avatar.” To do this, open app/models/user.rb and just add this one line:
class User < ActiveRecord::Base has_attached_file :avatar end
And then edit the new user form (app/views/users/new.html.erb) and add a file field to use to upload files. There are actually two code changes you need to make: first you need to set the HTML form to encode the uploaded file data (and other fields) using MIME multiple part syntax, and then second you need to actually add the file upload field. Here’s the finished new.html.erb file with these two changes in bold:
<h1>New user</h1><% form_for(@user, :html => { :multipart => true }) do |f| %> <%= f.error_messages %>
<p> <%= f.label :name %><br /> <%= f.text_field :name %> </p> <p> <%= f.label :email %><br /> <%= f.text_field :email %> </p> <p> <%= f.label :avatar %><br /> <%= f.file_field :avatar %> </p> <p> <%= f.submit 'Create' %> </p> <% end %>
<%= link_to 'Back', users_path %>
Also make the same changes to the edit form that was generated by the scaffolding: app/views/users/edit.html.erb. The best thing to do would be to include the same ERB file (maybe called “_form.html.erb”) in both the new and edit form files. Ideally the scaffolding generator would have done this for us…
Now if we run our application we can upload an image file and attach it to a user:
If you submit this form, the image file will be uploaded to the server and saved on the file system. By default, Paperclip saves files inside a “system” folder it creates in your Rails app’s public folder. Let’s take a look at my public folder and see where the file went:
$ find public/system public/system public/system/avatars public/system/avatars/1 public/system/avatars/1/original public/system/avatars/1/original/mickey-mouse.jpg
This is one of the nice things about Paperclip: it just works. I don’t have to think about or worry about where the files are going to go; Thoughtbot has chosen simple default values that make sense. Here we can see that there are a series of folders created that correspond to the attachment name, model primary key and also the “style” of the attachment (more on that below).
If you want to or need to save the files in some other place on your server’s file system you can specify different options to has_attached_file in your model; see this write up for an example: http://travisonrails.com/2009/01/11/Changing-Paperclip-File-Storage-Location. Paperclip also supports saving the files in Amazon’s S3 storage service, and in my next post I’ll demonstrate how to save the file data inside the database itself, right in the users table in this example.
I’m almost done; now I just need to display the uploaded image somewhere; the simplest thing to do is just to add an image tag to the users show page. Again, my changes to the standard scaffolding code are in bold:
<p> <b>Name:</b> <%=h @user.name %> </p><p> <b>Email:</b> <%=h @user.email %> </p>
<p> <b>Avatar:</b> <%= image_tag @user.avatar.url %> </p>
<%= link_to 'Edit', edit_user_path(@user) %> | <%= link_to 'Back', users_path %>
Now we can see the image for our new user:
Since this image is bigger that what I would like, I can take advantage of Paperclip “styles” feature to generate a smaller version of it. To do that you will need to be sure you have ImageMagick installed on your server, which is what Paperclip uses behind the scenes to modify image files. Then all you need to do is just add two “styles” to your model, like this:
class User < ActiveRecord::Base has_attached_file :avatar, :styles => { :thumb => "75x75>", :small => "150x150>" } end
The strings we pass in are actually options for ImageMagick's "convert" command; see it’s documentation for more details. And now in the show ERB we can just specify the “small” style in the image tag instead:
<%= image_tag @user.avatar.url(:small) %>
To see it, first I need to re-edit and re-upload the image (remember to add the file field code to edit.html.erb just like for new.html.erb):
Now when this form is submitted we will see the smaller image:
$ find public/system public/system public/system/avatars public/system/avatars/1 public/system/avatars/1/original public/system/avatars/1/original/mickey-mouse.jpg public/system/avatars/1/small public/system/avatars/1/small/mickey-mouse.jpg public/system/avatars/1/thumb public/system/avatars/1/thumb/mickey-mouse.jpg
Again, this is very simple and just works! As a last step, let’s add the thumbnail image to the users index page so we can see Mickey without even clicking on that user record. This is as simple as editing app/views/users/index.html.erb and adding a new table column:
<table> <tr> <th>Photo</th> <th>Name</th> <th>Email</th> </tr><% @users.each do |user| %> <tr> <td><%= image_tag user.avatar.url(:thumb) %></td> <td><%=h user.name %></td> <td><%=h user.email %></td> <td><%= link_to 'Show', user %></td> <td><%= link_to 'Edit', edit_user_path(user) %></td> <td><%= link_to 'Destroy', user, :confirm => 'Are you sure?', :method => :delete %></td> </tr> <% end %> </table>
And now we just need to refresh the index page since the thumb image file was already generated:
And there you have it: a working file upload web site written in minutes. This was made possible by Rails scaffolding, and Paperclip's simple, elegant design.