File uploading is an important aspect of many Ruby on Rails applications. The Paperclip gem was long seen as the go-to solution for this functionality. However, with its recent deprecation, Active Storage is now the go-to source for enabling file uploading within Rails applications. Active Storage is an aspect of Rails that allows users to upload files in various environments and to various cloud storage services. These services include Amazon’s S3 service, Google Cloud Storage, and Microsoft Azure Storage service. It also provides a local-disk based service that can be used in the application development stage. Images are one of the file types that Active Storage can store and are the focus of this quick, step-by-step tutorial.
In order to set up Active Storage within your project, enter the following commands to cd into the project directory, and install the service:
{% code-block language="js" %}
$ cd <your_project_directory_name>
$ rails active_storage:install
{% code-block-end %}
This creates a migration file that will introduce two tables into the application’s database once migrated, called, “active_storage_blobs” and “active_storage_attachments” as seen below.
{% code-block language="js" %}
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
t.bigint "record_id", null: false
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end
{% code-block-end %}
{% code-block language="js" %}
create_table "active_storage_blobs", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
t.bigint "byte_size", null: false
t.string "checksum", null: false
t.datetime "created_at", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
{% code-block-end %}
Once migrated, you can configure the storage services your application uses by editing the storage.yml file in the config folder. By default, Active Storage utilizes the local disk for storage.
{% code-block language="js" %}
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
local:
service: Disk
root: <%= Rails.root.join("storage") %
{% code-block-end %}
You can use different storage services in each environment (test, development and production) by editing the appropriate line in the environment config files to match your desired service. For example, if you configured the Google Cloud storage service to utilize it in production, ensure that the following line exists in your config/environments/production.rb file:
{% code-block language="js" %}
config.active_storage.service = :google
{% code-block-end %}
For an in-depth look at how to configure these cloud services in your rails app, check out this article by Wiktor Plaga.
Once your storage service is properly configured, you can move forward with implementing the necessary logic for uploading an image. No additional model is required.
In the case of an app where the user wants to associate images with their articles, the following configuration can be made to the appropriate model:
{% code-block language="js" %}
class Article < ApplicationRecord
belongs_to :user
has_one_attached :image
end
{% code-block-end %}
This macro “has_one_attached” enables one-to-one mapping between each Article record and the image file. The symbol that follows can be given any name. However, once that name is specified, it will have to be used in all subsequent application logic for image uploading. If you implemented a private params method for model creation, pass the named symbol to the params method as follows:
{% code-block language="js" %}
class ArticlesController < ApplicationController
.
[your other controller actions here]
.
.
private
def article_params
params.require(:article).permit(:title, :content, :image)
end
end
{% code-block-end %}
In order to enable the mapping of multiple images to each record, add the line below instead. It’s important to note that the symbol passed to the macro must be pluralized in this instance.
{% code-block language="js" %}
class Article < ApplicationRecord
belongs_to :user
has_many_attached :images
end
{% code-block-end %}
Once these simple configurations are made, you can go ahead and implement a form that allows users to attach an image file. The same symbol passed to the macro in the model must be passed to file_field form helper:
{% code-block language="js" %}
<%= form_with model: @article, method: "post" do |f| %>
<%= f.text_field :title, placeholder: 'Enter article title here' %>
<%= f.file_field :image %>
<%= f.text_area :content, placeholder: "Enter article content here", size: "70x20" %>
<%= f.submit "Post" %>
<% end %>
{% code-block-end %}
Once the user completes the form and hits send, the image will be uploaded and the credentials stored in the database. Upon uploading the image, you can incorporate it into the flow of the post in different ways. You can conditionally attach the image as follows:
{% code-block language="js" %}
<% if article.image.attached? %>
<img src="<%= (url_for(article.image)) %>">
<% end %>
{% code-block-end %}
You can also apply that image as a background:
{% code-block language="js" %}
<div style="background-image: url(<%= (url_for(@article.image)) %>)">
</div>
{% code-block-end %}
Rails also enables users to seed the database with images if they want. To do so, you can use the ‘attach’ method, passing in a hash with two keys. The ‘io’ key calls the open method on the File class and takes the image’s relative path as an argument.
{% code-block language="js" %}
user = User.create(
first_name: Faker::Name.first_name,
last_name: Faker::Name.last_name,
email: Faker::Internet.safe_email,
password: '123456789'
)
article = Article.new(
title: Faker::Lorem.sentence(word_count: 5),
content: Faker::Lorem.paragraphs(number: 4),
)
article.user = user
article.image.attach(
io: File.open('app/assets/images/news.jpg'),
filename: 'nw.jpg'
)
article.save!
{% code-block-end %}
Rails also provides some basic methods that can be used on the console to interact with the image records. The ‘attached?’ method can be used to determine if a particular object has an associated attachment. To call this method, run the following commands to open the console and query the database. The ‘attached’ method returns a Boolean response.
{% code-block language="js" %}
$ rails console
$ article.image.attached?
# False
{% code-block-end %}
Rails also provides two methods for deleting attached images from the database. The ‘purge’ and ‘purge_later’ methods are used for this purpose. They are synchronous and asynchronous respectively and can both be called in the rails console. Please note that associated images are automatically deleted from storage if the record it was associated with has been deleted. eg. deleting an article, also deletes its attached images. There is no need to add ‘dependent: :destroy’ to the model to allow this behavior.
And there you have it. I hope this is helpful, in showing how to add a feature in your Rails app, so your users can upload images.
Happy coding!
To learn more about Microverse, and joining our supportive community of remote software developers, get started below!
Career advice, the latest coding trends and languages, and insights on how to land a remote job in tech, straight to your inbox.