I am trying to do a like query like so
def self.search(search, page = 1 )
paginate :per_page => 5, :page => page,
:conditions => ["name LIKE '%?%' OR postal_code like '%?%'", search, search], order => 'name'
end
But when it is run something is adding quotes which causes the sql statement to come out like so
SELECT COUNT(*)
FROM "schools"
WHERE (name LIKE '%'havard'%' OR postal_code like '%'havard'%')):
So you can see my problem.
I am using Rails 4 and Postgres 9 both of which I have never used so not sure if its and an activerecord thing or possibly a postgres thing.
How can I set this up so I have like '%my_search%' in the end query?
Your placeholder is replaced by a string and you're not handling it right.
Replace
"name LIKE '%?%' OR postal_code LIKE '%?%'", search, search
with
"name LIKE ? OR postal_code LIKE ?", "%#{search}%", "%#{search}%"
Instead of using the conditions syntax from Rails 2, use Rails 4's where method instead:
def self.search(search, page = 1 )
wildcard_search = "%#{search}%"
where("name ILIKE :search OR postal_code LIKE :search", search: wildcard_search)
.page(page)
.per_page(5)
end
NOTE: the above uses parameter syntax instead of ? placeholder: these both should generate the same sql.
def self.search(search, page = 1 )
wildcard_search = "%#{search}%"
where("name ILIKE ? OR postal_code LIKE ?", wildcard_search, wildcard_search)
.page(page)
.per_page(5)
end
NOTE: using ILIKE for the name - postgres case insensitive version of LIKE
While string interpolation will work, as your question specifies rails 4, you could be using Arel for this and keeping your app database agnostic.
def self.search(query, page=1)
query = "%#{query}%"
name_match = arel_table[:name].matches(query)
postal_match = arel_table[:postal_code].matches(query)
where(name_match.or(postal_match)).page(page).per_page(5)
end
ActiveRecord is clever enough to know that the parameter referred to by the ? is a string, and so it encloses it in single quotes. You could as one post suggests use Ruby string interpolation to pad the string with the required % symbols. However, this might expose you to SQL-injection (which is bad). I would suggest you use the SQL CONCAT() function to prepare the string like so:
"name LIKE CONCAT('%',?,'%') OR postal_code LIKE CONCAT('%',?,'%')", search, search)
If someone is using column names like "key" or "value", then you still see the same error that your mysql query syntax is bad. This should fix:
.where("`key` LIKE ?", "%#{key}%")
Try
def self.search(search, page = 1 )
paginate :per_page => 5, :page => page,
:conditions => ["name LIKE ? OR postal_code like ?", "%#{search}%","%#{search}%"], order => 'name'
end
See the docs on AREL conditions for more info.
.find(:all, where: "value LIKE product_%", params: { limit: 20, page: 1 })
Related
I am very new to Rails and am trying to create a search field to query my database. When I use the WHERE method and use "like", it works, but it's far too broad. If a person's last name is Smith, and I search for "i", it will return Smith as that string contains an "i". How can I make it more specific? I've searched around, read the docs and thought the following should work, but it doesn't return records.
this works
def self.search(query)
where("last_name like ? OR email like ?", "%#{query}%", "%#{query}%")
end
this does not
def self.search(query)
where("last_name = ? OR email = ?", "%#{query}%", "%#{query}%")
end
You're going to want to look more into SQL, as this is very specifically an SQL question. The % operator in SQL returns a fuzzy match. You want a strict match. So
def self.search(query)
where("last_name = ? OR email = ?", query, query)
end
or more succinctly:
scope :search, ->(query) { where('last_name = :query OR email = :query', query: query) }
My sql in rails
#search = #search.where('txt_1 OR txt_2 OR keywords like ?', some_value)
Why this doesn't work. When I have only " txt_1 OR txt_2 " - i think it works ok. But when I add next OR, there is no result when some_value doesn't exist in last OR.
ANSWER:
ok as I see, OR is condition that return true for one OR/AND second variable. I make this like that, and it works - I don't know is this a good solution
#items.where(' (txt_1 OR txt_2 like ?) OR (keywords like ?) ', "%#{search_name}%","%#{search_name}%")
The OR statement in SQL has to be used to compare different assertion, like something = 'something' OR other_thing = 'other_stuff'.
In your case, you should use the OR keyword as the following:
#items.where('txt_1 LIKE ? OR txt_2 LIKE ? OR keywords LIKE ?', "%#{search_name}%","%#{search_name}%", "%#{search_name}%")
But since you use the same value (search_name), you can use this pretty usefull syntax:
#items.where('txt_1 LIKE :search OR txt_2 LIKE :search OR keywords LIKE :search', search: "%#{search_name}%")
Im trying to pull all records from a Project model that includes in the project_name the word 'Fox'. I can do an active record search and return specific project_names, like 'Brown Fox':
#projects = Project.where("project_name like ?", "Brown Fox")
But if I want to return all the names that INCLUDE 'Fox', this does not work unless the complete project name is 'Fox':
#projects = Project.where("project_name like ?", "Fox")
How do I do a search that returns all the objects with the word 'Fox' in the name?
Try using:
variable = "Fox"
Project.where("project_name like ?", "%#{variable}%")
You can use the SQL % operator:
#projects = Project.where("project_name like ?", "%Fox%")
Note that if you want your query to return results ignoring the word case, you can use PostgreSQL ilike instead of like.
Did you try ransack ?
With ransack you can do something like
#projects = Project.search(:project_name_cont => "Fox")
If you think it is too much for what you need. you can use the % operator as MurifoX said
Here's a version that will allow you to handle any number of input words and to search for all of them within a name. I was looking for this answer and didn't find the more complicated case, so here it is:
def self.search(pattern)
if pattern.blank? # blank? covers both nil and empty string
all
else
search_functions = []
search_terms = pattern.split(' ').map{|word| "%#{word.downcase}%"}
search_terms.length.times do |i|
search_functions << 'LOWER(project_name) LIKE ?'
end
like_patterns = search_functions.join(' and ')
where("#{like_patterns}", *search_terms)
end
end
I have a method in my User model:
def self.search(search)
where('last_name LIKE ?', "%#{search}%")
end
However, it would be nice for my users to be able to search for both first_name and last_name within the same query.
I was thinking to create a virtual attribute like this:
def full_name
[first_name, last_name].join(' ')
end
But is this efficient on a database level. Or is there a faster way to retrieve search results?
Thanks for any help.
Virtual attribute from your example is just class method and cannot be used by find-like ActiveRecord methods to query database.
Easiest way to retrive search result is modifying Search method:
def self.search(search)
q = "%#{query}%"
where("first_name + ' ' + last_name LIKE ? OR last_name + ' ' + first_name LIKE ?", [q, q])
end
where varchar concatenation syntax is compatible with your database of choice (MS SQL in my example).
The search functionality, in your example, is still going to run at the SQL level.
So, to follow your example, your search code might be:
def self.search_full_name(query)
q = "%#{query}%"
where('last_name LIKE ? OR first_name LIKE ?', [q, q])
end
NOTE -- these sorts of LIKE queries, because they have a wildcard at the prefix, will be slow on large sets of data, even if they are indexed.
One way this can be implemented is by tokenizing (splitting) the search query and creating one where condition per each token:
def self.search(query)
conds = []
params = {}
query.split.each_with_index do |token, index|
conds.push "first_name LIKE :t#{index} OR last_name LIKE :t#{index}"
params[:"t#{index}"] = "%#{token}%"
end
where(conds.join(" OR "), params)
end
Also make sure you prevent SQL injection attacks.
However, it's better to use full-text searching tools, such as ElasticSearch and its Ruby gem named Tire to handle searches.
EDIT: Fixed the code.
A scope can be made to handle complex modes, here's an example from one project I'm working on:
scope :search_by_name, lambda { |q|
if q
case q
when /^(.+),\s*(.*?)$/
where(["(last_name LIKE ? or maiden_name LIKE ?) AND (first_name LIKE ? OR common_name LIKE ? OR middle_name LIKE ?)",
"%#{$1}%","%#{$1}%","%#{$2}%","%#{$2}%","%#{$2}%"
])
when /^(.+)\s+(.*?)$/
where(["(last_name LIKE ? or maiden_name LIKE ?) AND (first_name LIKE ? OR common_name LIKE ? OR middle_name LIKE ?)",
"%#{$2}%","%#{$2}%","%#{$1}%","%#{$1}%","%#{$1}%"
])
else
where(["(last_name LIKE ? or maiden_name LIKE ? OR first_name LIKE ? OR common_name LIKE ? OR middle_name LIKE ?)",
"%#{q}%","%#{q}%","%#{q}%","%#{q}%","%#{q}%"
])
end
else
{}
end
}
As you can see, I do a regex match to detect different patterns an build different searches depending on what is provided. As an added bonus, if nothing is provided, it returns an empty hash which effectively is where(true) and returns all results.
As mentioned elsewhere, the db cannot index the columns when a wildcard is used on both sides like %foo%, so this could potentially get slow on very large datasets.
Pls i have the following code in my model
Letter.count(:id, :conditions => ["language_id = #{lang} AND :created_at => '#{start_date.to_date.strftime('%Y-%m-%d')}'..'#{end_date.to_date.strftime('%Y-%m-%d')}' " ])
I am trying to get the count of letters.id of different letters between the given dates.
This is the error an having...
Please those anyone know what am doing wrong...Thanks
SQLite3::SQLException: near ">": syntax error: SELECT COUNT("letters"."id") FROM "letters" WHERE ("letters".language_id = 1) AND (language_id = 1 AND :created_at => '2011-05-01'..'2011-05-08
This can be much simplified. A couple points:
You don't use the :created_at => ... format within a string
You need to use between ? and ? for dates.
You don't need to manually strftime your dates, Rails will handle this automatically.
In Rails 3, the preferred way is to use where(...) instead of a :conditions Hash for your count(...).
You should probably use Rails' safe interpolation for your language_id field too
Letter.where("language_id = ? AND created_at between ? and ?", lang, start_date.to_date, end_date.to_date).count