Carrierwave Uploads to Google Cloud Storage

I always liked the way carrierwave encapsulates all file uploading functionality in an uploader class. This is particularly useful for image uploads as carrierwave is able to preprocess images and create thumbnails automatically. In this post I will go through the steps you have to perform to be able to upload images to Google Cloud Storage using the Carrierwave gem while also processing the uploaded files and generating thumbnails for them.

My previous post uses the gcloud Gem to upload to Google Cloud Storage, which was a nightmare to set up and get working. The only reason I used gcloud was because carrierwave was giving me trouble initially.

Installation

The first step is to install 2 gems and a program called ImageMagick on your machine. ImageMagick is a image editing program with CLI. This CLI has a Ruby interface called minimagick, which in turn is used by carrierwave internally to resize images.

Run sudo apt-get install imagemagick to install ImageMagick.

gem 'carrierwave'

#It is recommended to use fog-google, but I had issues with that.
gem 'fog'

# ImageMagick interface for ruby
gem 'mini_magick'

ImageMagick is a program that is completely seperate to our Rails application. It is not included in our version control system. Carrierwave image resizing will not work on Heroku, unless we configure Heroku properly using a custom buildpack. I will write a post about that once I get to that stage.

Next, open your config/application.rb file and add require 'carrierwave' as shown below.

require File.expand_path('../boot', __FILE__)
require 'rails/all'
require 'carrierwave' #add this

The carrierwave documentation states to add require 'carrierwave/orm/activerecord'. This causes nasty migration errors and it will also cause your web server to throw errors on startup.

Once you added those gems, installed ImageMagick on your computer and added the require statement to application.rb then run the usual bundle install.

Creating the Initializer

You can access your keys here. If you have not set up a Google storage bucket yet, check out this post.

CarrierWave.configure do |config|
config.fog_credentials = {
    :provider                           => 'Google',
    :google_storage_access_key_id       => 'PASTE_HERE',
    :google_storage_secret_access_key   => 'PASTE_HERE'
}
config.fog_directory = 'BUCKET_NAME'
end

###Create a scaffold If you are starting from scratch, create a scaffold like the one below. We will store the image url in the file attribute.

rails g scaffold pictures title:string file:string

If you have an existing model to which you would like to add images, then create a migration instead. Replace CHILDMODEL with the name of your model.

rails g migration AddFileToCHILDMODEL file:string

We will later add support for multiple image uploads. Its important that you do not add your images to your parent model. Rather, add the image to an attachment model. That way we can add a has_many association to our parent model later to enable multiple image uploads associated with that one model. If you do not have a childmodel yet, then create one using the the scaffold command above.

Run rake db:migrate when you are done.

Creating the Uploader

Carrierwave uses uploader classes to encapsulate all uploading configuration and code from the rest of your Rails application. Inside the uploader, we can define different rules that will be obeyed every time a file is uploaded. Create an uploader using the following carrierwave command:

rails g uploader Image

If the command fails, you probably forgot to run bundle install after adding the carrierwave gem to your gemfile.

The command will create a file called image_uploader.rb in your app/uploaders/ directory (a new directory created for uploader classes).


class ImageUploader < CarrierWave::Uploader::Base

    # Include MiniMagick support:
    include CarrierWave::MiniMagick

    # Choose what kind of storage to use for this uploader:
    storage :fog # this is important

    def store_dir
        # this will save images in the directory /uploads/pictures/file/id/ in your storage bucket.
        "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"

    end

    # Process files as they are uploaded.
    # Note that I changed this from :scale to :resize_to_fit.
    # It looks like they wanted us to implement the :scale method ourselves which is not necessary.
    process :resize_to_fit => [1000, 1000]

    # This is where we define the different sized thumbnails/version we want.
    version :thumb do
        process :resize_to_fill => [50, 50]
        # :resize_to_fill will crop the image a little to ensure that it fills the specified dimensions
        # :resize_to_fit will scale the image down to ensure that it fits inside the specified dimensions, while keeping its original aspect ratio.
    end
end

You can either uncomment relevant sections in your generated uploader file or copy and paste the one above. Do take note of all the comments in your generated file because they are really helpful. For example, you may wish to restrict the supported image file types to JPG and PNG. This would also stop users from uploading non-image files. The comments also explain how to rename files before they are stored in the cloud. This is really cool and simple with carrierwave.

Edit your model

Open your model file and add this line to it. This will mount an uploader object which we can use later.

class Picture < ActiveRecord::Base
    mount_uploader :file, ImageUploader
end

Make sure that the :file corresponds to the database column you created to hold the URL of the file uploaded to Google Cloud Storage.

View

To enable users to upload images we have to modify our form partial. Open view/pictures/_form.html.erb and change f.input to f.file_field.

<%= f.file_field :file %>

You can use the following code to retrieve your images and thumbnails. Note that ATTRIBUTE must be replaced with the file attribute.

# General Code
<%= image_tag @picture.ATTRIBUTE_url(:thumb) %>
<%= image_tag @picture.ATTRIBUTE %>

# My code
<%= image_tag @picture.file_url(:thumb) %>
<%= image_tag @picture.file %>

Testing

You can now test the system. Your view should display a URL to the image file on Google’s server, and (after a few seconds), the image and its generated thumbnails! The crazy thing is that carrierwave not only handles the uploading, image resizing and URL management for your images, but it also takes care of updating and deleting the images from your cloud storage bucket when the associated records in Rails are modified or destroyed.

Adding Support for multiple Uploads

Images are now automatically resized and uploaded to your cloud storage bucket. The next step is to introduce the ability for users to upload multiple files. We can add this functionality with Rails associations. We are going to add a new model called Post which contains many pictures.

rails g scaffold posts title:string body:text

Edit the migration file

Before we migrate the database, we have to manually add post_id to the pictures table. We have to do this every time we create rails associations. In your Picture.rb model file add the t.references :post attribute. When you are finished you can run rake db:migrate.

If the migration fails, you can run rm db/development.sqlite3 db/schema.rb. This will delete your development database. Migrate the database again and it should work.

Editing the Picture model

Right below the class declaration add belongs_to :posts.

###Editng the Post model Add the following two lines to your Post model. The code dependent: :destroy will ensure that the destroy method is called for each picture when a post is deleted. We have to do this because carrierwave uses the destroy callbacks to remove uploaded files from the storage bucket. Without it, we would have orphaned picture records in our database as well as image files in the cloud storage bucket.

class Parent < ActiveRecord::Base
    has_many :pictures, dependent: :destroy
    # for the nested form we will create in the next section
    accepts_nested_attributes_for :pictures
end

###Edit the form partial I am using simple_form. In your partial add , :html => {:multipart => true} to the form block declaration. Then add nested fields for :pictures.

<%= simple_form_for(@post, :html => {:multipart => true}) do |f| %>
        <%= f.error_notification %>

        <div class="form-inputs">
            <%= f.input :title %>
        </div>

        # Nested fields for pictures
        <%= f.simple_fields_for :pictures do |p| %>
                # change file to the name of the attribute that stores the file URL in your picture table.
                <%= p.file_field :file, :multiple => true, name: "pictures[file][]" %>
        <% end %>
        <div class="form-actions">
            <%= f.button :submit %>
        </div>
<% end %>

###Customizing the Posts Controller Alright, this is by far the trickiest but also the coolest part. Inside your new-action add a line to build the pictures association for new posts.

  def new
    @post = Post.new
    @picture = @post.pictures.build # add this line
  end

In your create-action add the following modifications inside the if @post.save-block. As always, be careful to replace the attribute names with the ones you used, if you changed them.

def create
    @post = Post.new(post_params)

    respond_to do |format|
      if @post.save
        # add this block here
        params[:pictures]['file'].each do |a|
          @picture = @post.pictures.create!(:file => a, :post_id => @post.id)
        end
        # end of code to be added
        format.html { redirect_to @post, notice: 'Parent was successfully created.' }
        format.json { render :show, status: :created, location: @post }
      else
        format.html { render :new }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

Lastly, add this line to your show-action, which should be an empty action block before adding the line.

 def show
    @pictures = @post.pictures.all
  end

###Edit the view The last step is to edit your view to allow users to view the pictures they uploaded. I am just going to display all pictures and all their versions to demonstrate that everything works correctly. You can access your pictures using the @pictures variable you created in your show-action a few seconds ago.

<% @pictures.each do |pic| %>
        <%= image_tag pic.file_url(:thumb) %>
        <%= image_tag pic.file %>
<% end %>

And now we are finished! Restart your web server just in case and you should be able to add multiple images in your new Post form. It will take a moment to upload the images. All images should be displayed in your view as well.

Conclusion

I hope you got everything working in your application. We accomplished a lot in this post. First we added carrierwave which comes with image upload functionality as well as image processing features. Once we got that to work we added the Post model which allowed us to upload multiple images for each post. When posts are deleted so will their associated images in your storage bucket. HOwever, when posts are edited, the associated pictures will not be updated, unfortunately. You would have to modify your edit-action to do that.

As always, thanks for reading and I hope this helped you add upload functionality to your Rails application.