String concatenation of two attributes in Rails where query - ruby-on-rails

Let's say I have the attrs first_name and last_name for a User. I know the user's full name is "Johnny Appleseed", as I have a large Hash of user's full names as strings.
Sure, I could split each string first, and search like so:
User.where(first_name: 'Johnny', last_name: "Appleseed")
BUT, I'm wondering if there is a way to basically concat the two in a query, essentially like so:
User.where('first_name + last_name = ?', 'Johnny Appleseed')
Or if I could have a full_name method on the User model, and search by that?
def full_name
"#{self.first_name} #{self.last_name}"
end
User.where('full_name = ?', 'Johnny Appleseed')

Probably the easiest solution would be:
User.where(%q(concat_ws(' ', first_name, last_name) = ?), 'Johnny Appleseed')

Related

Ruby on Rails query where equal to two concatenated columns

My customer model has a first_name and last_name field. I can query the model with the following code:
Customer.where(first_name: "John").where(last_name "Doe")
However, I want to query the model like this:
Customer.where(full_name: "John Doe")
But I do not have a full_name column. How can I accomplish this without creating a full_name field?
You can use the CONCAT operator in SQL:
Customer.where("CONCAT(first_name, ' ', last_name) = ?", full_name)
Which actually might make sense if you are creating something like a search function with like/ilike:
Customer.where("CONCAT(first_name, ' ', last_name) LIKE ?", "%#{full_name}%")
You can not, if you do not have a full name attribute you can not query by a full name and any try to do it using ActiveRecord in a single query will add an extra complexity to your app (or model), but to solve your question with simplicity you can add a scope to your Customer model, or a class method passing the first and last name as argument.
Using a scope
scope :by_full_name, (lambda { |first_name, last_name|
where(first_name: first_name, last_name: last_name)
})
using a static (class) method:
def self.by_full_name(first_name, last_name)
where(first_name: first_name, last_name: last_name)
end
Personally, due to simplicity and clearly code I prefer to use scopes instead of class methods if no extra logic is required.
As an alternative to Max, split the name and query on both fields as in your question.
scope :where_full_name, ->(full_name) do
(first_name, last_name) = full_name.split(/\s+/)
where first_name: first_name, last_name: last_name
end
Then use
Customer.where_full_name 'Betty Davis'
This won't work for names like Joan van der Graff.

How to execute Model Method name in where query?

I have users table that has first_name and last_name. On my user index page, there is a text field for first_name and last_name search.
If I search with either first_name or last_name it works fine. But if I enter the full name in the text field then it doesn't give me any results
My User model
def fullname
first_name + last_name
end
My Query
User.where("lower(first_name) like ? or lower(last_name) like ?", "%#{params[:fullname].downcase}%", "%#{params[:fullname].downcase}%")
Concatenate the two names when searching:
User.where(
"(LOWER(first_name) || ' ' || LOWER(last_name)) LIKE ?",
"%#{params[:fullname].downcase}%"
)

Search for substring in two columns

I currently have a scope:
scope :named,->(query) do
where("(first_name || last_name) ~* ?", Regexp.escape(query || "a default string to prevent Regexp.escape(nil) errors"))
end
This works, except for when first_name or last_name is nil. How do I find with these columns if one of the columns is nil?
I think you can try something like this:
scope :named,->(query) do
where("first_name IS NOT NULL OR last_name IS NOT NULL AND (first_name || last_name) ~* ?", Regexp.escape(query || "a default string to prevent Regexp.escape(nil) errors"))
end
This is a basic scope to search for a name, it handles a search for 'first_name only', 'last_name only' or a search for full name.
If you split the query into an array, you can grab the first and last of the array and does not matter whether the query is one or two in length.
You could replace the split with your regex if needed
You could obviously expand on this (e.g. case insensitive, ordering), but gives the basic idea.
Also handles a nil query by defaulting to a empty string, if the strip fails.
scope :named,->(query) do
query.strip! rescue query = ""
query_words = query.split(' ')
where(["first_name LIKE ? OR last_name LIKE ?", "#{query_words.first}%", "#{query_words.last}%"])
end
Hope this helps

Concatenation for search in Ruby on Rails

So, as part of a search function in a Ruby on Rails app, I've made Users searchable by first name and last name. However, when I type in their full name, it doesn't render any results. So, if a User is named John Smith, I can type in "John" or "Smith" and it will bring him up, but if I type in "John Smith" it doesn't recognize it.
I know that's because in my search code I only enabled first_name and last_name but not the User's full name. What's the proper way to concatenate the first and last names to solve this problem?
Here's the current code:
users = User.find_by_sql(['select *
from users
where first_name ilike ?
or last_name ilike ?
order by last_name limit ?', q, q, 100])
Check this code,
users = User.find_by_sql(["SELECT * FROM users WHERE (coalesce(users.first_name, '') || ' ' || coalesce(users.last_name, '') ilike '%#{p.downcase}%' )"])
What about mysql concat ?
users = User.find_by_sql(["SELECT * FROM users WHERE
CONCAT(first_name, ' ', last_name) LIKE ?
ORDER BY last_name LIMIT ?", p, 100]
)
where p could be anything John, Smith, John Smith

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

Resources