Search code examples
ruby-on-railsrails-activestorage

How to serve ActiveStorage attachments under a subpath of the owner entity?


Here is what I would like to achieve. Given a model that has something like this

class MyModel < ApplicationRecord
    has_many_attached :files
end

I would like to be able to serve the attachments as files under
GET /mymodels/:id/files/:filename
(filename includes the format as well).
Of course this assume that my application no duplicate filenames for a specific MyModel id.

Is there a specific way that is recommended to do that?


Solution

  • config/routes.rb fully supports non-resourceful routes (i.e., arbitrary URL). They support dynamic segments in a form like in your question:

    get '/mymodels/:id/files/:filename', to: 'mymodels#serve_file'
    

    Given the above route, hitting https://my_app.com/mymodels/1/files/banana.txt will send the request to to the show_files method on MyModelsController with params of:

    { id: 1, filename: 'banana.txt' }
    

    The Rails Guide has lots of documentation for non-resourceful routes.

    EDIT: (the gist of the question was clarified)

    If your intent is to automatically download the attached files when the URL is hit, your controller action might look like:

      def serve_file
        mymodel = MyModel.find(params[:id])
        blob = mymodel.files.blobs.find_by(filename: "#{params[:filename]}.#{params[:format]}")
        redirect_to blob.url
      end
    

    You have to reconstitute the filename, since Rails will automatically split the terminating element in a path into filename and format parameters.

    NB: I had some trouble in dev with :local storage getting .url to work, but this works fine with actual remote storage (S3, etc.).

    If your intention is to display/manipulate the file within your app (i.e., not download), you should consider passing the filename as a query parameter rather than have it as the terminating element in the URI.