Rails Queries Made By User - ruby-on-rails

I am building a rails app where users can post, must like a form. I want users to be able to search through the posts with a text field, and then all records will be returned where the post content is similar to the user query. For example, a user enters:
'albert einstein atomic bomb'
Then, I want a to run a query where every word is checked, along with the query itself. Something like:
query_result = Hash.new
query_result << Post.where('content ILIKE ?', "%albert%")
query_result << Post.where('content ILIKE ?', "%einstein%")
query_result << Post.where('content ILIKE ?', "%atomic%")
query_result << Post.where('content ILIKE ?', "%bomb%")
query_result << Post.where('content ILIKE ?', "%albert einstein atomic bomb%")
This will not work of course, but I hope you get the idea. Any and all input would be appreciated.

You can use sunspot gem for stuff like this. Once it's setup you can do searches like so
# Posts with the exact phrase "great pizza"
Post.search do
fulltext '"great pizza"'
end
# One word can appear between the words in the phrase, so "great big pizza"
# also matches, in addition to "great pizza"
Post.search do
fulltext '"great pizza"' do
query_phrase_slop 1
end
end
and more. See the info in the link.

What about something like this?
query = 'albert einstein atomic bomb'
sub_queries = query.split("\n") << query
query_results = []
sub_queries.each { |q| query_results << Post.where('content ILIKE ?', "%#{q}%") }
You probably need to flatten the query_results array and uniq to remove duplicates.

Related

Error in using WHERE for search function in Ruby on Rails

I made a controller to search something ,
but the result was weird:
My code:
def create
#word = searching_params[:word]
#searching = current_user.searchings.build(word: #word)
flash[:notice] = "New searching is performed!" if #searching.save
#users = User.where("firstname LIKE ? OR lastname LIKE ?", "%#{#word}%", "%#{#word}%")
#posts = Post.where("body LIKE ?", "%#{#word}%")
render :index
end
So, when i searched for a name: Mose Collins,
o, se, ose could get the result,
but m, c, co and others would give me nothing.
LIKE performs a case sensitive match. If you want to perform a case insensitive match in a somewhat polyglot fashion you can use the LOWER() SQL function:
#users = User.where("LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ?", "%#{#word.downcase}%", "%#{#word.downcase}%")
Postgres has a ILIKE function which is case insensitive:
#users = User.where("firstname ILIKE ? OR lastname ILIKE ?", "%#{#word.downcase}%", "%#{#word.downcase}%")
You can also use Arel to construct it instead of a SQL string:
class User < ApplicationRecord
has_many :favorite_jobs
def self.search(term)
where(
arel_table[:firstname].matches("#{name}").or(
arel_table[:lastname].matches("#{name}")
)
)
end
end
This approach is more portable and espcially shines if you want to built the query programatically.

Postgres Rails 4 search query id or multiple columns

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

Search query for (name or forename) and (name forname) and (forname name)

Im beginner in rails and have some problems to improve my search query:
In the controller i call:
def index
if params[:search]
#persons = Person.search(params[:search]).order("created_at DESC")
else
#persons = Person.order("created_at DESC")
end
end
And in the model i have:
def self.search(query)
where("name like ?", "%#{query}%")
end
So actually i only filter name! Now i tried to improve it but it didnt worked out how i liked it, my aim is that a user can type in for example:
John
Smith
Smith John
John Smith
and it always should return John Smith. So how do i write such a long sql query? Thanks in advance!
This search method should work for you:
def self.search(query)
return where('FALSE') if query.blank?
conditions = []
search_columns = [ :vorname, :nachname ]
query.split(' ').each do |word|
search_columns.each do |column|
conditions << " lower(#{column}) LIKE lower(#{sanitize("%#{word}%")}) "
end
end
conditions = conditions.join('OR')
self.where(conditions)
end
This code is secure: sanitizes the strings before calling the SQL
This code is flexible: you can very easily add more columns to search on
This code is flexible: you can easily split on more than just space (- / | etc)
This code can be used for chain-scoping
This code is case-insensitive, will work with either John Smith or JohN SMITh
Don't hesitate to ask questions if needed!
What about doing something like this?
def self.search(query)
where("name LIKE ? or forename LIKE ? or concat(name, ', ', forename) LIKE ?", "%#{query}%", "%#{query}%" , "%#{query}%")
end
If your search queries are getting really complex you might want to take a look at search gems, such as elasticsearch
What you have should work for the following search queries
John
Smith
John Smith
Do the following if you want it to work for reversed search queries also
def self.search(query)
name_parts = query.split(' ')
condition = name_parts.collect{|part| "name like #{sanitize("%#{part}%")}"}.join(' AND ')
where(condition)
end

Search multiple columns - Rails

I am currently writing a search method for my rails applications, and at the moment it works fine. I have the following in my game.rb:
def self.search(search)
if search
find(:all, :conditions => ['game_name LIKE ? OR genre LIKE ? OR console LIKE ?', "%#{search}%", "#{search}", "#{search}"])
else
find(:all)
end
end
Now that searches fine, but my problem is that if there is a record in game_name that has the word 'playstation' in it, it will finish the search there. It only returns that record, rather than all games that have 'playstation' stored in console. Now I understand this is because I have 'OR' in my conditions, but I don't know an alternative. 'AND' requires all the conditions to match or none return at all. What is an alternative I can use to AND and OR? Help would be much appreciated.
If there is a solution that has separate search boxes and entries, then that would be fine, I don't necessarily require the search to find it all based on one search form.
If I understand your question correctly, your SQL looks good to me for what you are trying to do. An OR clause will return all records that match in column1, column2, or column3. It doesn't stop at the first match. I do see an issue with your parameters in that the first you are using LIKE with % but in the second two you aren't, maybe that is where your issue is coming from.
Should this be your find (% around second and third search)?
find(:all, :conditions => ['game_name LIKE ? OR genre LIKE ? OR console LIKE ?', "%#{search}%", "%#{search}%", "%#{search}%"])
or better use DRY version (above will not work for Rails 4.2+):
Item.where('game_name LIKE :search OR genre LIKE :search OR console LIKE :search', search: "%#{search}%")
What if you have 15 columns to search then you will repeat key 15 times. Instead of repeating key 15 times in query you can write like this:
key = "%#{search}%"
#items = Item.where('game_name LIKE :search OR genre LIKE :search OR console LIKE :search', search: key).order(:name)
It will give you same result.
Thanks
I think this is a little bit of a cleaner solution. This allows you to add/remove columns more easily.
key = "%#{search}%"
columns = %w{game_name genre console}
#items = Item.where(
columns
.map {|c| "#{c} like :search" }
.join(' OR '),
search: key
)
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
I think this is a more efficient solution if you want to search an array of columns as I do.
First and most importantly you can add a private function to your model that creates a query template:
def self.multiple_columns_like_query(array)
array.reduce('') { |memo, x| #
unless memo == '' #
memo += ' or ' # This is the
end #
memo += "#{x} like :q" # core part
} #
end
Than you can use the function in your search function:
def self.search(query)
if fields = self.searched_fields && query
where multiple_like_query(fields), q: "%#{query}%"
end
end
Here you should also define self.searched_fields as an array of field names.

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

Resources