I recently had a requirement to build an “avatar for users” feature. The application is a Rails 3 app running on Heroku. Easy enough. I chose to use the Paperclip gem provided by ThoughtBot. Paperclip’s default is to save uploaded files to the filesystem. The catch, Heroku doesn’t let you write to the filesystem, and I couldn’t use Gravatar. Well, there are a couple of ways to work around this Heroku limitation. One is to save the file to the database as a blob. There are pros and cons to this approach. The other approach, and the one I chose, was to use Amazon’s AWS-S3 service.
Setting up Paperclip
Setting up paperclip is pretty easy. There are various ways to do it, but I’m going to discuss how I did it.
First I added the paperclip and aws-s3 gems to my project’s Gemfile.
Then run bundle to install the gems.
Paperclip stores metadata about the uploaded files into the database, and therefore needs fields added to a table to do so. I added those fields right along side the fields in the user table. I didn’t have the need to track past history of upload avatar images. So this was an acceptable approach. Installing the paperclip gem gave us a generator to help us create the database fields we need. Run the following command in a terminal window.
$ rails g paperclip user avatar
Lets quickly break this command down.
- rails g is a short cut for rails generate. You should know this.
- paperclip is the name of the generator we want to run
- user is the table we are going to add the fields to. For me this table already existed.
- avatar is the prefix name we want to use for the database fields
This will create a migration file that adds the necessary fields to the user table, or what ever table name you supplied the generator.
Now you can run this migration.
$ rake db:migrate
The last thing needed to set up the paperclip gem is to add a little bit of code to the model that is going to be responsible for the attachment. In my case, this is the user model. The little bit of code to add is the has_attachment_file method. As you can see from the gist below the code is pretty simple, and maybe even a little self explanatory. In traditional Ruby fashion, this gives you a bunch of extra functionality that you can take advantage of in your views. Not going to cover that here though.
Take note of a few important items in the code.
- storage tells paperclip where to save the uploaded file. Default is the file system. Here I’ve specified it as s3.
- bucket is the name of the location in the AWS-S3 service that the file will be saved to. Think of it as a folder in the cloud.
- s3_credentials are used to authenticate to the AWS services. There are a couple of ways to set the credentials. I used a hash and set the access key and secret key individually for reasons that we’ll cover in a bit. You can also use a Yaml file here as well. See paperclip documentation for that.
And don’t forget to whitelist the avatar property for mass assignment by adding it to the attr_accessible list. See the gist above.
Create an AWS Account
You are going to obviously need an AWS account. I’m not going to go through all the steps to create an AWS account, but if you made it this far you should be pretty committed to going through the process. It’s a pretty cheap service to use, and you only pay for what you use. New users get the first year of S3 use for free. To date, I’ve spent $0.14 for it. Not a type-o, yes 14 cents. Here is the link to the AWS-S3 service. When you sign up for an AWS account you will be assigned an access key and a secret access key. I try and keep both private. The more important one is the secret access key. You can change this if needed, or even add more keys if that is a requirement. You can find your keys by going into the Security Credentials screen.
Once you find them, keep them handy, you’re going to need them in a bit.
Shell scripts – Local and Heroku
This is where things got a little different for me. Since this is an open source project on GitHub, I didn’t want to have any reference to my AWS keys any where in my source code. Hence the reason for using ENV variables that we saw earlier.
Local Development
In Rails applications you can set ENV variables at start up and have them exist for the duration of the application’s life. As an example.
$ S3_KEY=123456789ABCDEF S3_SECRET=x+yabld235lll654 rails s
Now two environment variables exist with the names S3_KEY and S3_SECRET. If you review the user model gist above you’ll see how you access these two values. You probably see right away typing this at the command line is error prone and very tedious every time you want to start the server. Sure there’s the bash history. But, I chose to make a simple shell script to start the server and set these variables.
Important: First add the *.sh file extension to your .gitignore file. This will ensure you don’t accidently check your AWS keys in and upload them to GitHub.
Next in the root of your application add the following command line, replacing the appropriate values with your AWS keys, to a file with the .sh file extension. I used run.sh.
S3_KEY=<your access key> S3_SECRET=<your secret access key> rails s
Save and then set this file as executable.
$ chmod +x run.sh
Test it. If all works, your application will start up as usual. I like this cause it doesn’t really interrupt my workflow.
$ ./run.sh
Heroku Production
The process is very similar as it was for the local development section above. For brevity I’m going to list the steps out instead of going into detail.
Create a .sh file in your application root. I used deploy_heroku.sh.
Add the following lines to that file.
git push heroku master
heroku rake db:migrate
heroku config:add S3_KEY=<your access key>
heroku config:add S3_SECRET=<your secret access key>
Make the file executable
$ chmod +x deploy_heroku.sh
Test it.
$ ./deploy_heroku.sh && heroku open
I loaded this script up with a few more things. I got tired of typing each step out every time I wanted to deploy to Heroku. You are free to change these steps any way you want. The important two pieces are where the config settings are getting set.
Conclusion
I hope you found this helpful and that it all makes sense. Please leave a comment below if you like this, have a suggestion to improvement, or if you have any questions.
References
http://devcenter.heroku.com/articles/s3
http://www.labnol.org/internet/tools/amazon-s3-buckets-tutorial/3890/
http://www.railstoolkit.com/posts/fancyupload-amazon-s3-uploader-with-paperclip