Rails 7.1 allows attaching file or pathname to has_one_attached

railsOctober 23, 2023Dotby Alkesh Ghorpade

Rails 7.1 allows attaching file or pathname to has_one_attached associations. This feature simplifies passing a file or a pathname in test files when creating model instances.

Active Storage is a built-in Rails feature that provides a unified interface for uploading, storing, and processing files. It supports multiple cloud storage services, such as Amazon S3, Google Cloud Storage, and Microsoft Azure Storage. Active Storage also comes with a local disk-based service for development and testing.

Before Rails 7.1

Before Rails 7.1, the has_one_attached association only accepted Active Storage blobs as attachments. This meant that users had to first upload a file to Active Storage before they could attach it to a model.

Let's say you have a Rails application with a User model. Your application accepts an image when creating a new user. Your application attaches a default avatar if the user does not upload any image. You may want to attach a default avatar from your Rails directory, as shown:

class User < ApplicationRecord
  has_one_attached :avatar_image
end

@user = User.find_by_email("sam@example.com")
@user.avatar_image.attach(
  io: File.open("path/to/default_avatar_image"),
  filename: "avatar_image.png",
  content_type: "image/png",
)

You might have to implement the below method in the test or factory class.

FactoryBot.define do
  factory :user do
    first_name { Faker::Name.first_name }
    last_name { Faker::Name.last_name }

    trait :with_avatar_image do
      transient do
        avatar_file { Rails.root.join("spec", "fixtures", "avatar_image.png") }
      end

      after :build do |user, evaluator|
        file = evaluator.avatar_file

        user.avatar_image.attach(
          io: file.open,
          filename: file.basename.to_s,
        )
      end
    end
  end
end

A developer needs to ensure the image is first uploaded to the ActiveStorage location and then it is attached to the User avatar field. These steps can be encapsulated, making the code much easier to understand and write.

In Rails 7.1

With the recent changes in Rails 7.1, you can pass the file or pathname to the has_one_attached association.

class User < ApplicationRecord
  has_one_attached :avatar_image
end

@user = User.find_by_email("sam@example.com")
@user.update(avatar_image: File.open("path/to/default_avatar_image"))

Attaching the image in the specs file is straightforward.

FactoryBot.define do
  factory :user do
    first_name { Faker::Name.first_name }
    last_name { Faker::Name.last_name }

    trait :with_avatar_image do
      avatar_image { File.open("path/to/default_avatar_image") }
    end
  end
end

To know more about this feature, please refer to this PR.

Closing Remark

Could your team use some help with topics like this and others covered by ShakaCode's blog and open source? We specialize in optimizing Rails applications, especially those with advanced JavaScript frontends, like React. We can also help you optimize your CI processes with lower costs and faster, more reliable tests. Scraping web data and lowering infrastructure costs are two other areas of specialization. Feel free to reach out to ShakaCode's CEO, Justin Gordon, at justin@shakacode.com or schedule an appointment to discuss how ShakaCode can help your project!
Are you looking for a software development partner who can
develop modern, high-performance web apps and sites?
See what we've doneArrow right