- Read Tutorial
What Carrierwave Provides
Uploading image, files, and various media can be challenging to build into an application from scratch. Carrierwave has been one of the more popular gems for managing uploading and as you'll see it provides a pretty powerful set of tools with a straightforward implementation. With Carrierwave you can:
Upload any type of file
Easily integrate with storage engines such as AWS's S3
Protect against malicious file uploads with a whitelisting capability
A DRY interface so that uploaders can be utilized from various models throughout an application
The ability to integrate outside libraries for features such as image manipulation (we'll do this in the walk through)
Drawbacks
File uploading is a complex feature to build into an application, so no gem that manages this process is going to be perfect. Some issues I've run into Carrierwave are:
It can be complex to integrate common UI elements such as progress bars for file uploads
Uploading video components that need to be converted to different codecs can be challenging
Uploading very large files requires quite a bit more work to prevent server timeouts (this is the case for most file uploading gems)
Implementation / Carrierwave Example
To get started let's create the application:
rails new carrierwave-tutorial -T
After running rake db:create && rake db:migrate
we can start setting up the model that we'll be integrating the uploader with. Run the scaffold:
rails g scaffold Post title:string image:string description:text
After running rake db:migrate
we can start integrating Carrierwave, begin with adding the following Gems into the Gemfile
:
gem 'carrierwave' gem 'mini_magick', '~> 3.5.0' gem 'fog' gem 'figaro' gem 'unf'
Run bundle install
and it will bring in all of the Gem code and we can run the generator that will create an uploader configuration file:
rails generate uploader Photo
This generator created a single file: app/uploaders/photo_uploader.rb
, delete everything in the file and replace it with the contents below:
# app/uploaders/photo_uploader.rb # encoding: utf-8 class PhotoUploader < CarrierWave::Uploader::Base include CarrierWave::MiniMagick include Sprockets::Rails::Helper storage :fog def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end version :thumbnail do process :resize_to_fill => [20, 20] end version :profile_size do process :resize_to_fill => [300, 300] end def extension_white_list %w(jpg jpeg gif png) end end
Going line by line let's see what this file makes available to us:
It includes some helper methods and the
MiniMagick
module which gives us some nice image manipulation methodsIt specifies what storage engine to use, in this case we're going to be using
fog
and connect it to AWS for storing the uploading filesThe
:thumbnail
and:profile_size
are methods that we can call when we want to render the images. This is handy since it means you can do something such as@post.image.thumbnail
and it will automagically render an image that has been resized to20x20
pixels, without having to manipulate the image sizes at the view layerThe
extension_white_list
method lets you declare a list of file extensions that users are allowed to upload. For example, in this list users wouldn't be able to upload a PDF or .zip file
With out uploader configured we can now add the uploader to the model that we want to accept file uploads. I like this implementation since it is flexible enough for us to pick and choose which models should be able to take in a file. Update the post.rb
file like I did below:
# app/models/post.rb class Post < ActiveRecord::Base mount_uploader :image, PhotoUploader end
The mount_uploader
method takes in two arguments:
The attribute in the model that will store the image file URL
The uploader that we want to use. This is a flexible implementation since it means that you could have one model that is configured to work with a photo uploader, and another model that can interface with a video uploader
Now we need to integrate the API keys for the AWS connection and we're going to use the figaro
gem to securely manage those credentials. Running:
figaro install
Will create the config/application.yml
file and also add the file to the .gitignore
file. It's very important that you never push these credentials to your version control system (aka GitHub/Bitbucket) since hackers could take these credentials and use them to do things such as spinning up new servers. I had a developer who worked for me who accidentally pushed up my AWS credentials and within 10 hours a hacker had scraped the keys and ran up over $20,000 in charges (AWS thankfully caught the hack and didn't charge me for it).
Place the following code into the config/application.yml
file (with your details filled in obviously):
AWS_ACCESS_KEY_ID: "Your AWS access key" AWS_SECRET_ACCESS_KEY: "Your AWS secret access key" development: AWS_BUCKET: "Your S3 development bucket name" production: AWS_BUCKET: "Your S3 production bucket name"
Now to create the connection to AWS, create a new initializer for the fog
gem:
# config/initializers/fog.rb CarrierWave.configure do |config| config.fog_credentials = { :provider => 'AWS', :aws_access_key_id => ENV['AWS_ACCESS_KEY_ID'], :aws_secret_access_key => ENV['AWS_SECRET_ACCESS_KEY'] } config.fog_directory = ENV['AWS_BUCKET'] config.fog_public = false end
This initializer file will make the connection to the AWS API and securely pass in the parameters we need for the file uploading to work.
Now that our setup is complete let's update the file uploader in the view:
<!-- app/views/posts/_form.html.erb --> <%= form_for(@post) do |f| %> <% if @post.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2> <ul> <% @post.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <div class="field"> <%= f.label :title %><br> <%= f.text_field :title %> </div> <div class="field"> <%= f.label :image %><br> <%= f.file_field :image %> </div> <div class="field"> <%= f.label :description %><br> <%= f.text_area :description %> </div> <div class="actions"> <%= f.submit %> </div> <% end %>
Let's startup the Rails server and test this out, navigate to localhost:3000/posts/new
and fill out the fields, including uploading a file. If everything is setup properly everything should flow through and the file should be uploaded to S3, the show page after the upload should look something like below:
That URL is the image file page coming from S3 where the image is now stored. To see what this looks like update the view template like below and use the image_tag
view helper:
<!-- app/views/posts/show.html.erb --> <p id="notice"><%= notice %></p> <p> <strong>Title:</strong> <%= @post.title %> </p> <p> <strong>Image:</strong> <%= image_tag(@post.image.profile_size.url) %> </p> <p> <strong>Description:</strong> <%= @post.description %> </p> <%= link_to 'Edit', edit_post_path(@post) %> | <%= link_to 'Back', posts_path %>
Notice how I called the profile_size
method on the image so it automatically sizes it with the same parameters we used in the uploader configuration file.
And that's how easy it is to build a file uploader into an application with Carrierwave.