Activeadmin: how to filter for strings that match two or more search terms
Asked Answered
P

3

10

Let's say I've got User class with an :email field. And let's say I'm using activeadmin to manage Users.

Making a filter that returns emails that match one string, e.g. "smith", is very simple. In admin/user.rb, I just include the line

filter :email 

This gives me a filter widget that does the job.

However, this filter doesn't let me search for the intersection of multiple terms. I can search for emails containing "smith", but not for emails containing both "smith" AND ".edu".

Google tells me that activerecord uses Ransack under the hood, and the Ransack demo has an 'advanced' mode that permits multiple term searches.

What's the easiest way to get a multiple term search widget into activeadmin?

Ideally, I'd like a widget that would allow me to enter smith .edu or smith AND .edu to filter for emails containing both terms.

Podiatry answered 23/10, 2015 at 19:9 Comment(3)
This looks similar: scriptscoop.net/t/2e4ac6d6ff33/…Persistence
to use mltiple field search you would use filter :email_or_first_name_cont. I think (never tried it, so am not even sure if it is logical to assume it will work) you could try filter :email_or_email_cont :)Leila
I with it were this simple, but filter :email_or_email_cont doesn't seem to do the job. :( Thanks anyway!Magnificent
G
11

there is simple solution using ranasckable scopes

So put something like this in your model

class User < ActiveRecord::Base
    ....
    scope :email_includes, ->(search) {
       current_scope = self
       search.split.uniq.each do |word|
         current_scope = current_scope.where('user.email ILIKE ?', "%#{word}%")  
       end
       current_scope
    }

    def self.ransackable_scopes(auth_object = nil)
      [ :email_includes]
    end
end

After this you can add filter with AA DSL

Like

  filter :email_includes, as: :string, label: "Email"

UPD

should work if change email_contains_any to email_includes

Gaut answered 31/10, 2015 at 9:13 Comment(5)
These searches work when I run them in the console, but I can't make them work from activeadmin. AA won't split the search string into an array of terms. I still need to use my custom AA fork. Am I doing something wrong?Magnificent
@dB', for soem reasons we can't use _any, change email_contains_any to email_includesGaut
Ah, thank you! This works, and it's a much better solution than mine. Is this documented anywhere? Do the activerecord docs explain how to do these multi-term searches?Magnificent
it should be documented in ransack (AA just uses ransack), using ransackable scopes you can build any logic you want, not only multi-term searches ,and yes I like it as wellGaut
I wish I could upvote this more than once! What I needed was the ability to filter by a list of email addresses, so I did arr = search.downcase.split(/[ \n]/) then current_scope = current_scope.where(email: arr)Trencherman
P
6

I've figured out a solution but it's not pretty.

The good news is that Ransack has no trouble with multiple terms searches. These searches use the 'predicate' cont_all. The following line works for finding emails containing 'smith' and '.edu'.

User.ransack(email_cont_all: ['smith','.edu'] ).result

Since these searches are easy in Ransack, they're probably straightforward in Activeadmin, right? Wrong! To get them working, I needed to do three things.

  1. I put a custom ransack method (a.k.a. ransacker) into User.rb. I named the ransacker email_multiple_terms.

    class User < ActiveRecord::Base
    
    # ...
    
    ransacker :email_multiple_terms do |parent|
      parent.table[:path]
    end
    
  2. I declared a filter in my activeadmin dashboard, and associated it with the ransacker. Note that the search predicate cont_all is appended to the ransacker name.

admin/User.rb:

ActiveAdmin.register User do

# ...

filter :email_multiple_terms_cont_all, label: "Email", as: :string 

This line creates the filter widget in Activeadmin. We're nearly there. One problem left: Activeadmin sends search queries to ransack as a single string (e.g. "smith .edu"), whereas our ransacker wants the search terms as an array. Somewhere, we need to convert the single string into an array of search terms.

  1. I modified activeadmin to split the search string under certain conditions. The logic is in a method that I added to lib/active_admin/resource_controller/data_access.rb.

    def split_search_params(params)
      params.keys.each do |key|
        if key.ends_with? "_any" or key.ends_with? "_all"
          params[key] = params[key].split  # turn into array
        end
      end
      params
    end
    

I then called this method inside apply_filtering.

    def apply_filtering(chain)
      @search = chain.ransack split_search_params clean_search_params params[:q]
      @search.result
    end

This code is live in my own fork of activeadmin, here: https://github.com/d-H-/activeadmin

So, to get multiple term search working, follow steps 1 and 2 above, and include my fork of A.A. in your Gemfile:

  gem 'activeadmin', :git => 'git://github.com/d-H-/activeadmin.git'

HTH.

If anyone's got a simpler method, please share!

Podiatry answered 27/10, 2015 at 2:20 Comment(0)
G
3

Just add three filters to your model:

filter :email_cont
filter :email_start
filter :email_end

It gives you a flexible way to manage your search.

enter image description here

This filter executes next sql code:

SELECT  "admin_users".* FROM "admin_users"
WHERE ("admin_users"."email" ILIKE '%smith%' AND 
       "admin_users"."email" ILIKE '%\.edu')
ORDER BY "admin_users"."id" desc LIMIT 30 OFFSET 0

I expect that exactly what you're looking for.

Giff answered 25/10, 2015 at 20:34 Comment(1)
This is close, but I actually need something that matches multiple queries anyway in the email. Searching at the beginning, middle and end doesn't quite cut it for me. :( Thanks, tho.Magnificent

© 2022 - 2024 — McMap. All rights reserved.