Welcome all! As we learned in my previous article, Active Storage is an inbuilt library in Ruby on Rails that simplifies the process of file uploads in our applications.
The fact that it is an inbuilt library means that we do not have to add the active storage gem in our Gemfile to use it, rather we can directly out of the box start to use it.
This is the part B of my Ultimate Guide to Active Storage in Rails series where I aim to teach you all that is required to know about Active Storage. In this part of the series, I aim to walk you through the technical setup of Active Storage and how to use it in our applications. As a bonus, at the end I will show you how you can write your own custom validations to limit the file size of an uploaded file. Part A of the series can be found here.
- Rails V7
- PostgreSQL database
- Ruby V 3+
- Visual studio code
- Some experience with Rails MVC architecture.
If you would like to follow along with me, I created a GitHub repository for this specific tutorial which has all the code that will be listed here. You can find the GitHub repository here and the branch name with the code is "activestorage". If you like, give me a follow!
To get started, we are going to create a new rails project which will use PostgreSQL as our database. (PostgreSQL is optional and you can use whichever RDMS you wish to use.) In your terminal, type and hit enter:
rails new activestorage_tutorial --database=postgresql cd activestorage_tutorial
This command will create a new rails application for us with all the dependencies required to have a fully functional fullstack application in minutes, configured to use PostgreSQL as its database.
Once completed, you need to cd into the new directory and open it with the text editor of your choice. Our next steps will be to update our config for our database to ensure that rails can work with our local PostgreSQL database. Go to config -> database.yml file, edit the development and test blocks and add your username and password. Below is an example:
development: <<: *default database: activestorage_tutorial_development username: nemwel password: root test: <<: *default database: activestorage_tutorial_test username: nemwel password: root
our next steps will involve creating our database with
We want to implement and test the file upload functionality and we will need to have a User who will have the following properties: name, email, profile_photo, phone_number. In your terminal type:
rails generate scaffold User name email phone_number:integer profile_photo
Below are the files generated by the scaffold:
Note: In rails, all the properties for an object(User) are default type string. Therefore not defining the datatype for eg the name will give it a type of string when the columns are generated. Also, we are using string for the profile_photo field because ACtive Storage will generate urls for each uploaded file which will be of type string. I hope this explains why.
We are using the scaffold to generate the users resource for us which includes a user controller, model, migration, views, test files, and an update to our routes with the user's resource.
Let us update our routes to add a root route to the users(index.html.erb) view on application load. add the following line in your routes file(Config -> routes.rb):
You can now run the migrations with the command below and start your server with rails s and go to http://127.0.0.1:3000/ in your browser.
Setup Active storage
Now that we have our application set up, it is time to setup Active Storage. Go to your terminal and type:
Which will generate three tables as we learned in my previous article, and migrate with
rails db:migrate to update our schema.
In our application since a user should only have one profile_photo at any given period, we are going to go to our user model in app -> models -> user.rb file and add the has_one_attached association. Add the following inside our user model:
class User < ApplicationRecord has_one_attached :profile_photo end
Our next step will be to update our views. We want to be able to upload images from our local machines, store them, and retrieve them to display it in our browser.
We shall first update our file upload. Let's go to app -> views -> users -> new.html.erb (If you generated the Users resource with scaffold, the form will not be in this file but can be found inside the _form.html.erb partial). We ideally want to update the input field for the profile photo to a file upload field. Update this:
<%= form.text_field :profile_photo %>
<%= form.file_field :profile_photo %>
that will allow us to upload a file.
Once we can upload files, our next steps should be to ensure that we are now able to display the files that we uploaded. We shall go to app -> views -> users -> index.html.erb (If you generated the users resouce with scaffold, the markup will be found in the _user.html.erb partial instead). We will change the markup for displaying the image to:
<% if user.profile_photo.attached? %> <image src="<%= (url_for(user.profile_photo))%>"> <% end %>
We are checking through the active_storage_attachments table to see if there is a profile_photo for this specific user. If such a file exists, create a custom URL for that file that is passed to the image tag.
With that, we have our complete file upload and retrieval all wired up and working as expected.
Additional: some custom validations
Now that we have learned what active Storage is, how to set it up and make it to work, we have not touched on how to ensure the integrity of the files we are uploading.
Imagine a situation where you have limited storage space and you'd like to only allow images that are eg less than 1MB to be uploaded. Ideally, you'd like to have some sort of restrictions on eg the file sizes of the file we are uploading. This is where validations are king and luckily for us, rails provides a way to write our own custom methods.
In our users model, let's add the following:
class User < ApplicationRecord has_one_attached :profile_photo validate :valid_image def valid_image return unless profile_photo.attached? unless profile_photo.blob.byte_size <= 1.megabyte errors.add(:profile_photo, "The image is more than 1MB") end end end
In a nutshell, we are using the unless a statement to check if the condition is false. If the condition is truly false, we execute the block of code(In our case, the error message and the image will not be uploaded because the validation failed)
With our above validation set and you try to uload an image whose size is more than 1MB, the following error will be shown:
I hope that my short article has given you insights on how you can setup and use active storage in your applications and gave you an introduction on custom validations in Ruby on Rails.
This one marks the end of the second series of The Ultimate Guide to Active Storage in Rails. Here, I walked you through the technical set up of Active storage locally in your application and in the last part of the series, I will show you how to link it with Cloudinary to upload your images to an external cloud storage service provider will be coming soon. Be on the lookout for it!
That is it for today's class. I hope this was useful information for you. See you in my next article.