ActiveRecord Fuzzy Search - ruby-on-rails

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

Related

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.

Use pg textsearch with multiple conditions

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.

search double case in mongodb in Ruby on rails

here my code in model is
def self.search(search)
if search
where(name: /#{Regexp.escape(search)}/i)
else
scoped
end
end
Now i want to add another field also to search
like
where(price: /#{Regexp.escape(search)}/i)
So my query should search two field
like
where(name: /#{Regexp.escape(search)}/i) (or) where(price: /#{Regexp.escape(search)}/i)
How to add this two field in mongodb like or . Working example is accepted.
Since i have less knowledge about mongodb.
If you are using Mongoid 3, you can write your query like this:
self.or({name: /#{Regexp.escape(search)}/i}, {price: /#{Regexp.escape(search)}/i})
If you are using something other than Mongoid 3 (like MongoMapper or Mongoid 2), please give the name and version number.
Source: Selection syntax docs for Mongoid 3 are here.
Try this where('$or' => [{"name" => "/#{Regexp.escape(search)}/i"}, {"price" => "/#{Regexp.escape(search)}/i"}])

Searching through tag using Ransack

I'm using Ransack to perform fairly complex searches through some models. One of these models holds free text and uses the acts_as_taggable gem to tag the words.
I'm trying to create a collection selector of these words so that ransack can find any of the full text records from a subset of the tags that the user can define.
This gets me nearly there, but if I try to choose more than one word, it doesn't return any results!
= f.select :note_in, #freetexts.tag_counts_on(:tags), {}, {:multiple => true}
Ransack is not really oriented to complex searchs. It is very probable that if you stress ransack enough you end with a harder problem that if you where doing a complex select.
For complex search I would recomment Sequel, from the same author of ransack and much better oriented to complex searchs.
Moreover, according to thes thread you are on a dead end:
Ransack and acts-as-taggable-on issues
I'm not an expert at all, but this non-ransack solution could work for those that need to filter by tags with the acts-as-taggable-on gem:
#search = MyModel.ransack(params[:q])
#result = #search.result(distinct: true).includes(:related_model)
#result = #result.tagged_with(params[:tags].split(/\s*,\s*/)) if params[:tags].present?
#result = #result.paginate(page: params[:page], per_page: 20)
This expects a new :tags param that is out of the scope of Ransack. You can use to filter out the results that Ransack gives you.

Rails 3 Active Record relation order: Use hash instead of string

To sort a relation in Rails 3, we have to do this:
User.where(:activated => true).order('id ASC')
But I think this:
User.where(:activated => true).order(:id => :asc)
would make better sense because the way the field name be escaped should depend on the adapter (SqlLite vs Mysql vs PostgreSQL), right?
Is there something similar to that?
As far as I know there's no option for this syntax built into ActiveRecord, but it shouldn't be hard for you to add one. I found the order method defined in lib/active_record/relation/query_methods.rb. Theoretically, you should be able to do something like this:
module ActiveRecord
module QueryMethods
def order(*args)
args.map! do |arg|
if arg.is_a? Hash
# Format a string out of the hash that matches the original AR style
stringed_arg
else
arg
end
end
super
end
end
end
I think the key problem is: ActiveRecord API is not aware of ordering semantic. It just accepts a string and bypasses to the underlying database. Fortunately, Sqlite, MySQL and PostgreSQL has no difference in order syntax.
I don't think ActiveRecord can do this abstraction well, and it doesn't need to do it. It works well with relation databases, but is hard to integrate with NoSQL, eg. MongoDB.
DataMapper, another famous Ruby ORM, did better abstraction. Take a look at its query syntax:
#zoos_by_tiger_count = Zoo.all(:order => [ :tiger_count.desc ])
The API is aware of the ordering semantic. By default, DataMapper will generate SQL order statement:
https://github.com/datamapper/dm-do-adapter/blob/master/lib/dm-do-adapter/adapter.rb#L626-634
def order_statement(order, qualify)
statements = order.map do |direction|
statement = property_to_column_name(direction.target, qualify)
statement << ' DESC' if direction.operator == :desc
statement
end
statements.join(', ')
end
However, it's possible to override at DB adapter layer:
https://github.com/solnic/dm-mongo-adapter/blob/master/lib/dm-mongo-adapter/query.rb#L260-264
def sort_statement(conditions)
conditions.inject([]) do |sort_arr, condition|
sort_arr << [condition.target.field, condition.operator == :asc ? 'ascending' : 'descending']
end
end
TL;DR:
You don't need worry about syntax problem if you are only using SqlLite, Mysql and PostgreSQL.
For better abstraction, you can try DataMapper.
For this particular case, you could drop the 'ASC' bit as ordering in all database is implicitly ascending
Foo.order(:bar)
I am aware that this doesn't cover the case where you'd want to do order by bar desc but actually for order by this doesn't matter much unless you are using functions for the order by clause in which case maybe something like squeel would help

Resources