Preloading using includes on unrelated models - sql

Suppose I have three models, set up something like this:
class Student < ActiveRecord::Base
has_many :tests
has_many :cars
end
class Car < ActiveRecord::Base
belongs_to :student
end
class Test < ActiveRecord::Base
belongs_to :student
end
I want to query all tests whose student does not have car. Preloaded.
I've tried the following:
Test.includes(:cars) # does not work because the two are not associated
Test.joins('inner join cars ON tests.student_id = cars.student_id') # works, but it doesn't preload the Cars model in my result
I'd prefer not to create a has_many :through relationship, because they really aren't related at all, but I'm not opposed to it if that's the best solution.
Thoughts?
Rails 4.1.5
PostgreSQL 9.3.4
ruby 2.1.2

A join across three tables is an inefficient way to do this. Rails might even be smart enough to realise this and split it into seperate db queries.
I would do it like this, which has two simple queries instead
student_ids_with_car = Car.select("student_id").distinct
#tests = Test.where("student_id not in (?)", student_ids_with_car)

You don't have to use has_many :through to associations and associations of those associations at the same time.
Test.includes(:student => :cars)
Will include student's and their cars (by default it will preload, you can force a joins based include by using eager_load instead of preload).

Related

Rails includes with where condition

I have two models:
user.rb
class User < ApplicationRecord
has_many :orders, inverse_of: :user, dependent: :restrict_with_exception
end
order.rb
class Order < ApplicationRecord
belongs_to :user, inverse_of: :orders
end
I am using includes with where like this:
User.where(id: selected_salesmen.pluck(:id))
.includes(:orders)
.where("order.booked_at > ? AND order.booked_at < ?",
booked_at_gteq,
booked_at_lteq)
However, it's not giving me required users with orders. Any explanation of why this isn't working?
This may be some confusing between the methods includes and joins, which have related, but very different meanings.
includes will eager-load related records, which prevents multiple database calls later on. It's mostly used for performance tuning.
joins will include a join to the related table in your database query, which allows you to build conditions based on the related model.
Note that, to use joins, you need to refer to the table name, not the relation name. By default, ActiveRecord will connect to a table which is the pluralised name of the model.
So, change includes to joins and 'order' to 'orders':
User.where(id: selected_salesmen.pluck(:id))
.joins(:orders)
.where(
"orders.booked_at > ? AND orders.booked_at < ?",
booked_at_gteq,
booked_at_lteq
)
You may also want to check that selected_salesmen.pluck(:id) returns some ids, too.

How to sub-query on the "many" part in a one-to-many association?

I have a standard one-to-many association in two models:
class User < ActiveRecord::Base
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user
end
How do I query for messages that are last for users with name "John"?
I know I can use a joins to filter messages by user attributes. I am also convinced that the "last for that user" part of the query can be done with what is called an sql sub-query, but how exactly it is done is beyond me.
Message.joins(:user).where(users: {name: "John"}).<subquery?>

Complex user relationships in Rails/Devise app

Rails app using Devise for user authentication. Started simple, now getting quite complex.
There are 3 kinds of user that will be logging in to the site: Admin, Teacher, Parent. I have a single Devise User model and User.role string for these. There is a Student model (which doesn't log in) and a Lesson model.
These are the relationships I need:
Lesson belongs to one Student
Student has many Lessons
Student has many Teachers
Student belongs to one Parent
Parent has many Students
Teacher has many Students
Basic functionality of the site: Teachers log in and make lessons for their students. Parents log in and see all lessons for their kids (students). Admins log in and can CRUD teachers, parents and students.
I have most of it working but got stumped when I tried to implement 'student has many teachers' and 'parent has many students'. My initial (incorrect) assumptions were that a student only has one teacher and that a parent just has one child at the school.
So it seems that using a User.role string won't suffice for these relationships.
Any advice or general concepts would be greatly appreciated.
Are you just looking for a join table, ie a has_and_belongs_to_many relation?
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
As I see it:
class Student
belongs_to :parent
has_and_belongs_to_many :teachers
end
class Parent
has_many :students
end
class Teacher
has_and_belongs_to_many :students
end
There can be multiple ways of solving this, what i understand is that you have solved this much but you have made things bit complex.
you can simply have 1 model `User' and remaining things can be polymorphic.
You can move the common attributes in User and separate the remaining in their corresponding models.
It would be much better if had a look at code.
Thanks
I ended up creating a third model and using has_many :through => to link everything together. I wanted to keep a single Devise user model and use a .role for authorization.
The third model I created was Timeslot, which is essentially where a teacher and student interact. Ideally I would have called it class or session but they're both reserved words.
# timeslot.rb
class Timeslot < ActiveRecord::Base
belongs_to :user
belongs_to :student
end
# user.rb
class User < ActiveRecord::Base
ROLES = %w[admin teacher parent]
has_many :timeslots
has_many :students, through: :timeslots
end
# student.rb
class Student < ActiveRecord::Base
has_many :lessons
has_many :timeslots
has_many :users, through: :timeslots
end
This allows a user with a teacher role to have many students and vice-versa. The Student model has a parent_id column which stores the id of the user with role parent. That part is still a bit clunky but it works fine for now. I'm sure I'll have to refactor to something more elegant if the app has to scale.

how to access data with has_many :through relation

Hi I have three models: company, plan and subscription with following association
class Company < ActiveRecord::Base
has_one :plan, :through => :subscriptions
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :plan
belongs_to :company
end
class Plan < ActiveRecord::Base
has_many :subscriptions
has_many :companies, :through => :subscriptions
end
I have two plans in my application plan 'A' and plan 'B'. Plan 'A' is free and 'B' has some fee. Now i want to get companies registered with plan 'A' and companies with plan 'B'.
I want this data in my model, i know their is definitely a simple way to get all this but
every thing i have used i not giving me right data.any help would be thankful.
To get companies registered with plan 'A' and companies with plan 'B'.
Take object of plan, and then through following relationship code, you will get count of companies.
This is join concept.
eg. #plan is object of Plan 'A'.
then #plan.companies.count.
I suggest to use "polymorphic association" concept.
You need to insert new records through the association. Here is a related link that might help. how to add records to has_many :through association in rails
But a Pseudo-code will be like
1. You have a company object
2. you will have company.subscriptions
3. Insert new Plan objects in company.subscriptions
4. Save the data.
If you are still facing problem, I will try to add some code example.

Crafting the most efficient Rails 3 has_many :through query

I have two Rails 3 models, Product and Room defined per below. Each Room can have multiple products, and each Product can be in multiple rooms:
class Product < ActiveRecord::Base
#...
has_many :rooms, :through => :product_selection
has_many :product_selection
end
class Room < ActiveRecord::Base
#...
has_many :products, :through => :product_selection
has_many :product_selection
end
class ProductSelection < ActiveRecord::Base
#...
belongs_to :product
belongs_to :room
end
I'd like to create a query within rooms_controller.rb to return records for the first 10 rooms, and for each Room include a count of the number of products pushed to that room (to find out how many products are in each Room) and a sum of a column within Product called cost for all products in the Room (to get the total cost of all the products in the room). After calling the query, I would ideally be able to call #rooms[i].total_products and #rooms[i].total_cost (or something similar) along with the Room fields so it can be easily digested and iterated by a template.
I know I could create 10 different calls then loop through each as #room.products.count and #room.products.sum(cost), but there has to be a more efficient way...and I have a feeling I'm overlooking something obvious.
Thanks!!
The naive implementation will have you do N+1 queries...
If your desire was to just count your products efficiently, I would suggest implementing a :counter_cache
Though as you also want to perform a calculation (summation on cost), eager loading the products with the rooms query will be more desirable. The idea being to return your list of rooms and their products.
room_array = Room.includes(:products).limit(10)
If there was the further desire to make this efficient, you may want to have the database do the summation, too.