How to use select....as and concat in rails 5 - ruby-on-rails

I have a table called Student, the student table contains the id, first_name and last_name. I am trying to select and concatenate first_name and last_name and display the column as "Name". This is my query:
Student.select("concat(first_name, ' ', last_name) as 'Name'").find(201410204)
but it returns
SELECT concat(first_name, ' ', last_name) as 'Name' FROM `students` WHERE `students`.`id` = 201410204 LIMIT 1
#<Student id: nil>
but when i try to paste the query inside mysql workbench it return the student name that has the id 201410204 and the column name is "Name".
Click to see the result of the query
What's the problem about my code? Thanks

You need to select the id as well:
Student.select(:id, "CONCAT(first_name,' ',last_name) as name").find(201410204)
2.4.1 :073 > Student.select(:id, "CONCAT(first_name,' ',last_name) as name").find(23000)
Student Load (0.9ms) SELECT "students"."id", CONCAT(first_name,' ',last_name) as name FROM "students" WHERE "students"."id" = $1 LIMIT $2 [["id", 23000], ["LIMIT", 1]]
=> #<Student id: 23000, name: "Joe Blow">

Let me give you two solutions, one by using the power of rails and other using the scalability of rails.
1) (Using power of rails)
In the Student model create a method named full_name and concatenate first_name and last_name.
class Student < ActiveRecord::Base
.....
def full_name
"#{try(:first_name)} #{try(:last_name)}".to_s
end
.....
end
Now open console or any where you need,
student = Student.find(201410204)
student.full_name
thats it, you will get the full name.
2) Using Scalability of rails, even can execute SQL queries.
student = ActiveRecord::Base.connection.exec_query("SELECT CONCAT(first_name,' ', last_name) FROM students where students.id = 201410204")
It returns an array,now you can retrieve using,
student.rows[0]
Thats it, you will get the same result.

One solution
In model Create A method
def full_name
"#{self.try(:first_name)} #{self.try(:last_name)}"
end
Now
Student.find(121).full_name

With select you restrict the returned columns. Your query returns only the Column Name. id and all other columns are missing. In this case you get an instance of student where all attributes are nil. Only the attribute Name is set. When you try Student.select("concat(first_name, ' ', last_name) as 'Name'").find(201410204).Name you will see the right result.
When you want to get all attributes you have to extend your select like this Student.select("concat(first_name, ' ', last_name) as 'Name', *").find(201410204)
Edit:
I did check it for mysql on https://de.piliapp.com/mysql-syntax-check/, I don't know why but you have to name * first.
Student.select("*, concat(first_name, ' ', last_name) as 'Name'").find(201410204)

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.

RoR Filter Out Current User with Join, Select and Order

I have the following statement in an RoR controller called nominations:
#users = User.joins(:department).select("CONCAT(last_name, ', ', first_name,
' - ', name) as user_dept, last_name, first_name, name,
users.id").order("last_name, first_name, middle_name")
In the view, I have this to put this in a drop down:
<%= select_tag "nomination[user_id]", options_from_collection_for_select(#users,
:id, :user_dept), prompt: "Select User" %>
I want to filter out the current user so that someone can't nominate themselves. I've seen several articles about using where.not or scope (for example) but all of the statements I could find are basic selections from one table. How do I define and use a scope while preserving all of the other stuff? I need that join and formatting.
Edit - Whenever I try where.not anywhere in the controller or model, I get the error "wrong number of arguments (0 for 1)".
This should do the trick:
#users = ...
#users = #users.where.not(users: {id: current_user.id})
Note that you need to specify name of the table (not the model) when you do the join, otherwise database will have no idea which id column it should look for (users.id vs departments.id).
Before Rails 4
not method is quite a new thing and is not available in Rails 3. (In fact, where method wasn't expecting to be called without arguments, that's why the error you got is unexpected number of arguments (0 for 1)).
Those were dark times when we had to use the following monstrosity to get stuff working:
#users = ...
#users = #users.where('users.id != ?', current_user.id)
You need a scope in the User model:
class User < ActiveRecord::Base
scope :except_id, -> (id) ( where.not(id: id) )
end
Then you can freely use it everywhere you want:
#users = User.except_id(current_user.id).joins(:department).select("...")
#users = User.where.not(id: current_user.id).joins(:department)
.select("CONCAT(last_name, ', ', first_name, ' - ', name) as user_dept, last_name, first_name, name, users.id")
.order("last_name, first_name, middle_name")

How to pass numbers in an array into a SQL statement

So I am attempting to do a SQL query where I need to provide a lot of user id's.
Currently what I am trying to do is this:
following_ids = $redis.smembers(self.redis_key(:following))
Medium.includes([{comments: :user}, :likes, :user]).where("user_id IN (#{ following_ids.each { |id| id } }) OR user_id = :user_id", user_id: id)
Now the following_ids is in this form: ["1", "2", "3"], containing user id's
But the loop I try to do seems to still put the whole array into the SQL statement. I can see this from my server log:
Medium Load (0.3ms) SELECT "media".* FROM "media" WHERE (user_id IN (["2", "3"]) OR user_id = 1)
PG::SyntaxError: ERROR: syntax error at or near "["
LINE 1: SELECT "media".* FROM "media" WHERE (user_id IN (["2", "3"])...
What am I doing wrong here?
You don't need to complicate things that much. You could simply do
user_ids = $redis.smembers(self.redis_key(:following)) << id
Medium.includes([{comments: :user}, :likes, :user]).where(user_id: user_ids)
Try changing the interpolated code to:
following_ids.to_s.tr("[]", "")
So in total:
Medium.includes([{comments: :user}, :likes, :user]).where("user_id IN ( #{following_ids.to_s.tr("[]", "") }) OR user_id = :user_id", user_id: id)
UPDATE: See Kaspar's comment below for the preferred 'active record way' to do this.

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 SQL query builder... Or ActiveRecord query builder

I need to run sql query like
sql = 'SELECT * FROM users WHERE id != ' + self.id.to_s + ' AND id NOT IN (SELECT artner_id FROM encounters WHERE user_id = ' + self.id.to_s + ')'
sql += ' AND id NOT IN (SELECT user_id FROM encounters WHERE partner_id = ' + self.id.to_s + ' AND predisposition = ' + Encounter::Negative.to_s + ')'
sql += ' AND cfg_sex = ' + self.sex.to_s + ' AND cfg_country = ' + self.country.to_s + ' AND cfg_city = ' + self.city.to_s
sql += ' ORDER BY rand() LIMIT 1'
It can be executed by AR.find_by_sql, but the code before is bad readable.
Are there any query builder, which can build that query?
For example, Kohana (it is PHP framework, I am php developer, but I want to change that kid-language to ruby/rails) have a query builder, which works like this:
$sql = DB::select('*')->from('users');
$sql->where('id', 'NOT_IN', DB::expr('SELECT partner_id FROM encounters WHERE user_id = '.$user->id));
$sql->where('id', 'NOT_IN', DB::expr('SELECT user_id FROM encounters WHERE partner_id = '.$user->id.' AND predisposition = '.Encounter::Negative));
....
etc
...
Query which was builded with query builder like a Kohana query builder is more readable and understandable.
Are there any gem to solve this problem?
You need the squeel gem. It extends AR with blocks and makes very complicated queries with ease.
Just few features:
# not_in == cool! )
Product.where{id.not_in LineItem.select{product_id}}
# SELECT "products".* FROM "products" WHERE "products"."id" NOT IN
# (SELECT "line_items"."product_id" FROM "line_items" )
# outer joins on pure Ruby:
LineItem.joins{product.outer}
# LineItem Load (0.0ms) SELECT "line_items".* FROM "line_items"
# LEFT OUTER JOIN "products" ON "products"."id" = "line_items"."product_id"
# calcs, aliasing:
Product.select{[avg(price).as(middle)]}
# SELECT avg("products"."price") AS middle FROM "products"
# comparison
Product.where{id != 100500}
Product.where{price<10}
# logical OR
Product.where{(price<10) | (title.like '%rails%')}
# SELECT "products".* FROM "products" WHERE (("products"."price" < 10 OR
# "products"."title" LIKE '%rails%'))
# xxx_any feature (also available xxx_all)
Product.where{title.like_any %w[%ruby% %rails%]}
# SELECT "products".* FROM "products" WHERE (("products"."title" LIKE '%ruby%' OR
# "products"."title" LIKE '%rails%'))
Note the using blocks: {...} here aren't hashes. Also note the absence of symbols.
If you decide to pick it, read the section that starts with "This carries with it an important implication"
There's a ruby library that utilizes relational algebra. It is called ARel. If you are using Rails 3.x, then you already have.
ids = Partner.where(user_id: self.id).pluck(:partner_id) << self.id
users = User.where("id NOT IN #{ ids.join(',') }")
Here's the same query cast into rails AREL terms. It's not pretty yet -- it's a complicated query in general.
User.where("id = ? AND "
"id NOT IN (SELECT artner_id FROM encounters WHERE user_id = ?) AND " +
"id NOT IN (SELECT user_id FROM encounters WHERE partner_id = ? AND predisposition = ? ) AND " +
"cfg_sex = ? AND cfg_country = ? AND cfg_city = ?)",
self.id, self.id, self.id, Encounter::Negative,
self.sex, self.country, self.city).order(" rand() ").limit(1)
(I've not tested this, so it's possible there could be typo's in it.)
I'd recommend a couple things:
When you have complex where clauses they can be chained together and AREL will put them back together generally pretty well. This allows you to use scopes in your model classes and chain them together.
For example, you could do this:
class User < ActiveRecord::Base
def self.in_city_state_country(city, state, country)
where("cfg_sex = ? AND cfg_country = ? AND cfg_city = ?", city, state, country)
end
def self.is_of_sex(sex)
where("cfg_sex = ?", sex)
end
end
Then you could rewrite these portions of the query this way:
User.is_of_sex(user.sex).in_city_state_country(user.city, user.state, user.country)
and so on.
Breaking the queries down into smaller parts also makes it easier to test specific pieces of it with your rspecs. It results in more modular, maintainable code.
For more details, check out the Rails Guide - Active Record Query Interface

Resources