Search code examples
ruby-on-railsruby

Can I get a ActiveStorage Attachment via a relation in one simple query?


Given the following relations:

class User
  has_many :messages
end

class Message
  has_many_attached :files
end

I want to get the attachment by id, scoped on the current_user. A way to do this is as follows:

current_user.messages.joins(:files_attachments).where(active_storage_attachments: { blob_id: params[:id] }).first.files.find(params[:id])

I was wondering if there was a prettier way that's still efficient?

The main issue is that record is polymorphic, so this gives an error:

ActiveStorage::Attachment.includes(:record).where(messages: { user_id: current_user.id }).find_by(blob_id: params[:id] })

Users can have many messages, so this solution would be too inefficient:

ActiveStorage::Attachment.where(record_type: "Message", record_id: current_user.messages.pluck(:id)).find_by(blob_id: params[:id] })

Solution

  • To get an attachment by its id - blob_id scoped to only the current user (addressing security concerns), define a scope in the User model:

    class User
      has_many :messages
    
      def find_attachment_by_blob_id(blob_id)
        ActiveStorage::Attachment
          .joins("INNER JOIN messages ON messages.id = active_storage_attachments.record_id AND active_storage_attachments.record_type = 'Message'")
          .where(messages: { user_id: id })
          .find_by(active_storage_attachments: { blob_id: blob_id })
      end
    end
    

    Then you can get the attachment by:

    attachment = current_user.find_attachment_by_blob_id(params[:id])