简体   繁体   中英

ActiveAdmin custom filter from model attributes on the fly

I am updating an old ruby\\rails application that has an ActiveAdmin component (ActiveAdmin 0.6, Ruby 1.9.3 and Rails 3.2). The user has requested a filter that searches all fields in a given model. I don't think this is practical because you can't search a date or numeric value for "a" so I have compromised on just searching text with the filter.

Having looked at the ActiveAdmin documentation this states that you can create a filter for several attributes using " or " between the attributes. So if I wanted to search the "circumstances" or "accident_type" attributes I would use the filter below:

filter :circumstances_or_accident_type, :as => :string, label: "Search All Text Fields"

If I use this syntax the filter works as expected.

I now want to find all the string\\text attributes to create by filter attributes which I did using this code (there are probably neater ways of doing this but it works):

xfilter_text = ""
Notification.columns.each do |xfield|
  if xfield.type == :string or xfield.type == :text
    if xfilter_text.length == 0
      xfilter_text = xfield.name
    else
      xfilter_text << "_or_"
      xfilter_text << xfield.name
    end
  end
end

I used the result to hard-code the values into the filter which gave me the following (yes there are a few attributes in the model):

filter :circumstances_or_accident_type_or_author_type_or_location_or_immediate_action_or_injury_details_or_outcome_type_or_investigation_findings_or_action_to_prevent_recurrence_or_building_or_classification_or_manager_email_or_manager_name_or_current_stage_or_injured_last_name_or_injured_first_name_or_injured_gender_or_injured_address_or_injured_home_telephone_or_injured_work_status_or_injured_job_title_or_injured_working_pattern_or_injured_email_or_riddor_document_or_body_part_or_kind_of_accident_or_injury_type_or_service_or_team_or_defects_or_witness_details_or_location_details_or_hse_reference_number_or_riddor_category_or_address_or_details_of_treatment_or_processor_actions_or_business_unit_or_other_author_type_or_lost_time_details_or_changed_by_or_details_of_hospital_treatment, :as => :string, label: "Search All Text Fields"

I tested this and it worked. All good so far. I could just leave it here but I wanted to ensure the code is self maintaining so any changes in the model would not require changes to the custom filter. This is the part I am having trouble with. I would like to change the hardcoded attributes to use the results of the code that creates the filter attributes somehow. Something like this:

filter :get_filter, :as => :string, label: "Search All Text Fields"

def get_filter
  xfilter_text = ""
  Notification.columns.each do |xfield|
    if xfield.type == :string or xfield.type == :text
      if xfilter_text.length == 0
        xfilter_text = xfield.name
      else
        xfilter_text << "_or_"
        xfilter_text << xfield.name
      end
    end
    return xfilter
  end
end

I expect that I would need something that checks that attributes are returned otherwise the filter would fail. I can add that once I get the code working.

Appreciate any help or suggestions.

I'd be inclined to take the messy business of generating the query and delegate it to the model, using its own scope/class method. Then you just need to inform MetaSearch/Ransack (depending on your ActiveAdmin version) that it can search that scope, and you can add it as a filter.

For bonus points, you could drop the search method into a concern that you can include into any model.

app/admin/notifications.rb

filter :containing_text, as: :string, label: 'Text Search:'

app/models/notification.rb

# for MetaSearch
# search_methods :containing_text

# for Ransack
def self.ransackable_scopes(_opts)
  [:containing_text]
end

# this could be dropped into a concern as-is
def self.containing_text(query)
  # select text-type columns
  cols = columns.select { |c| [:string, :text].include?(c.type) }

  # generate query fragment
  fragment = cols.map { |c| "#{ table_name }.#{ c.name } LIKE ?" }
                 .join(' OR ')

  # execute sanitized query
  where(fragment, *Array.new(cols.size, "%#{ query }%"))
end

### EDIT by OP ###

I had never used concerns before so eventually worked out how to get it working:

1) Add the concern path to your application.rb

config/application.rb

class Application < Rails::Application
  config.autoload_paths += %W(#{config.root}/app/models/concerns)
end

2) Add the include to the Searchable concern and method call into the notifcation model

app/models/notification.rb

include Searchable
search_methods :containing_text

3) Created the concern:

/app/models/concerns/searchable.rb

module Searchable
  extend ActiveSupport::Concern
  module ClassMethods
    def self.containing_text(query)
      # select text-type columns (string and text)
      cols = columns.select { |c| [:string, :text].include?(c.type) }
      # generate query fragment
      fragment = cols.map { |c| "#{ table_name }.#{ c.name } LIKE ?" }

                     .join(' OR ')
      # execute sanitized query
      where(fragment, *Array.new(cols.size, "%#{ query }%"))
    end
  end
end

That then seemed to work. I probably should rename the searchable into something better but it works.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM