Use pg textsearch with multiple conditions - ruby-on-rails

Im currently using postgres textsearch functionality to search recipes which have a particular ingredient like so
Recipe.includes("ingredients").where("ingredients.name ## :query", :query => query)
but what i want to do is to be able to search for multiple ingredient query names, either matching all or any depending on the situation.
I.e. given these two recipes and their ingredients
Sandwich => Bacon, cheese, tomato
Pasta => Bacon, Olive
using these custom methods id like to define
Recipe.search_by_any("Bacon Tomato") => Sandwich and Pasta
Recipe.search_by_all("Bacon Tomato") => Sandwich
How do you achieve this using pg textsearch on associated record columns?
I dont want to use PGSearch gem as it's giving me some wierd errors

Partial answer:
The easy search is the one which matches any ingredient. Use an inner join to filter the results you want.
def self.search_by_any(query)
ingredients_query = query.split(' ').join('|') # Convert queries like "Bacon Tomatoes" to "Bacon|Tomatoes"
Recipe.joins(:ingredients).includes(:ingredients).where("ingredients.name ## to_tsquery(:ingredients_query)", :ingredients_query => ingredients_query)
end
The search_by_all method is going to be a bit more complicated. I'll take a look at it later if no one else has given a good suggestion.

In most of this cases I would like to use the PgSearch gem (https://github.com/Casecommons/pg_search). I think you could find the multi-search method helpful in this case.

Related

ActiveRecord Fuzzy Search

I'm looking to perform a search to find an object similar to this:
Object(id: 1, example: "abc")
by using a search like this:
params[:search] = "abcdef"
Object.where("example LIKE ?", "#{params[:search]%")
but am only able to use the above example if my search has less characters than my object, not more.
I think it should be
params[:search] = "abcdef"
Object.where("example LIKE ?", "%#{params[:search]}%")
Also might want to use ilike for case insensitive search (if you're using postgres)
Note: the fuzzily gem does not work with Rails 6. This solution has been deprecated.
The fuzzily gem allows you to do fuzzy searching of ActiveRecord models that you've instrumented appropriately.
Fuzzily finds misspelled, prefix, or partial needles in a haystack of strings. It's a fast, trigram-based, database-backed fuzzy string search/match engine for Rails.
Once you've installed the gem, you can instrument your model as follows:
class MyStuff < ActiveRecord::Base
# assuming my_stuffs has a 'name' attribute
fuzzily_searchable :name
end
You can then perform fuzzy searches as follows:
MyStuff.find_by_fuzzy_name('Some Name', :limit => 10)
# => records

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

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.
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
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.
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
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.
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!
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.
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.

Rails order by in active record for search

I want to organize search result in rails.
If i search for a string eg "food products" using
Products.where("name ILIKE '%food%' or name ILIKE '%products%' or name ILIKE '%food products%'")
but the result set is not organized. I want the results with complete "food products" string to be on top. but i am unable to arrange results some how.
Any help will be highly appreciated
thanks
Give it a try
Products.where("name LIKE '%food%' OR name LIKE '%product%'").order("CASE WHEN NAME LIKE '%food%' THEN 1
WHEN NAME LIKE '%product%' THEN 2
ELSE 100 END)
This will give you result as per your sort order.
This is a situation where you probably want to take advantage of full text search in Postgres. The pg_search gem makes this very easy to implement in rails. Add gem 'pg_search' to your Gemfile and run bundle.
In your model:
class Product < ActiveRecord::Base
include PgSearch
pg_search_scope :search_by_name, :against => :name
end
Now you can use this method and it should put the "food products" result on top if that is the exact match:
Product.search_by_name('food products')

Datamapper: Sorting results through association

I'm working on a Rails 3.2 app that uses Datamapper as its ORM. I'm looking for a way to sort a result set by an attribute of the associated model. Specifically I have the following models:
class Vehicle
include DataMapper::Resource
belongs_to :user
end
class User
include DataMapper::Resource
has n, :vehicles
end
Now I want to be able to query the vehicles and sort them by the name of the driver. I tried the following but neither seems to work with Datamapper:
> Vehicle.all( :order => 'users.name' )
ArgumentError: +options[:order]+ entry "users.name" does not map to a property in Vehicle
> Vehicle.all( :order => { :users => 'name' } )
ArgumentError: +options[:order]+ entry [:users, "name"] of an unsupported object Array
Right now I'm using Ruby to sort the result set post-query but obviously that's not helping performance any, also it stops me from further chaining on other scopes.
I spent some more time digging around and finally turned up an old blog which has a solution to this problem. It involves manually building the ordering query in DataMapper.
From: http://rhnh.net/2010/12/01/ordering-by-a-field-in-a-join-model-with-datamapper
def self.ordered_by_vehicle_name direction = :asc
order = DataMapper::Query::Direction.new(vehicle.name, direction)
query = all.query
query.instance_variable_set("#order", [order])
query.instance_variable_set("#links", [relationships['vehicle'].inverse])
all(query)
end
This will let you order by association and still chain on other scopes, e.g.:
User.ordered_by_vehicle_name(:desc).all( :name => 'foo' )
It's a bit hacky but it does what I wanted it to do at least ;)
Note: I'm not familiar with DataMapper and my answer might not be within the standards and recommendations of using DataMapper, but it should hopefully give you the result you're looking for.
I've been looking through various Google searches and the DataMapper documentation and I haven't found a way to "order by assocation attribute". The only solution I have thought of is "raw" SQL.
The query would look like this.
SELECT vehicles.* FROM vehicles
LEFT JOIN users ON vehicles.user_id = users.id
ORDER BY users.name
Unfortunately, from my understanding, when you directly query the database you won't get the Vehicle object, but the data from the database.
From the documentation: http://datamapper.org/docs/find.html. It's near the bottom titled "Talking directly to your data-store"
Note that this will not return Zoo objects, rather the raw data straight from the database
Vehicle.joins(:user).order('users.name').all
or in Rails 2.3,
Vehicle.all(:joins => "inner join users on vehicles.user_id = user.id", :order => 'users.name')

using searchlogic with habtm associations

I have the following issue:
I have two classes in my rails app, joined by a HABTM association, like so:
#ad.rb
has_and_belongs_to_many :specs
#spec.rb
has_and_belongs_to_many :ads
joined by an ads_specs table.
I'm trying to perform a search on the Ad class using the excellent searchlogic gem. Everything went fine until I wanted to return all the ads that have ALL the selected specs, not any of them.
I tried #ads = Ad.specs_id_like_all(id1, id2, id3)but to no results, since I think it's trying to match a spec with an id of "id1, id2, id3". I also tried to .split the ids or directly type them in an array but nothing worked.
My exact search query is:
if params[:search]
#ads = Ad.search(:price_lte => params[:search][:price],
:rulaj_lte => params[:search][:rulaj],
:fabrication_date_gte => Date.new(params[:search][:"an_fabr(1i)"].to_i,"01".to_i,"01".to_i)).specs_id_like_all(2, 457, 233)
else
#ads = Ad.all
end
Do you guys have any idea how could I solve this problem? I swear it's the last time I use HABTM associations in a rails app, but it's so late to change to a polymorphic one this late in the development process :).
If i understand correctly, you're trying to perform an SQL IN() query (i.e. spec_id IN (x,y,z..) )
Searchlogic does support passing an array to a *_eq() method which will use SQL's IN() predicate:
Ad.search(:spec_id_eq => [1,2,3,4])
**Not sure if this works in older versions of Searchlogic*

Resources