Include has_many records in query with Rails - ruby-on-rails-3

I have a has_many (through accounts) association between the model User and the model Player.
I would like to know what is the best way to query all the users, and for each returned record get the associated players usernames attributes (in comma separated values).
So, if for example, a User named 'John' is associated with 3 players with usernames 'john_black', 'john_white', 'john_yellow'. I would like the query to return not just the User attributes but also an attribute called, for example, player_username, that would have this value: john_black, john_white, john_yellow.
I have tried the following Arel query in the User model:
select(`users`.*).select("GROUP_CONCAT(`players`.`username` SEPARATOR ',') AS comma_username").joins(:user)
.joins(:accounts => :player )
Which gives me the following SQL:
SELECT `users`.*, GROUP_CONCAT(`players`.`username` SEPARATOR ',') AS comma_username FROM `users` INNER JOIN `accounts` ON `accounts`.`user_id` = `users`.`id` INNER JOIN `players` ON `players`.`id` = `accounts`.`player_id`
If I execute this in MySQL console it works, but if I try to fetch from the model, I don't get the comma separated values.
What am I missing?

I believe ActiveRecord maps all columns retrieved by the SQL query with all attributes you have on your model, which in most of the cases are exactly the same. Maybe if you create a virtual accessor on your model, ActiveRecord could map your virtual column to the virtual attribute.
class User
attr_accessible :player_username
...
Give it a try and see if this works.

Related

Count has_many records of query

If I have a Model with a has_many relationship, how can I retrieve all of the records that all of the records in my query point to?
Let's just say, buildings have a has_many relationship with rooms. Here's what I want to do:
Building.where(...query...).rooms.count
This is just an example. I might want to count them, or I might want an ActiveRecord of the rooms that belong to the buildings that match the query.
One way is this, but I'm wondering if there's a better way:
building_ids = Building.where(...query...).pluck(:id)
Room.where(building_id: building_ids).count
Using select instead of pluck will result in a single sql statement instead of two separate ones.
building_ids = Building.where(...).ids
Room.where(building_id: building_ids)
you can also use join
Room.joins(:building).where(building: { name: 'somename' })
I'd use the sum of the counter caches.
Add the counter cache colum in a migration:
add_column :buildings, :rooms_count, default: 0
Update the relation in Room:
belongs_to :building, counter_cache: true
Then you could do something like:
Building.sum(:rooms_count)
That'll avoid n+1 queries
More details here https://blog.appsignal.com/2018/06/19/activerecords-counter-cache.html

Rails/SQL: Using an array as a search parameter

In an ecommerce shop application I would like to retrieve all orders that match a first_name that was entered via a search form and where paid == true. The search form submits the search term via params Parameters: {"utf8"=>"✓", "search"=>"john", "commit"=>"Search"}. In the controller
#users = User.search(params[:search]) #returns all users with the matching first_name, e.g. 'john'
#order = Order.where('user_id = ? AND paid = ?', #users.ids, true )
The query in #order works just fine, if only one user is returned, e.g. only one user is named john. But if multiple users are named John, multiple user ids are returned and the error message ActiveRecord::StatementInvalid is returned. My understanding is that the query stops working once `#users.ids is an array with more than one value.
How do I structure the following query: for each user_id return all orders (user.orders) where paid equals true.
Models
user.rb
has_many :orders
order.rb
belongs_to :users
There are various ways to go about this.
You can use a JOIN as Nic Nilov suggests but this can be difficult if your #users query is built using scopes and you don't want to manually inline those scopes.
You could also use a subquery since ActiveRecord in Rails4 is smart enough do The Right Thing when you use a relation in a where, you just have to use the hash form of where:
#users = User.search(params[:search])
# #users should be a User::ActiveRecord_Relation now.
#orders = Order.where(:user_id => #users, :paid => true)
This will end up with SQL like:
select *
from orders
where paid = 't'
and user_id in (
select id
from users
where /* whatever `search` does... */
)
The advantage here is that you don't need to know what User.search does as long as it is returning an ActiveRecord relation.
If your #users is actually an array (of ids or whole User instances) then you'd do it exactly the same way:
# Suppose #users is an array of Users or an array of User ids...
#orders = Order.where(:user_id => #users, :paid => true)
and ActiveRecord will figure out what to do with your #users array without you having to do anything extra.
Instead of two queries, you should use a nested query with a WHERE IN clause
SELECT * from Order WHERE user_id IN (SELECT user_id FROM users WHERE first_name LIKE ?) AND paid = true
This should do:
Order.joins(:user).where(users: { name: params[:search] }, paid: true)
It generates a single query with an INNER JOIN:
SELECT "orders".*
FROM "orders"
INNER JOIN "users" ON "users"."id" = "orders"."user_id"
WHERE "users"."name" = 'Test User' AND "orders"."paid" = 't'

Rails / SQL Query finding most recent Event

I'm using Rails 4.2 and PostgreSQL 9.4.
I have a basic users, reservations and events schema.
I'd like to return a list of users and the most recent event they attended, along with what date/time this was at.
I've created a query that returns the user and the time of the most recent event. However I need to return the events.id as well.
My application does not allow a user to reserve two events with the same start time, however I appreciate SQL does not know anything about this and thinks there can be multiple events in the result. Hence I am happy for the query to return an appropriate event ID at random in the case of a hypothetical 'tie' for events.starts_at.
User.all.joins(reservations: :event)
.select('users.*, max(events.starts_at)')
.where('reservations.state = ?', "attended")
.where('events.company_id = ?', 1)
.group('users.id')
The corresponding SQL query is:
SELECT users.*, max(events.starts_at) FROM "users" INNER JOIN "reservations" ON "reservations"."user_id" = "users"."id" INNER JOIN "events" ON "events"."id" = "reservations"."event_id" WHERE (reservations.state = 'attended') AND (events.company_id = 1) GROUP BY users.id
The reservations table is very large so loading the entire set into Rails and processing it via Ruby code is undesirable. I'd like to perform the entire query in SQL if it is possible to do so.
My basic model:
User
has_many :reservations
Reservation
belongs_to :user
belongs_to :event
Event
belongs_to :company
has_many :reservations
The generic sql that returns data for the most recent event looks like this:
select yourfields
from yourtables
join
(select someField
, max(datetimefield) maxDateTime
from table1
where whatever
group by someField ) temp on table1.someField = temp.somefield
and table1.dateTimeField = maxDateTime
where whatever
The two "where whatever" things should be the same. All you have to do is adapt this construct into your app. You might consider putting the query into a stored procedure which you then call from your app.
I think your query should focus first to retrieve the most recent reservation.
SELECT MAX(`events.starts_at`),`events"."id`,`user_id` FROM `reservations` WHERE (reservations.state = 'attended')
Then JOIN the Users and Events.
Assuming the results will include every User and Event it may be more efficient to retrieve all users and events and store then in two arrays keyed by id.
The logic behind that is rather than a separate lookup into the user and events table for each resulting reservation by the db engine, it is more efficient to get them all in a single query.
SELECT * FROM Users' WHERE 1 ORDER BYuser_id`
SELECT * FROM Events' WHERE 1 ORDER BYevent_id`
I am not familiar with Rails syntax so cannot give exact code but can show using it in PHP code, the results are put into the array with a single line of code.
while ($row = mysql_fetch_array($results, MYSQL_NUM)){users[$row(user_id)] = $row;}
Then when processing the Reservations you get the user and event data from the arrays.
The Index for reservations is critical and may be worth profiling.
Possible profile choices may be to include and exclude 'attended' in the Index. The events.starts_at should be the first column in the index followed by user_id. But profiling the Index's column order should be profiled.
You may want to use a unique Index to enforce the no duplicate reservations times.

Rails ActiveRecord query where relationship does not exist based on third attribute

I have an Adventure model, which is a join table between a Destination and a User (and has additional attributes such as zipcode and time_limit). I want to create a query that will return me all the Destinations where an Adventure between that Destination and the User currently trying to create an Adventure does not exist.
The way the app works when a User clicks to start a new Adventure it will create that Adventure with the user_id being that User's id and then runs a method to provide a random Destination, ex:
Adventure.create(user_id: current_user.id) (it is actually doing current_user.adventures.new ) but same thing
I have tried a few things from writing raw SQL queries to using .joins. Here are a few examples:
Destination.joins(:adventures).where.not('adventures.user_id != ?'), user.id)
Destination.joins('LEFT OUTER JOIN adventure ON destination.id = adventure.destination_id').where('adventure.user_id != ?', user.id)
The below should return all destinations that user has not yet visited in any of his adventures:
destinations = Destination.where('id NOT IN (SELECT destination_id FROM adventures WHERE user_id = ?)', user.id)
To select a random one append one of:
.all.sample
# or
.pluck(:id).sample
Depending on whether you want a full record or just id.
No need for joins, this should do:
Destination.where(['id not in ?', user.adventures.pluck(:destination_id)])
In your first attempt, I see the problem to be in the usage of equality operator with where.not. In your first attempt:
Destination.joins(:adventures).where.not('adventures.user_id != ?'), user.id)
you're doing where.not('adventures.user_id != ?'), user.id). I understand this is just the opposite of what you want, isn't it? Shouldn't you be calling it as where.not('adventures.user_id = ?', user.id), i.e. with an equals =?
I think the following query would work for the requirement:
Destination.joins(:adventures).where.not(adventures: { user_id: user.id })
The only problem I see in your second method is the usage of destinations and adventures table in both join and where conditions. The table names should be plural. The query should have been:
Destination
.joins('LEFT OUTER JOIN adventures on destinations.id = adventures.destination_id')
.where('adventures.user_id != ?', user.id)
ActiveRecord doesn't do join conditions but you can use your User destinations relation (eg a has_many :destinations, through: adventures) as a sub select which results in a WHERE NOT IN (SELECT...)
The query is pretty simple to express and doesn't require using sql string shenanigans, multiple queries or pulling back temporary sets of ids:
Destination.where.not(id: user.destinations)
If you want you can also chain the above realation with additional where terms, ordering and grouping clauses.
I solved this problem with a mix of this answer and this other answer and came out with:
destination = Destination.where
.not(id: Adventure.where(user: user)
.pluck(:destination_id)
)
.sample
The .not(id: Adventure.where(user: user).pluck(:destination_id)) part excludes destinations present in previous adventures of the user.
The .sample part will pick a random destination from the results.

Rails3 joins not including joined columns

I've got two related ActiveObject models:
class Product < ActiveRecord::Base
belongs_to :product_type
#contains a field called name
end
class ProductType < ActiveRecord::Base
has_many :products
# contains a field called name
end
If I join both models using their relationship, it all works well:
Product.joins(:product_type)
"SELECT \"products\".* FROM \"products\" INNER JOIN \"product_types\" ON \"product_types\".\"id\" = \"products\".\"product_type_id\""
But now I need to get the Products that have an specific ProductType, and what I know is the product type is its name (each product type's name is unique), so I've tried using conditions in the joined tables, as the Rails guides suggests:
Product.joins(:product_type).where(:product_type => {:name => "MyProductTypeUniqueName"})
which produces the following SQL:
"SELECT \"products\".* FROM \"products\" INNER JOIN \"product_types\" ON \"product_types\".\"id\" = \"products\".\"product_type_id\" WHERE \"product_type\".\"name\" = 'MyProductTypeUniqueName'"
Unfortunately it fails with the following error:
ActiveRecord::StatementInvalid: PG::Error: ERROR: missing FROM-clause entry for table "product_type"
The error seems to happen because the product_type columns are not included in FROM clause when the SQL is generated ( at least the 'product_type.name' column isn't)
Using PostgreSQL and Rails 3.2.3
SQL results are provided using the to_sql method for the ActiveRecord::Relation object instance returned
I know that I can make a join directly providing the sql string but this should work as it is an example almost equal to the one in the rails guides.
What am I doing wrong?
EDIT: Both models contains a field called 'name'. Included in case this is the reason the SQL is not building correctly.
try this,
Product.joins(:product_type).where(:table_name => {:name => "MyProductTypeUniqueName"})
where :table_name is the name of the table for :product_type in the databse