Skip to content
This repository

multi term search in one field #218

Closed
anso opened this Issue April 08, 2013 · 14 comments

5 participants

anso Ryan Bigg rilian Dimitris Jon Atack
anso
anso commented April 08, 2013

Not an issue I suppose but a lack of information/documentation on this...
I wiould like to do something like that :

 User.search({:profile_last_name_or_profile_first_name_cont_any=> 'jane doe'.split(' ')}).result

the SQL generated is the following

"SELECT "users".* FROM "users" LEFT OUTER JOIN "profiles" ON "profiles"."user_id" = "users"."id" WHERE ((("profiles"."last_name" ILIKE '%jane%' OR "profiles"."last_name" ILIKE '%doe%') OR ("profiles"."first_name" ILIKE '%jane%' OR "profiles"."first_name" ILIKE '%doe%')))"

which returns the expected results... But how to implement this request in my search form (in my view) and/or in my controller ?

My search form is :

<%= search_form_for @q, url: search_cockpit_users_path, method: 'post', id: 'users-search-form', class: 'span3 pull-right' do |su| %>
      <%= su.text_field :profile_last_name_or_profile_first_name_or_email_cont_any, :placeholder => 'full name or email contains' %>
      <%= su.submit 'Search', :name => nil, :class => 'btn ' %>
  <% end %>

As it is the SQL returned is

"SELECT "users".* FROM "users" LEFT OUTER JOIN "profiles" ON "profiles"."user_id" = "users"."id" WHERE ((("profiles"."last_name" ILIKE '%jane doe%') OR ("profiles"."first_name" ILIKE '%jane doe%')))"

but how to implement the split(' ') to have the expected SQL ?

Any help (and a bit of explanation) would be great, documentation is a bit light on the subject...

Cheers

Ryan Bigg
Collaborator
radar commented April 11, 2013

Why don't you take the parameter from the form and split it in the controller as you do in the first example?

anso
anso commented April 12, 2013

@radar
Of course it's the first thing I tried... But it does not work,
The search field string is converted as an SQL query between the submit action and before the search is effectively done and the result returned by the controller...

Ryan Bigg
Collaborator
radar commented April 15, 2013

@anso: Do you have an example app that I could try and reproduce this problem in? That would help me understand what's going wrong here and probably fix your problem.

anso
anso commented April 16, 2013

@radar : Ryan I have no example app. But the problem is easy to reproduce. (I paste the basic code here http://pastebin.com/JCruwhhp)

Two simplistic models User and Profile and a one-to-one relation between both. (User has_one Profile, Profile belongs_to User) and a users/index view, with search form in it and the users listing under the search form

Given you have 4 users (IDs 1, 2, 3, 4)

ID /profile.last-name / profile.first_name
1 DOE Jane
2 VEGA Vincent
3 DOE John
4 DUMMY Jane

If I type 'DOE' in the search field it returns users 1 & 3
If I type 'Jane" in the search field it returns users 1 & 4

If I type 'Jane DOE' it should returns

  • user 1 (contains 'Jane' in profile.first_name & 'DOE' in profile.last_name),
  • user 3 (contains 'DOE' in profile.last_name)
  • user 4 (contains 'Jane' in profile.first_name)

but it returns nothing as the "where" query generated by ransack is

WHERE ((("profiles"."last_name" ILIKE '%jane doe%') OR ("profiles"."first_name" ILIKE '%jane doe%')))

Cheers

rilian

I am not sure if "search any" feature is supported in Ransack but in your case as a quick workaround you can split request with .split(' ') and pass all found words into grouping with or combinator , see example #169

anso
anso commented April 17, 2013

@rilian : I've seen this post but did not understand it. It is said : "I catch the params[:q] before passing it to the ransack engine and do some manipulations on it " ... How ? When ? Where ?

rilian

@anso, your controller fills in params variable (Hash)

your form use @q to store search params, which is fine

here is a workaround for any

# a bit dirty code
params[:q][:combinator] = 'or'
params[:q][:groupings] = []
custom_words = params.delete('profile_last_name_or_profile_first_name_or_email_cont_any')
custom_words.split(' ').each_with_index do |word, index|
  params[:q][:groupings][index] = {profile_last_name_or_profile_first_name_or_email_cont: word} 
end

@q = User.joins(:profile).search(params[:q])
@users = @q.result.paginate(:per_page =>10, :page => params[:page])
...
anso
anso commented April 17, 2013

@rilian : Aaaaaaah ok ! All is clear now ! Thanks a lot rilian you made my day better (and me a bit less idiot)!

Ryan Bigg radar closed this April 17, 2013
Ryan Bigg
Collaborator
radar commented April 17, 2013

Thanks @rilian :)

Dimitris

very nice. thanks

Jon Atack
Collaborator

For query performance and simplicity, you can also store the various terms you want to search on in a combined search text column in your database (I use a citex field in postgres and remove accents and punctuation).

For example, first name + last name + email + phone stored as "JohnDoejohndoegmailcom1234567890"

Thus there is only one field to search on, and you can add a database index on the search query.

Dimitris

good idea. does this solution suffer from some 'false positives'? i.e. if you search with 'ilco' or 'hnDo' it would most likely come back with a result (which may or may not be ok)

rilian

i like @jonatack 's idea

@dimitrisdovinos you should decide yourself how to combine text data. it may be separated differentlyJohn Doe$$$johndoegmailcom$$$1234567890 to avoid false positives

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.