ruby-on-railsrails-activestorageruby-on-rails-7ransack

Search Rails 7 ActiveStorage filename with Ransack


I have a Rails 7.1.3 app that has ActiveStorage and Ransack installed. I have a model called Upload that has_one_attached :file. I would like to search by filename, using Ransack, in the uploads/index view. I have tried the answers here and the suggestion here, but neither are working. I have also tried to create lib/active_storage/attachment.rb and add the rasackable_attributes, but still no luck. The error I'm getting is Association named 'file' was not found on Upload; perhaps you misspelled it?. Anyone have any idea how to get this to work?

Here is my code:

# upload.rb

has_one_attached :file

def self.ransackable_associations(auth_object = nil)
  ["file"]
end

# lib/active_storage/attachment.rb

class ActiveStorage::Attachment < ApplicationRecord
  def self.ransackable_attributes(auth_object = nil)
    ["blob_id", "created_at", "id", "id_value", "name", "record_id", "record_type"]
  end
end

/uploads/index.html.erb

<%= f.search_field :file_filename_cont, autofocus: true, placeholder: "Search by filename" %>

# controllers/uploads_controller.rb

@q = Upload.ransack(params[:q])
@uploads = @q.result.includes(:file)

Solution

  • has_one_attached :file creates file_attachment and file_blob associations:
    https://github.com/rails/rails/blob/v7.1.3.3/activestorage/lib/active_storage/attached/model.rb#L114-L115

    you can use reflections to inspect associations:

    >> Import.reflections.keys
    => ["file_attachment", "file_blob"]
    

    ActiveStorage::Blob is where the actual filename is stored:

    >> ActiveStorage::Blob
    => ActiveStorage::Blob(id: integer, key: string, filename: string, content_type: string, metadata: text, service_name: string, byte_size: integer, checksum: string, created_at: datetime)
    #                                                ^^^^^^^^^^^^^^^^
    

    Bring it all together:

    # config/initializers/active_storage.rb
    
    ActiveSupport.on_load(:active_storage_blob) do
      def self.ransackable_attributes(auth_object = nil)
        %w[filename]
      end
    end
    
    # app/models/import.rb
    
    class Import < ApplicationRecord
      has_one_attached :file
    
      def self.ransackable_attributes(auth_object = nil)
        []
      end
    
      def self.ransackable_associations(auth_object = nil)
        ["file_blob"]
      end
    end
    
    # app/controllers/imports_controller.rb
    
    class ImportsController < ApplicationController
      # GET /imports
      def index
        @q = Import.ransack(params[:q])
        @imports = @q.result
      end
    end
    
    # app/views/imports/index.html.erb
    
    <%= search_form_for @q do |f| %>
      <%= f.label :file_blob_filename_cont %>
      <%= f.search_field :file_blob_filename_cont %>
    <% end %>
    
    <%= render @imports %>
    

    Seems to work:

    >> Import.ransack({"file_blob_filename_cont"=>"ava"}).result
    => [#<Import:0x00007f9101d16048 id: 1>]
    
    >> Import.ransack({"file_blob_filename_cont"=>"asdf"}).result
    => []