Search through sentences - ruby-on-rails

I have a rails app and have decided to have a search. I have a search textfield and the controller takes care of everything. In my search method
def self.search(search)
if search
str = []
search.split.each do |s|
a = find(:all, :conditions => ['title or content LIKE ?', "%#{s}%" ])
str = (str + a).uniq
end
else
find(:all)
end
end
I want to be able to handle multiple word searches. If you remove the each...loop it works fine with 1 word searches.
Anyways, I would like to find the related posts for each word that is searched and return a combination of that.
Ex. If someone searches "Greatest Player" it will return all instances of posts that have the title or content "Greatest" and any that have the title or content "Player"

You most likely should be looking at full text searching:
Full Text Searching with Rails
http://en.wikipedia.org/wiki/Full_text_search

Shouldn't it be
a = find(:all, :conditions => ['title or content LIKE ?', "%#{s}%" ])

All you needed was to change the find statement to Mischa's code and add a return statement
Working Code:
def self.search(search)
if search
str = []
search.split.each do |s|
a = where(title LIKE ? or content LIKE ?', "%#{s}%", "%#{s}%")
str = (str + a).uniq
end
return str
else
find(:all)
end
end

Related

Ignore uppercase, downcase and accent in search

I have a search system with filter here. This system work like a charm but I have some problem with downcase / uppercase and accent.
For example if I search "marée" I have result but if I search "MAREE" or "Marée" or "maree". I don't have result.
I want to fix that. How I can fix this ? Thank you.
my controller
def resultnohome
if params[:query].blank?
redirect_to action: :index and return
else
#campings = Camping.searchi(params[:query], params[:handicap], params[:animaux], params[:television], params[:plage], params[:etang], params[:lac])
if params[:query] == "aube"
#pub = Camping.find_by_id(1)
else
end
end
end
My model
def self.searchi(query, handicap, animaux, television, plage, etang, lac)
return scoped unless query.present?
result = left_outer_joins(:caracteristiquetests, :situations).where('nomdep LIKE ? OR name LIKE ? OR nomregion LIKE ? OR commune LIKE?', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%")
result = result.where('handicap LIKE ?', "%#{handicap}%") if handicap
result = result.where('animaux LIKE ?', "%#{animaux}%") if animaux
result = result.where('television LIKE ?', "%#{television}%") if television
result = result.where('plage LIKE ?', "%#{plage}%") if plage
result = result.where('etang LIKE ?', "%#{etang}%") if etang
result = result.where('lac LIKE ?', "%#{lac}%") if lac
return result
end
If you insist on using SQLite then you don't have many good options. The most common suggestion is to have extra columns in your database, that are normalized values so if your plage column contains "Marée" then you also have a column plage_ascii that contains "maree"
you need to create the additional columns with migrations, then you would have a before_save action in your model...
before_save :create_normalized_strings
def create_normalized_strings
self.handicap_ascii = handicap.downcase.mb_chars.normalize(:kd).gsub(/[^x00-\x7F]/n, '').to_s
self.animaux_ascii = animaux.downcase.mb_chars.normalize(:kd).gsub(/[^x00-\x7F]/n, '').to_s
# etc etc
end
Then in your search do...
if handicap
test_handicap = handicap.downcase.mb_chars.normalize(:kd).gsub(/[^x00-\x7F]/n, '').to_s
result = result.where('handicap_ascii LIKE ?', "%#{handicap}%")
end
It's not great, as it basically forces you to duplicate data in your database into extra columns. If you can consider more sophisticated databases other than SQLite then you'd be better off... personally I wouldn't ever use SQLite in a production environment.

Search every field or attribute in model Rails

I have a simple model called Company with atributes such as Name, contact email, address, etc. I've created a search form where a user can find the company by searching for any attribute i.e., city, name of business, etc.
I'm new to Rails but I've figured out how to get it working like so in my controller.
if(params[:searchstring].present?)
logger.debug "*** Running search --> "
term = params[:searchstring]
#companies = Company.where('name LIKE ? OR name LIKE ? OR contactemail LIKE ? OR city LIKE ?' , "%#{term}%", "%#{term}%", "%#{term}%","%#{term}%" ).paginate(page: params[:page]).order('id DESC')
else
logger.debug "*** Running search --> get em all "
#companies = Company.all.paginate(page: params[:page], per_page: 5).order('id DESC')
end
end
This works fine but I'm guessing there is a much simplier way. I'd prefer not to modify the query every time I add an attribute. Any ideas on how to improve this?
You could go with something like this (It is just an example, might be a better way, but it is fonctionnal)
# company.rb
def self.search_all_properties term
query = ''
properties = Company.column_names
properties.each_with_index do |prop, index|
if index < properties.count - 1
query = query + prop + ' LIKE :term OR '
else
query = query + prop + ' LIKE :term'
end
end
term = "%" + term + "%"
Company.where(query, :term => term)
end
and use it like this
Company.search_all_properties term

I need a search form with a large number of fields. How do I do this cleanly in Ruby on Rails?

I'm building a database application that tracks a lot of data for a Person, such as first_name, last_name, DOB, and 20+ more fields.
Users will need to be able to search for all of these fields. I'm having trouble writing clean code for this. The code below is what I have so far in my people_controller:
The data is submitted from a form_tag
def search
#people = Person.all
general_info_string = String.new
if(params[:first_name] != "") then general_info_string << 'people.first_name = "' + params[:first_name] + '" AND ' end
if(params[:last_name] != "") then general_info_string << "people.last_name = '" + params[:last_name] + "' AND " end
... Lots more of similar clauses
general_info_string = general_info_string[0, general_info_string.length - 5]
# ^This line removes the trailing " AND " from the string
#people = #people.where(general_info_string)
end
general_info_string is so called because there are more "where" clauses(not shown) and separate strings that I build to search for them.
The problem here is that the code looks like a mess and seems like a "hacky" way to do something that should be well supported by Rails. How could I perform this operation in a cleaner way?
That's not just hacky - it leaves you open to a string injection attack.
You need a :conditions using the ? template, like this example:
:conditions => ["key1 = ?", var]
but you need to make the template part into a string that grows once per parameter, and you need to make the var into an array that grows with each parameter's value. That gives you something like this:
template = []
values = []
if params[:first_name].present?
template.push 'people.first_name = ?'
values.push params[:first_name]
end
if params[:last_name].present?
template.push 'people.last_name = ?'
values.push params[:last_name]
end
template = template.join(' AND ')
:conditions => [template, *values]
From there, you should DRY up all those ifs, such as with a table of value keys. Then you'd loop through the table, check the key, and push its results into the arrays:
fields = [:first_name, :last_name, :shoe_size, ...]
fields.each do |field|
if params[field].present?
template.push "people.#{field} = ?"
values.push params[field]
end
end

How can I convert that to MetaWhere or Arel?

Consider a City Model having:
def self.search(field, search)
if search
where("#{field} LIKE ?", "%#{search}%")
else
scoped
end
end
How can I use Arel or Metawhere in that situation knowing that field is a String can have anything like:
"name"
"residents.name"
"state.name"
I want to do something like that (will not work):
def self.search(field, search)
if search
where(field =~ "%#{search}%")
else
scoped
end
end
So, what are your thoughts?
The real question is, how can I convert that:
"residents.name LIKE '#{value}%'"
To that:
:residents => { :name =~ "#{value}%" }
You should be able to use Arel like this.
def self.search(field, search)
if search
if field =~ /\./ # table and field
table, field = field.split('.')
arel_join = table.singularize.camelize.constantize.arel_table
joins(table.to_sym).where(arel_join[field].matches("%#{search}%"))
else
where(Resource.arel_table[field].matches("%#{search}%"))
end
else
scoped
end
end
There's a Railscast that does a good job of explaining the basics of using Arel in Rails 3.

Optional mix of filter parameters in a search the Rails way

I've got a simple list page with a couple of search filters status which is a simple enumeration and a test query which I want to compare against both the title and description field of my model.
In my controller, I want to do something like this:
def index
conditions = {}
conditions[:status] = params[:status] if params[:status] and !params[:status].empty?
conditions[???] = ["(descr = ? or title = ?)", params[:q], params[:q]] if params[:q] and !params[:q].empty?
#items = Item.find(:all, :conditions => conditions)
end
Unfortunately, it doesn't look like I can mix the two types of conditions (the hash and the paramatized version). Is there a "Rails Way" of doing this or do I simply have to do something awful like this:
has_status = params[:status] and !params[:status].empty?
has_text = params[:q] and !params[:q].empty?
if has_status and !has_text
# build paramatized condition with just the status
elsif has_text and !has_status
# build paramatized condition with just the text query
elsif has_text and has_status
# build paramatized condition with both
else
# build paramatized condition with neither
end
I'm migrating from Hibernate and Criteria so forgive me if I'm not thinking of this correctly...
Environment: Rails 2.3.4
You can mix hash and array conditions using scopes:
hash_conditions = {}
# build hash_conditions
items_scope = Item.scoped(:conditions => hash_conditions)
unless params[:q].blank?
items_scope = items_scope.scoped(:conditions => ["(descr = ? or title = ?)", params[:q], params[:q]])
end
...
items = items_scope.all
So you can mix and match any types of conditions, and the query will be executed only when you do items_scope.all
a=[],b=[]
unless params[:status].blank?
a << "status = ?"
b << params[:status]
end
unless params[:q].blank?
a << "(descr = ? or title = ?)"
b << params[:q] << params[:q]
end
#items = Item.all( :conditions => [a.join(" AND "), b] )
A better search on my part turned up something called "named scopes" which looks like is exactly what I'm looking for. I'm about to see if it will work with the will_paginate gem....
Reference:
http://edgerails.info/articles/what-s-new-in-edge-rails/2010/02/23/the-skinny-on-scopes-formerly-named-scope/

Resources