简体   繁体   中英

Search an array of values with Ransack

I'm new to Ransack and I've ran into what appears to be a case that isn't cleanly covered by Ransack. I'm basically trying to search a value but the searched value is wrapped in an array.

CODE:

<%= f.search_field :category_or_account_number_or_status_or_account_number_or_account_name_or_accounts_name_or_accounts_number_or_user_name_or_user_rep_code_list_cont_any %>

At the very end there is this piece user_rep_code_list_cont that is a default array attribute on users it looks like this currently ["al20", "b234"]

So, when I type al20 in the Ransack search bar I get this error.

ERROR:

PG::UndefinedFunction: ERROR:  operator does not exist: character 
varying[] ~~* unknown
LINE 1: ..."name" ILIKE '%al20%') OR "users"."rep_code_list" ILIKE 
'%al...
                                                         ^
HINT:  No operator matches the given name and argument type(s). You 
might need to add explicit type casts.

CONTROLLER:

def index
  @q = Submission.submissions_for(user: current_user).ransack(params[:q])
  @submissions = @q.result.includes(:user, :accounts).ordered(current_user).page(params[:page]).per(25)
end

Again, I'm not a Ransack expert but this seems like something that should be covered by now. I want to search an attribute on a model that is an Array. Any help would be great thanks!

I ended up using a custom Ransacker for this case:

ransacker :rep_code_list do
  Arel.sql("array_to_string(rep_code_list, ',')")
end

This will turn the array into a string so that Ransack can search with the cont predicate. Not sure if this is the best way to do it but it worked for my case.

Working with arrays and Ransack is not that simple, you have to do a lot of the work by hand as the underlying queries quickly take you into advanced territory. Checking membership in an array is relatively easy , checking every element of an array against a LIKE pattern is somewhat more complicated as you need to do a LATERAL JOIN to an unnest function call to unwrap the array so that you can LIKE against its members:

select users.*
from users, unnest(users.rep_code_list) c -- This is an implicit LATERAL JOIN
where c ilike '%some-pattern%'

and you'll probably want to throw a distinct on (users.id) in the SELECT clause to clean up any duplicates that appear from other parts of the query:

select distinct on (users.id) users.*
from users, unnest(users.rep_code_list) c -- This is an implicit LATERAL JOIN
where c ilike '%some-pattern%'
  and ...

To get Ransack to use such a query requires you to add a scope (and tell Ransack that it can use the scope) or perhaps writing a custom ransacker . Unfortunately, there doesn't seem to be any way to get Ransack to use a scope together with the usual attr1_or_attr2_or... parameter name parsing logic so your scope has to do it all:

class User < ApplicationRecord
  def self.ransackable_scopes(auth = nil)
    %i[things_like]
  end

  def self.things_like(s)
    select('distinct on (users.id) users.*')
      .joins(', unnest(users.rep_code_list) c')
      .where('c ilike :pat or users.category ilike :pat or ...', pat: "%#{s}%")
  end
end

and then in the form:

<%= f.search_field :things_like %>

You might have better luck mashing that LATERAL JOIN logic into a custom ransacker or, better IMO, replacing the array with a separate table so that you can use Ransacker's association logic and treat the codes like first class entities in the database rather than just strings. Some sort of full test search (instead of Ransack) might be another option.

You might be able to do something with PostgreSQL's array_to_string function to flatten the array but then you'll have to deal with delimiters and you'll still be stuck with a "do it all" scope (or perhaps a custom ransacker).

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