preloader
Loading...
New webinar: "The Remote Job Search: My Microverse Journey" with graduate Paul Rail
Watch Now

We have launched an English school for software developers. Practice speaking and lose your fear.

Topics

Image upload in Rails just got easier to implement with Shrine which is far more flexible than Active Storage. If you’re familiar with it, you will know it is a modular file upload system for Ruby applications but it also works well with Rails. Shrine is not just for image uploading but is a toolkit for handling file attachments in general. For more details on this see Shrine’s official documentation. 

In this tutorial, we are going to be setting up Shrine to upload the image to our Rails application in seven simple steps. The current version at the time of this article is 3.3.0, but you’ll want to check the documentation page in the event there’s an update.

Below, I've outlined the 7 simple steps to Rails image upload using Shrine. I have also created this video tutorial walking you through these steps.

Step 1: Rails Setup and Gem Installation

Let’s first create a new Rails app and add the required gem for our simple app using `rails new photo_app -T`. The `-T` flag is there to skip the default unit test.

Add the shrine and image_processing gem to your Gemfile:

{% code-block language="js" %}
gem 'shrine', '~> 3.3'
gem 'image_processing', '~> 1.12', '>= 1.12.1'
### Run bundle install
{% code-block-end %}

Image processing gem will come in handy when creating various sizes of images, so if you will need to do cropping or other image manipulations for generating avatar thumbnail sizes, you will need it.

Also, you should note image_processing gem is also required for image type and size validations.

Step 2: Add Configs for Shrine Initializers

Next, create shrine.rb under initializer, so you should have `config/initializers/shrine.rb`. Now let's add the config files below. I will briefly explain this but you can find more information in the getting started doc of the Shrine documentation.

{% code-block language="js" %}
require "shrine"
require "shrine/storage/file_system"
require "shrine/storage/memory"
if  Rails.env.test?
  Shrine.storages = {
    cache: Shrine::Storage::Memory.new,
    store: Shrine::Storage::Memory.new,
  }
else
 Shrine.storages = {
   cache: Shrine::Storage::FileSystem.new("public", prefix:     "uploads/cache"), # temporary
   store: Shrine::Storage::FileSystem.new("public", prefix: "uploads"),       # permanent
 }
end
Shrine.plugin :activerecord    # loads Active Record integration
Shrine.plugin :cached_attachment_data # enables retaining cached file across form redisplays
Shrine.plugin :restore_cached_data  # extracts metadata for assigned cached files
Shrine.plugin :validation_helpers
Shrine.plugin :validation
{% code-block-end %}

That’s it! Now, we will further examine direct uploads to Amazon s3 and Cloudinary storage in a later article.

Taking a good look at the config file above, you will notice the options Shrine provides for storage, cache, and store. The cached is used for temporary uploads (usually 30 days) that go to cache mostly when validations on your form fail. This allows Rails to render it from the cached, so the user doesn’t need to submit that file a second time. That is essentially how the cached_attachment_data and restore_cached_data plugin works.

The permanent storage is the store directory. This is usually a separate directory that is designated differently from cached to know which is permanent, and which can be deleted after a default of 30 days.

Step 3: Generate Article Scaffold

Rails scaffolding is a quick way to generate some of the major pieces of an application. If you want to create the models, views, and controllers for a new resource in a single operation, scaffolding is the tool for the job, see Ruby Guides on Scaffolding for more details.

Now, let’s generate our Article Scaffold with the following attributes: title, body, and image using `rails generate scaffold Article title body:text image_data:text`

Nicely done! Now let's set up our database and run migrations.

{% code-block language="js" %}
rails db:create && rails db:migrate
{% code-block-end %}

Step 4: Create Image Uploader Class

Shrine allows us to validate assigned files using the validation plugin. We would create our image uploader class like this inside our app folder `app/uploaders/image_uploader.rb`

{% code-block language="js" %}
class ImageUploader < Shrine
end
{% code-block-end %}

Then, we add our image validations here, so we know we’re done with the imageUploader class. 

Although it isn’t that clear from the file validation section of the Shrine documentation, the `Shrine.plugin :validation` should go to the initializer we created in step 2, while the `Attacher.validate` goes to the imageUploader class. 

So, let’s update our class to look like:

{% code-block language="js" %}
class ImageUploader < Shrine
 Attacher.validate do
   validate_mime_type %w[image/jpeg image/png image/webp]
   validate_max_size  1*1024*1024
 end
end
{% code-block-end %}

Here, we are making sure our image types are jpeg, png and webp and that their sizes do not exceed 1Mb. See the documentation for any other options you may wish to include.

Step 5: Associating Your Model With Shrine Image Attribute

Next, we will add an ‘image’ virtual attribute to our model, in this case Article. Remember that while generating our scaffold in step 3 we had the `image_data:text` as one of the columns? That is exactly what we are going to use here. 

Inside your `app/models/article.rb` add the include ImageUploader class with image attachment, as well as validation to our title. We will use this to test out the caching features of Shrine in a later step.

{% code-block language="js" %}
class Article < ApplicationRecord
 include ImageUploader::Attachment(:image)
 validates :title, presence: true
end
{% code-block-end %}

Now,  a few tweaks on our article controller and view to properly reference the Attachment(:image) we’ve just created here.

Inside our `app/controllers/articles_controller.rb` update the `:image_data` to `:image`

{% code-block language="js" %}
def article_params
 params.require(:article).permit(:title, :body, :image)
end
{% code-block-end %}

And inside our `app/views/articles/_form.html.erb` replace 

{% code-block language="js" %}
<div class="field">
 <%= form.label :image_data %>
 <%= form.text_area :image_data %>
</div>
## With
<div class="field">
 <%= form.label :image %>
 <%= form.file_field :image %>
</div>
{% code-block-end %}

In order for your image to show up, we need to modify the show page of our article at `app/views/articles/show.html.erb`. Locate where we have the image data then replace

{% code-block language="js" %}
<p>
<strong>Image data:</strong>
<%= @article.image_data %>
</p>
## With
<p>
<strong>Image data:</strong>
<%= image_tag @article.image_url if @article.image %>
</p>
{% code-block-end %}

Bravo! We are almost done.

Step 6: Testing Out Our Image Uploader

This is the step I love the most. It’s the moment of truth  where errors could send you back to step 2, 3, or even 1 to start checking where you missed something. 

So, fire up your server, `rails s` and navigate to http://localhost:3000/articles/new. Go ahead and create your first article and upload an image. You should see something like this in the image_data column: 

`Image data: {“id”:”783805e99c812b461b95343470c04636.png”,”storage”:”store”,”metadata”:{“filename”:”logo-1.png”,”size”:70389,”mime_type”:”image/png”}}`

That means you’re on the right track. Before we proceed to the last step, let's modify the article's show page to display our actual image.

{% code-block language="js" %}
# Replace
<%= @article.image_data %>
# With
<%= image_tag @article.image_url if @article.image %>
{% code-block-end %}

You could also try to upload an image greater than our set max-size to make sure the validations are working.

Step 7: Implementing Shrine Caching Feature

There are often cases when you upload a photo but validation fails. In our example, something like forgetting to fill in the title means our image field will lose reference to the attached image. Shrine provides a wonderful feature that takes care of these problems. It allows you to persist image data on your form, which is terrific. 

So, let’s update our article's image file field on our form to look like this:

{% code-block language="js" %}
<div class="field">
 <%= form.label :image %>
 <%= image_tag form.object.image_url if form.object.cached_image_data %>
 <%= form.hidden_field :image, value: form.object.cached_image_data %>
 <%= form.file_field :image %>
</div>
{% code-block-end %}

Now, let’s intentionally fail to fill in the title, then attach our image and try to submit it. 

Submit Image Again

Voila! You should see the above. Even though the ‘Title can’t be blank’ error stops the form from submitting, we have our cached image, so we just need to fill in the missing title and submit instead of re-attaching the image again. Great, isn’t it?

Conclusion

Handling image uploads has never been easy, but using Shrine for file attachment comes with flexibility and can save you a lot of time. The background processing is simply amazing. After going through this tutorial, your confidence level in handling image uploads in your Rails application should be much higher.  In my next article, we will take a look at how we can use Shrine for direct uploads to cloud storage services like Amazon s3 and Cloudinary. You can access the full code for that here.

We have launched an English school for software developers. Practice speaking and lose your fear.

Subscribe to our Newsletter

Get Our Insights in Your Inbox

Career advice, the latest coding trends and languages, and insights on how to land a remote job in tech, straight to your inbox.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
We use own and third party cookies to; provide essential functionality, analyze website usages, personalize content, improve website security, support third-party integrations and/or for marketing and advertising purposes.

By using our website, you consent to the use of these cookies as described above. You can get more information, or learn how to change the settings, in our Cookies Policy. However, please note that disabling certain cookies may impact the functionality and user experience of our website.

You can accept all cookies by clicking the "Accept" button or configure them or refuse their use by clicking HERE.