Since Rails 5.2 we have a standard way to handle file attachments via Active Storage.
Provided you've configured it as explained in the overview, it's as simple as
class Customer < ApplicationRecord
has_one_attached :profile_image
end
If a customer record with a profile image is saved, you'll get roughly this structure of records:
customer = Customer.last
#=> Customer:0x0000000122366cd0 id: 123, ...
customer.profile_image
#=> ActiveStorage::Attached::One:0x000000011d936520
@name="profile_image", ...
customer.profile_image_attachment
#=> ActiveStorage::Attachment:0x0000000120bb7698
id: 456,
name: "profile_image",
record_type: "Customer",
record_id: 123,
blob_id: 789,
created_at: "2025-03-05 14:06:38.275327000 +0200">
customer.profile_image_attachment.blob
#=> ActiveStorage::Blob:0x000000012119afd8
id: 999,
key: "52fbm7514ubgz8kyk8zf3pd41bwj",
filename: "best_pic.png",
content_type: "image/png",
metadata: {"identified"=>true, "analyzed"=>true},
service_name: "local",
byte_size: 2470984,
checksum: "jR7kuklM32nVLn2nyymZtA==",
created_at: "2025-03-05 14:06:38.270468000 +0200"
So, by calling has_one_attached
macro, Rails generates #<name>
and #<name>_attachment
methods. ActiveStorage::Attachment
and ActiveStorage::Blob
are plain models with tables, so we can join and query them! Notice that ActiveStorage::Attachment
is a simple tying record, polymorphically joining a Blob to any of our app model records, and specifying the attachment name, since a model can have several attachments like profile pic, ID copy etc.
Let's try querying for customers who used the phrase "business" in the profile pic file name (this example is contrived, in my case attachments have project names in them, so it's useful for people to search by that):
Customer.joins(profile_pic_attachment: :blob)
.merge(ActiveStorage::Blob.where("filename ILIKE ?", '%business%'))
Querying by content_type
i.e. file extension, created_at
or even file size can also be useful.
Top comments (0)