In my application I have a customers model with three columns, first_name, middle_name and last_name. I have a method in the model that performs the search:
class Customer < ActiveRecord::Base
belongs_to :user
def self.search(search, user)
if search
.where('first_name LIKE ? OR middle_name LIKE ? OR last_name LIKE ?', "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%")
.where(user: user)
else
where(user: user)
end
end
end
The problem with this search function is that it only allows searching by one of the three columns at a time.
For example, a customer has a first_name of "foo", a middle_name of "bar" and a last_name of "baz". Searching for "foo", "bar", or "baz" individually returns results, but "foo bar" or "bar baz" does not.
What is the best way I can allow searching across all three columns?
You can concat your fields in the database query like
Updated:
.where("concat_ws(' ' , first_name, middle_name, last_name) LIKE ?", "%#{search}%")
This should work for foo, foo bar, or foo bar baz
but not foo baz
If you want to support foo baz as well then
.where("first_name LIKE ? OR middle_name LIKE ?"\
" OR last_name LIKE ? OR concat_ws(' ' , first_name, middle_name, last_name) LIKE ?"\
" OR concat_ws(' ' , first_name, last_name) LIKE ?",
"%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%")
.where("first_name LIKE ? OR middle_name LIKE ? OR last_name LIKE ? or CONCAT(first_name, middle_name, last_name) LIKE ?", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%")
If you use pg, you can try like this
def self.search(query)
where(['phone ilike :query',
'LOWER(name) ilike :query',
'LOWER(email) ilike :query',
'LOWER(address) ilike :query'].join(' OR '), {query: "%#{query}%" })
end
Related
I have these three tables as below
office.rb
has_many :documents
has_one :information
information.rb
belongs_to :office
document.rb
belongs_to :office
I am trying to write a query where as below
documents_controller.rb
def search
#documents = Document.all
#documents.joins(:office).where("offices.name ILIKE ?", "%#{params[:search]}%") OR #documents.joins(:office).joins(:information).where("informations.first_name ILIKE ? OR informations.last_name ILIKE ?", "%#{params[:search]}%", "%#{params[:search]}%")
end
I am trying to achieve the above statement but I am doing something wrong. Please help me fix this query
So, the idea is to retrieve any document where the office's name is the search term or where the information first/last name is the search term, right?
The first step is to create the joins:
Document.joins(office: :information)
The second step is to create the condition:
where("offices.name ILIKE :term OR informations.first_name ILIKE :term OR informations.last_name ILIKE :term", term: "%#{params[:search]}%")
and joining both sentences:
Document.joins(office: :information).where("offices.name ILIKE :term OR informations.first_name ILIKE :term OR informations.last_name ILIKE :term", term: "%#{params[:search]}%")
there are other fancy ways to do the same thing like using or scope but probably will be more complex to understand:
search_term = "%#{params[:search]}%"
base_query = Document.joins(office: :information)
office_scope = base_query.where("offices.name ILIKE :term", search_term)
first_name_scope = base_query.where("informations.first_name ILIKE :term", search_term)
last_name_scope = base_query.where("informations.last_name ILIKE :term", search_term)
office_scope.or(first_name_scope).or(last_name_scope)
I am working in an app with a basic search form with Heroku, but I can't get my sql query to work properly with PostgreSQL, even though this query worked with MySQL. By the way, I tried to paste the logs from Heroku, but it only says that when you search something it renders 500.html.
Here's my model OrdemDeServico with the search action:
def self.search(search)
if search
joins(:cliente).where("clientes.nome LIKE ? OR veiculo LIKE ? OR placa LIKE ? OR ordem_de_servicos.id = ?", "%#{search}%", "%#{search}%", "%#{search}%", "#{search}")
else
where(nil)
end
end
I just installed PostgreSQL locally, and it returned this error when searching:
`PG::InvalidTextRepresentation: ERROR: invalid input syntax for integer: "Augusto"
LINE 1: ... placa LIKE '%Augusto%' OR ordem_de_servicos.id = 'Augusto')
query:
SELECT "ordem_de_servicos".* FROM "ordem_de_servicos" INNER JOIN "clientes" ON "clientes"."id" = "ordem_de_servicos"."cliente_id" WHERE (clientes.nome LIKE '%Augusto%' OR veiculo LIKE '%Augusto%' OR placa LIKE '%Augusto%' OR ordem_de_servicos.id = 'Augusto') ORDER BY prazo LIMIT 5 OFFSET 0
I finally worked it out a solution. Those who have the same problem here's my code for the model:
def self.search(search)
if search
where("id = ?", search)
joins(:cliente).where("clientes.nome ilike :q or veiculo ilike :q or placa ilike :q", q: "%#{search}%")
else
where(nil)
end
end
I'm making a search-function in a Rails project with Postgres as db.
Here's my code
def self.search(search)
if search
find(:all, :conditions => ["LOWER(name) LIKE LOWER(?) OR LOWER(city) LIKE LOWER(?) OR LOWER(address) LIKE LOWER(?) OR (venue_type) LIKE (?)", "%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%"])
else
find(:all)
end
end
But my problem is that "venue_type" is an integer. I've made a case switch for venue_type
def venue_type_check
case self.venue_type
when 1
"Pub"
when 2
"Nattklubb"
end
end
Now to my question: How can I find something in my query when venue_type is an int?
Please try this code. Here I have added switch case to where clause.
def self.search(search)
if search
find(:all, :conditions => ["LOWER(name) LIKE LOWER(?) OR LOWER(city) LIKE LOWER(?) OR LOWER(address) LIKE LOWER(?) OR (venue_type = CASE WHEN 'pub' = ? THEN 1 WHEN 'nattklubb' = ? THEN 2 END)", "%#{search}%", "%#{search}%", "%#{search}%", "#{search.downcase}", "#{search.downcase}"])
else
find(:all)
end
end
I think you have to use if-else condition in your where clause something like following
find(:all, :conditions => ["LOWER(name) LIKE LOWER(?) OR LOWER(city) LIKE LOWER(?)
OR LOWER(address) LIKE LOWER(?) OR
IF(venue_type == 1, 'Pub', IF(venue_type == 2, 'Nattklubb2', '')) LIKE (?)",
"%#{search}%", "%#{search}%", "%#{search}%", "%#{search}%"])
If you are using rails 3.2 or greater, you should use where( [] ) instead for find.
where( [ "field like ?", field ] ).order(). ...
Also, when using pgsql why are you not using ilike instead of like with lower?
In your case while searching int or not int, I would use different queries.
When search.to_i != 0
I'm implementing a simple search function that should check for a string in either the username, last_name and first_name. I've seen this ActiveRecord method on an old RailsCast:
http://railscasts.com/episodes/37-simple-search-form
find(:all, :conditions => ['name LIKE ?', "%#{search}%"])
But how do I make it so that it searches for the keyword in name, last_name and first name and returns the record if the one of the fields matched the term?
I'm also wondering if the code on the RailsCast is prone to SQL injections?
Thanks a lot!
I assumed your model name is Model - just replace it with your real model name when you do the actual query:
Model.where("name LIKE ? OR last_name LIKE ? OR first_name LIKE ?", "%#{search}%","%#{search}%","%#{search}%")
About your worries about SQL injections - both of code snippets are immune to SQL injections. As long as you do not directly embed strings into your WHERE clause you are fine. An example for injection-prone code would be:
Model.where("name LIKE '#{params[:name]}'")
Although the selected answer will work, I noticed that it breaks if you try to type a search "Raul Riera" because it will fail on both cases, because Raul Riera is not either my first name or my last name.. is my first and last name... I solved it by doing
Model.where("lower(first_name || ' ' || last_name) LIKE ?", "%#{search.downcase}%")
With Arel, you can avoid writing the SQL manually with something like this:
Model.where(
%i(name first_name last_name)
.map { |field| Model.arel_table[field].matches("%#{query}%")}
.inject(:or)
)
This would be particularly useful if the list of fields to match against was dynamic.
A more generic solution for searching in all fields of the model would be like this
def search_in_all_fields model, text
model.where(
model.column_names
.map {|field| "#{field} like '%#{text}%'" }
.join(" or ")
)
end
Or better as a scope in the model itself
class Model < ActiveRecord::Base
scope :search_in_all_fields, ->(text){
where(
column_names
.map {|field| "#{field} like '%#{text}%'" }
.join(" or ")
)
}
end
You would just need to call it like this
Model.search_in_all_fields "test"
Before you start.., no, sql injection would probably not work here but still better and shorter
class Model < ActiveRecord::Base
scope :search_all_fields, ->(text){
where("#{column_names.join(' || ')} like ?", "%#{text}%")
}
end
The best way to do this is:
Model.where("attr_a ILIKE :query OR attr_b ILIKE :query", query: "%#{query}%")
I want to write a simple search method in my User model where it checks agisnt the Second name and first name and returns matching users. I have this at the moment but throws an error:
def self.search(search)
if search
where("first_name like ? or second_name like ?", "%#{search}%")
else
all
end
end
the error is: wrong number of bind variables (1 for 2) in: first_name like ? or second_name like ?
How can i fix this?
Thanks
You have two ? which means the where method is expecting two arguments:
def self.search(search)
if search
where("first_name like ? or second_name like ?", "%#{search}%", "%#{search}%")
else
all
end
end
I'm not sure if you can streamline those likes to use one argument instead of the duplicate two, but you could clean it up a little:
def self.search(search)
if search
q = "%#{search}%"
where("first_name like ? or second_name like ?", q, q)
else
all
end
end
You can use
where("first name like :name or second name like :name", :name => "%foo%")