Rails activerecord LIKE AND clause error - ruby-on-rails

Here is an activerecord query i'm trying to use in rails
q = "Manchester"
b = "John Smith"
Model.find(:all, :conditions => ["city ? AND name like ?", q, b])
but i get this error in rails console
ActiveRecord::StatementInvalid: SQLite3::SQLException: near "'Manchester'": syntax error: SELECT "model".* FROM "model" WHERE (city 'Manchester' AND name like 'John Smith')
Please help!

You missed LIKE for city.
Model.where('city LIKE ? AND name LIKE ?', "%#{q}%", "%#{b}%");

You can also use this syntax which is a lot more readable than trying to figure out which ? goes with which variable. I mean if you have 1 or 2 it's fine, but once you have more it gets pretty ugly.
Model.where("city LIKE :city AND name LIKE :name", { city: "%#{q}%", name: "%#{b}%" })
The placeholders and hash key can be anything you like as long as they match (don't use :city and then hamster: in the hash key for example).
The nice thing about this is that you can also use one variable for multiple searches:
where("user LIKE :term OR email LIKE :term OR friends LIKE :term", { term: "%#{params[:term]}%"})

Try this:
Model.find(:all, :conditions => ["city = ? AND name like ?", q, b])

Related

Rails ActiveRecord - Search on Multiple Attributes

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}%")

Rails: Sorting Objects From Different Models

I've populated a hash with two different models. I then try to sort them like so:
#search_results = User.find(:all, :conditions => ['name LIKE ?', "%#{params[:query]}%"])
#search_results += Book.find(:all, :conditions => ['title LIKE ?', "%#{params[:query]}%"])
#search_results.sort! { |a,b| a.impressions_count <=> b.impressions_count }
This throws the following error:
comparison of User with Book failed
Both users and books have an integer-based impressions_count. Why can't I sort via this attribute? What other options do I have?
I faced a similar problem recently and ended up writing some custom sql because all other ways returned an array. Pretty sure its not a good idea to use the sort method since it will always be more efficient to sort in SQL than ruby, especially when the data set gets large
#combined_results = User.find_by_sql(["SELECT title, id, impressions_count, NULL as some_attribute_of_book
FROM user
WHERE title LIKE ?
UNION SELECT title, id, impressions_count, some_attribute_of_book FROM book
WHERE title LIKE ?
ORDER BY impressions_count", params[:query], params[:query]])
The above is completely untested code, more of an example than anything

find all :conditions id found in an array of values

I have 2 models (player and team linked through the model lnkteamplayer)
Team has_many players through lnkteamplayer
Player has_many teams through lnkteamplayer
I need to retrieve all players not belonging to a specific team.
<% #players = Player.find(:all, :conditions => ["id != ?",#team.lnkteamplayers.player_id ]) %>
I am getting an error with above line of code. My question is how do i pass an array of values in the above condition.
Thanks for any suggestion provided.
You've got a couple of problems there:
1) the first part of conditions, "id != ?", is a fragment of sql, and in sql you do "not equals" as <> not !=. Eg "id <> ?"
2) To use an array, the sql syntax is id in (1,2,3) or id not in (1,2,3). In your conditions you can do this like :conditions => ["id not in (?)", array_of_ids]
So, you could get players not on a team like this:
#team = Team.find(params[:team_id])
#not_on_team = Player.find(:all, :conditions => ["id not in (?)", #team.player_ids])
Since you haven't provided an error message, I am kind of guessing here. However, I don't think != is a valid syntax in many SQL dialects. You are probably looking for something like NOT IN () instead.
Also, #team.lnkteamplayers.player_id probably doesn't work since the value returned from #team.lnkteamplayers likely doesn't have a player_id method; you might want the ids of the actual players instead.
That can be done using something like #team.lnkteamplayer_ids.
All in all, your line probably needs to look like
<% #players = Player.find(:all, :conditions => ["id NOT IN (?)", #team.lnkteamplayer_ids]) %>
but without more information we can't say for sure.

How to do a LIKE query in Arel and Rails?

I want to do something like:
SELECT * FROM USER WHERE NAME LIKE '%Smith%';
My attempt in Arel:
# params[:query] = 'Smith'
User.where("name like '%?%'", params[:query]).to_sql
However, this becomes:
SELECT * FROM USER WHERE NAME LIKE '%'Smith'%';
Arel wraps the query string 'Smith' correctly, but because this is a LIKE statement it doesnt work.
How does one do a LIKE query in Arel?
P.S. Bonus--I am actually trying to scan two fields on the table, both name and description, to see if there are any matches to the query. How would that work?
This is how you perform a like query in arel:
users = User.arel_table
User.where(users[:name].matches("%#{user_name}%"))
PS:
users = User.arel_table
query_string = "%#{params[query]}%"
param_matches_string = ->(param){
users[param].matches(query_string)
}
User.where(param_matches_string.(:name)\
.or(param_matches_string.(:description)))
Try
User.where("name like ?", "%#{params[:query]}%").to_sql
PS.
q = "%#{params[:query]}%"
User.where("name like ? or description like ?", q, q).to_sql
Aaand it's been a long time but #cgg5207 added a modification (mostly useful if you're going to search long-named or multiple long-named parameters or you're too lazy to type)
q = "%#{params[:query]}%"
User.where("name like :q or description like :q", :q => q).to_sql
or
User.where("name like :q or description like :q", :q => "%#{params[:query]}%").to_sql
Reuben Mallaby's answer can be shortened further to use parameter bindings:
User.where("name like :kw or description like :kw", :kw=>"%#{params[:query]}%").to_sql
Don't forget escape user input.
You can use ActiveRecord::Base.sanitize_sql_like(w)
query = "%#{ActiveRecord::Base.sanitize_sql_like(params[:query])}%"
matcher = User.arel_table[:name].matches(query)
User.where(matcher)
You can simplify in models/user.rb
def self.name_like(word)
where(arel_table[:name].matches("%#{sanitize_sql_like(word)}%"))
end

Rails 3 - Using LIKE to search a combined 2 columns

I'm following ryan's Simple Search Form tutorial here:
http://railscasts.com/episodes/37-simple-search-form
I have the following line in my Users Model:
find(:all, :conditions => ['fname LIKE ?', "%#{search}%"])
But what I'd like to do is search across a combine 2 columns,: fname & lname
As users are searching my full names:
Example, James Brown
fname = James
lname = Brown
Is there a way to do this in Rails safely that will work across DBs like SQLite, MySQL or Postgres (heroku uses)?
Thanks!
It may not be pretty, but I use this in my Person model:
scope :by_full_name lambda {|q|
where("first_name LIKE ? or last_name LIKE ? or concat(last_name, ', ', first_name) LIKE ?", "%#{q}%", "%#{q}%" , "%#{q}%")
}
See one of my other posts for an bit extra that will let the search query be optional.
This ended up working extremely well... Not sure about performance though. Can Indexes Help This?
:conditions => ['fname || lname LIKE ?', "%#{search}%"]

Resources