Signed id for attachment points to the Blob id

Hi, Let’s say I have an attachment

> attachment = ActiveStorage::Attachment.last
> a.signed_id
=> "signed_id_one"

So currently when you want to get signed_id of an attachment, you actually get signed_id of a blob. Whenever I’m passing this signed_id from the front-end to the Rails app I am clueless on how to query specific attachment, because I have additional columns like title in my ActiveStorage::Attachment model. Now to go around that I just map these attachments in the attachable model itself. For example Post model has a method called attachments and I map for ex. signed_id and few other things for quick queries and I have to use this helper method to pull out. For example in my controller

@post.attachments.find{ |a| a[:signed_id] == parameters['attachment']['attachment_id'] }

Is it possible to be able to find Attachment by signed_id? If I query using find_signed method it looks for a Blob.

> ActiveStorage::Attachment.find_signed(signed_id_one)
=> nil

I’d like to contribute, but I’m not sure how to solve this in the source code myself, I’m still relatively fresh to rails. I wanted to ask if this is expected behaviour.

First, start by taking a look at the Contributing Guide. It contains instructions on how to download the “rails-dev-box” and use it to edit/test the source code, as well as how to open PRs for the Rails team.

Next, you will take a look at the Active Storage lib. If you check the file under active_storage/app/models/active_storage.attachment you will see this in line 25:

delegate :signed_id, to: :blob

This is why calling signed_id on the attachment returns the signed_id of the blob. Unfortunately if you open a PR for changing this, it will probably be rejected. This line was added so that this code would work:

url_for @user.avatar

So you cannot find attachments by signed id. The thing here, is that you are not supposed to add fields like “title” to the attachment or the blob. There have been PRs in the post by contributors who wanted to do this, and they have all been rejected for the same reason: This is not how active storage is supposed to work.

If you need extra fields, you create a model tho hold that field, and the attachment

class Photo < ApplicationRecord
  has_one_attached :file
  validates :title, presence: true

So your best option is to move that title from the attachment to your model.

1 Like

Thank you for pointing me in the right direction, I’ve already managed to find that in the code but I wasn’t sure why it’s designed that way, so this clarifies it for me. Why is ActiveStorage::Attachment not supposed to work this way? I’m having a hard time understanding how should I go about this additional model. I tried it before, and I couldn’t figure out how to add attachments to this “wrapper” model of an attachment.

Take this with a grain of salt, since I’m just a regular contributor and not a member of the Rails team, but two things you should keep in mind

1 - Most Rails features are extractions of a production app, and therefore tailored to a very specific use case. The community later expands that use case. For example, Active Storage originated from Basecamp where it was only responsible for serving private files, so it had no support for CDN caching

2 - When evaluating PRs, my experience is that Rails maintainers put a lot of emphasis on “what is Rails responsability and what is not”. This is where your use case is on. Active Storage responsibility is “uploading files to storage, serving them, and extracting useful information from them, such as image dimensions.” This means that AST is not expected to have any knowledge of where the file originate from or how it will be used. And giving a file a title falls under “AST knows how a file will be used”.

You did not mention your use case, so I will try to explain with an example. Let’s say your product team tells you this: “Users can create galleries, and each gallery has many photos”. Simple enough, and you could implement something like this:

class Gallery < ApplicationRecord
  belongs_to :user
  has_many_attached :photos
end do |photo|
  image_tag photo

Of course, things are never that simple. After you show your PR, the product team will say "That’s mighty fine. But we also need to let them choose change the position of the photos, give each one a title and description, and oh, when they upload it they must be able to resize and crop it. Also, we want it optimized so it displays fast on mobile phones”

This is where you might think: “Well, I need to add those things to the attachments or blobs”. Unfortunately you’d fall under “this is not Active Storage responsability. AST is not supposed to know how an image will be used”. The expected implementation is this:

class Gallery < ApplicationRecord
  belongs_to :user
  has_many :photos

class Photo < ApplicationRecord
  belongs_to :gallery
  has_one_attached :file

  validates :title, presence: true
  validates :description presence: true
  validates :position, presence: true

  validates :crop_x, presence: true
  validates :crop_y, presence: true
  validates :width, presence: true
  validates :height, presence: true

  def thumbnail
    file.variant { 
      saver: { strip: true, quality: 80, interlace: true }, format: "jpg" },
      crop: [crop_x, crop_y, width, height],
      resize_to_limit: [818, nil]
end do |photo|
  image_tag photo.thumbnail

Thanks Breno, this explains alot. One thing I was struggling with and therefore I gave up on this idea was, how do I include that in my form? Wherever I found this solution no one ever mentioned how the user should upload the file when editing the gallery. For example if I have a form for the gallery, I’m not able to upload it in a straight forward manner as I used to when it was just an AS Attachment. How do you go around that?

Section 9 of the ActiveStorage explains how to use it with DirectUpload. I presume that’s what you’re looking for.

Here is a tutorial that walks you through how to create a post that has multiple file attachments.

I searched for active storage direct upload example to find this tutorial.

great reply & example, cheers!