7 Practical Tips for ActiveStorage on Rails 5.2
What is ActiveStorage?
Handle (image) file upload in Rails like carrier_wave, paperclip, and dragonfly. Only difference is that it's extracted from Basecamp production code and made into the rails ecosystem.
So not only is the framework already used in production, it was born from production.
http://weblog.rubyonrails.org/2018/3/20/Rails-5-2-RC2/
Guess that's a pretty great sales pitch.
Here is an overview from the official guides that covers all the basic usage: from installing to quick get-started code examples. If you haven't checked it, that's gonna be your first stop for sure.
In Bloggie, we've been using ActiveStorage from day one. Here are 7 practical tips to help you kick it off. ️
Understand Blob and Attachment models
ActiveStorage is designed with Blob and Attachment models, so when you add this to any existing Rails applications, you won't need to modify your tables (like adding any image_id
column or so). This is great!
A key difference to how Active Storage works compared to other attachment solutions in Rails is through the use of built-in Blob and Attachment models (backed by Active Record). This means existing application models do not need to be modified with additional columns to associate with files. Active Storage uses polymorphic associations via the Attachment join model, which then connects to the actual Blob.
https://github.com/rails/rails/blob/master/activestorage/README.md#compared-to-other-storage-solutions
Let's see how the tables look like when we upload a user avatar image.
Assume we have a user with id 101.
active_storage_attachments
: the "join" model connects polymorphic records (users
in this case) with blobs.
active_storage_blobs
: contains the metadata about a file and a key.
Now we can visualize the connections.
Say, when you have another model Post
that also associated with images, that'll be saved into the active_storage_attachments
with record_type
as Post
and record_id
as the post id.
Solve N+1 with eager loading
So far so good. But with this type of model design, it also means that you need to be extra careful on the N+1 queries in a has_many situation.
Imagine a typical blog application where a post has many images.
Then in the post page, you simply find and render it.
If the post has many images attached, you may see something like this in your rails server logs:
This is because when rendering the image, internally it'll need to load the image record from the database and then perform a redirection (more on this topic in another post).
Fortunately, ActiveStorage is shipped with one scope:with_attached_images
. Let's add it to the controller.
Now previous N+1 queries are all loaded in one.
Under the hood, with_attached_images
simply is a macro of includes("#{name}_attachment": :blob)
, where name
is whatever you defined with has_many_attached :the_name
in your model.
Upload image by passing the image url
We love omniauth! We use it to get users login via whatever services they like.
During the authentication, say Twitter, it would be nice that we can pass the twitter avatar url that is shipped with omniauth to ActiveStorage, so the user would have the same avatar instead of blank one.
Sadly, ActiveStorage doesn't support this yet (there was an PR but got closed), but can be easily handled with a few lines of code.
I'll leave it to you to handle the filename and content_type, but you get the gist .
One of the great features of ActiveStorage is Analyzer
!
After uploading an image, it'll add the analyze into ActiveJob queue, and when it's executed, it'll return the image width and height for you!
Note that it requires MiniMagick so make sure it's in your Gemfile:
Besides image analyzer, there's also an VideoAnalyzer that would give back the duration, angle, and display aspect ratio (e.g. "4:3") for the uploaded video.
(In Rails 5.2, mini_magick is already added to the Gemfile by default ️)
Get file name and extension
Straightforward
Read more about ActiveStorage filenames.
Attach images in tests
Suppose you're using RSpec with FactoryBot, and in your model spec you want to create your main object with images attached. (apologize for the made-up/not-convincing spec example )
Well, we can "borrow" it from the rails repo! In activestorage/test/test_helper.rb, there is a helper method called create_file_blob
which does exactly what we wanted.
I don't know if there's better way of doing it, but at least you could port it like this:
Then put one tiniest image file you could find and save it as spec/fixtures/file/images.jpg
, that's where the file_fixture
method would look for. (Can also download one from https://github.com/rails/rails/blob/master/activestorage/test/fixtures/files)
That's it! Back to the previous spec:
You can also use the helper method to stub the metadata.
For example, in bloggie we're setting a cover image that'll be used to make the post look good when sharing to Twitter. We want this to be as transparent as possible, so what we'd done is to see if the image width and height are bigger than a threshold. The actual spec looks like this:
Be careful with git clean --force -d
When developing with ActiveStorage on my local machine, I had frequently ran into the situation where all my local images seemed to be lost. I couldn't figure out why, until...
If you follow the default configuration, in development mode, all your uploaded files will be saved under storage
folder.
and your .gitignore will surely have it covered:
If you're in the habit of running git clean -fd
blindly like me, to get a clean state, you may get yourself tricked like I did.
Let's have a dry run first:
That was why it tended to happen when I switched branch... as I usually cleaned off all unstaged files and folders with that command, and in turn it'd remove the entire storage/
folder that contains all the images
So now guess I'd need to use the --exclude
argument:
Putting this into my .zshrc
That's it! Hope these are helpful! If you have other great tips or any suggestions, please leave a comment below! Cheers
(This post is written with Rails 5.2.0.RC2)
Clap to support the author, help others find it, and make your opinion count.