Why isn't this second-level eager loading working? - mysql

I have three models, Subrating, Thing, and Category. Subrating belongs_to Thing, and Thing has_many Categories.
I have a list of Subratings, and I'm trying to eager-load the Categories that are associated with the Things that are associated with each Subrating.
This is what I tried:
controller
#subratings = Subrating.all( :include => { :thing => :categories })
view
<% #subratings.sort_by(&:rating).reverse.each do |subrating| %>
<%= subrating.thing.categories.sort_by(&:thing_count).second.name %>
<% end %>
But it doesn't solve my n+1 problem. I'm not even sure whether the database is lazy-loading Things or Categories or both, but this is the line that keeps reappearing hundreds of times in my server:
CACHE (0.0ms) SELECT COUNT(*) FROM "things" INNER JOIN "category_things" ON "things"."id" = "category_things"."thing_id" WHERE "category_things"."category_id" = $1 [["category_id", 1]]
What am I doing wrong?
associations
class Subrating < ActiveRecord::Base
belongs_to :thing
end
class Thing < ActiveRecord::Base
has_many :category_things
has_many :categories, :through => :category_things
has_many :subratings
end
class Category < ActiveRecord::Base
has_many :category_things
has_many :things, :through => :category_things
end
class CategoryThing < ActiveRecord::Base
belongs_to :category
belongs_to :thing
end

Maybe you should try to call includes on the Model class like:
#subratings = Subrating.includes(thing: [:categories])
as documented in http://apidock.com/rails/ActiveRecord/QueryMethods/includes
although it also should work the way you are trying it, since .all is an alias for .find(:all) but then i would propose to try:
#subratings = Subrating.all( :includes => { :thing => [:categories] })
(note that i changed include to includes and made thing point to an array)

#subratings = Subrating.includes(:thing => :categories)
This will also work for you and give you all records of substrings table.

Related

How to sort nested items on field in 'parent' model?

I am new to Ruby on Rails and try to make the right query. But after reading the documentation and examples I don't manage to get the right query out of it. So I hope someone can point me in the right direction.
Situation I build an app where trainers can setup trainings, and give these trainings on several dates (the dates are called thrills), other users can subscribe for these thrills. It has the following structure:
class User
has_many :trainings
has_many :thrills, through: :trainings
has_many :reservations
class Training
belongs_to :user
has_many :reservations
has_many :thrills
has_many :users, through: :thrills
class Thrill
belongs_to :training
has_many :reservations
has_many :users, through: :reservations
class Reservation
belongs_to :user
belongs_to :thrill
has_one :training, through: :thrill
Question
How can I make a list of all reservations of the current_user ordered by the thrilldate? The thrilldate is set in the Thrills model.
In my reservations_controller.rb I have:
#reservations = current_user.reservations
This gives the right list of all the reserations, but I want to sort list this on reservation.thrill.thrilldate
I tried so by using this view:
<% #reservations.order("reservation.thrill.thrilldate ASC").each do |reservation| %>
Unfortunately this gives the error:
SQLite3::SQLException: no such column: reservation..thrill.thrilldate: SELECT
"reservations".* FROM "reservations" WHERE "reservations"."user_id" =
? ORDER BY reservation.thrill.thrilldate ASC
Who knows how I can refer to the thrilldate in the Thrills model? Thanks for helping me out!
Assuming thrilldate is a column on Thrill
#reservations = current_user.reservations.joins(:thrill).order('thrills.thrilldate asc')

Query in Ruby on Rails 4 to make a selection based on the current user

I am new to Ruby on Rails and try to make the right query. But after reading the documentation and examples I don't manage to get the right query out of it. So I hope someone can point me in the right direction.
Situation
I build an app where trainers can setup trainings, and give these trainings on several dates (the dates are called thrills), other users can subscribe for these thrills. It has the following structure: models and needed table.
My models code looks like this:
class User
has_many :trainings
has_many :thrills, through: :reservations
has_many :reservations
class Training
belongs_to :user
has_many :reservations
has_many :thrills
has_many :users, through: :thrills
class Thrill
belongs_to :training
has_many :reservations
has_many :users, through: :reservations
class Reservation
belongs_to :user
belongs_to :thrill
has_one :training, through: :thrill
On an index page I want to show all the thrills that the current user has setup sorted by date. I think I need a query that comes up with the table in the uploaded image, and from that table I can select all Thrills where current_user.id = user_id
On the search page I want to show only the trainings that have a Thrill that is not full (therefore I want to make a count of the Reservations)
I was thinking of something like this:
#thrills = Thrill.joins(:trainings).where('? = trainings.user_id, current_user.id')
or
#thrills = Thrill.where('? = #thrill.training.user_id', current_user.id).all
or
#thrills = Thrill.joins(:trainings).where(trainings: { user_id: current_user.id })
But unfortunately none of them works. Does someone have an idea how to solve this? Thanks in advance!
Usually you have these two models:
class Thrill < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :thrills
end
So for your index page what you can do is:
current_user.thrills # => ActiveRecord::Relation (can call each, map etc.)

Rails 3+, Querying polymorphic association from both ends

I'm trying to querying on polymorphic associations in Rail, and come across with this problem. I have a few models, User, MorningVisit, AfternoonVisit, NightVisit, and Result
class User < ActiveRecord::Base
attr_accessible :name
has_one :morning_visit
has_one :afternoon_visit
has_one :night_visit
end
class MorningVisit < ActiveRecord::Base
belongs_to :user
has_many :results, as: :visitable
end
class AfternoonVisit < ActiveRecord::Base
belongs_to :user
has_many :results, as: :visitable
end
class NightVisit < ActiveRecord::Base
belongs_to :user
has_many :results, as: :visitable
end
class Result < ActiveRecord::Base
attr_accessible :type
belongs_to :visitable, polymorphic: true
end
Everything stores perfectly, but now I need to do some search and querying on User using filterrific.
If I do
includes(:visitable => :user).order("user.name")
I get
Can not eagerly load the polymorphic association :visitable
So I tried to manually joined MorningVisit with
.joins( "JOIN morning_visits ON lab_results.visitable_id = morning_visit.id AND lab_results.visitable_type = 'MorningVisit'")
.joins( "INNER JOIN `users` ON `morning_visits`.`user_id` = `users`.`id` ")
And order("user.name") works.
But if I added
.joins( "JOIN afternoon_visits ON lab_results.visitable_id = afternoon_visits.id AND lab_results.visitable_type = 'AfternoonVisit'")
It returns nothing. And if I tried outer join, it keep telling me my SQL syntax was wrong.
Does anyone have a solution for this?

Creating nested records on create with Rails

I have a projects model that I am using to auto generate departments within a specific project on create. This is included in the projects model with:
class Project < ActiveRecord::Base
attr_accessible :title, :departments_attributes, :positions_attributes, :id
belongs_to :user
has_many :departments
has_many :positions
validates :title, presence: true
before_create :set_departments
accepts_nested_attributes_for :departments
accepts_nested_attributes_for :positions
private
def set_departments
self.departments.build department: "Test Dept", production_id: self.id
end
end
Each department has many positions. I am trying to create positions as well for the departments. How could I associate a new position with a department in this model?
There are two ways:
#app/models/project.rb
class Project < ActiveRecord::Base
has_many :departments
accepts_nested_attributes_for :departments
before_create :set_department
private
def set_department
self.departments.build department: "Test"
end
end
#app/models/department.rb
class Department < ActiveRecord::Base
has_many :positions
accepts_nested_attributes_for :positions
before_create :set_positions
private
def set_positions
self.positions.build x: y
end
end
... or ...
#app/models/project.rb
class Project < ActiveRecord::Base
has_many :departments
accepts_nested_attributes_for :departments, :projects
before_create :set_departments
private
def set_departments
dpt = self.departments.build department: "Test"
dpt.positions << Position.new position: "Admin"
dpt.positions << Position.new position: "Tester"
end
end
--
You can also declare multiple nested attributes on a single line:
accepts_nested_attributes_for :departments, :positions
If I understand your question correctly, you might do something like this in your Department model:
after_create { self.positions.create! }
Though this might be a problematic approach. Creating records like this using ActiveRecord callbacks (which is what gives us after_create) can make your whole app really fragile. For example, if you do this, you'll never be able to make a department without an associated position. Perhaps one day you'll need to do just that.
So even though it's not the exact answer to your question, I suggest looking at created these associated models in a service object, or at least in controller code.
You can add a new position with:
Project.first.positions << Position.create(:foo => 'foo', :bar => 'bar')
or
position = Position.create(:foo => 'foo', :bar => 'bar')
Department.first.positions << position
Project.first.positions << position
Obviously the ".first" is just for illustration and you can use the << notation with any Department or Project instance.
Looking at this again, it seems like a really good fit for polymorphic associations.
class Position < ActiveRecord::Base
belongs_to :positioned, polymorphic: true
end
class Project < ActiveRecord::Base
has_many :positions, as: :positioned
end
class Department < ActiveRecord::Base
has_many :positions, as: :positioned
end
And in your migration:
create_table :positions do |t|
...
t.integer :positioned_id
t.string :positioned_type
...
end
There may be a more suitable way to name things for your app but this is the general idea.

Joining 3 models together in Rails to display in a view iterating over one model

I have a fairly standard 'has_many :x, :through => :y' relationship with a user, a problem, and a completed_problem which acts as the association between the two:
/models/user.rb
class User < ActiveRecord::Base
has_many :completed_problems
has_many :problems, :through => :completed_problems
end
/models/problem.rb
class Problem < ActiveRecord::Base
belongs_to :wall
has_many :completed_problems
has_many :users, :through => :completed_problems
end
/models/completed_problem.rb
class CompletedProblem < ActiveRecord::Base
belongs_to :user
belongs_to :problem
validates_presence_of :user
validates_presence_of :problem
end
My complication is that data in each of these models impacts the display. I'm looking to display a list of problems on each wall, and for each problem in that list to use or show:
problem.id
problem.name
time since the current user completed the problem last
if no logged in user, some text
if user hasn't completed that problem, some other text
A (very ugly) first pass at the view is as follows:
/views/walls/show.html.erb
<% #wall.problems.each do |problem| %>
<a id=<%= "problem_#{problem.id}" %>>
<h3><%= problem.name %></h3>
<p><%= "#{time_ago_in_words(problem.last_complete_by_user(current_user))} ago" if current_user && problem.last_complete_by_user(current_user) %></p>
</a>
</li>
<% end %>
I've since overwritten it, but problem.last_complete_by_user (seen in the above snippet) was an attempt to use the problem object to find all the related completed_problems, with the user as an argument, in order to identify the 'updated_at' value for the most recently updated completed_problem for that particular problem and user.
Of course this isn't ideal because it'll be a separate query for each item in the list - I assume the preferred solution would be a method in the wall controller or model that joins across all 3 tables and returns a new array for the view to iterate over. Unfortunately I've spent too long bouncing between :join, :include and :find_by_sql without a solution.
Can someone at least lead me in the right direction for how to get this view working properly?
This is how I would solve the problem. It may not be the most efficient solution, but it's clean and easy to refactor when the time comes. I haven't tried the code, but it's probably not too far off. If you go this route and run into performance problems, I would look into fragment caching before adding a bunch of crazy SQL.
Models:
class User < ActiveRecord::Base
has_many :completed_problems
has_many :problems, :through => :completed_problems
# Finds the last completed problem
def last_completed_problem(problem)
problems.order('created_at DESC').where(:problems => {:id => problem}).limit(1).first
end
end
# No Changes
class Problem < ActiveRecord::Base
belongs_to :wall
has_many :completed_problems
has_many :users, :through => :completed_problems
end
# No changes
class CompletedProblem < ActiveRecord::Base
belongs_to :user
belongs_to :problem
validates_presence_of :user
validates_presence_of :problem
end
app/controllers/walls_controller.rb:
class WallsController < Application::Controller
def show
#wall = Wall.find(params[:id]).includes(:problems)
end
end
app/helpers/wall_helper.rb:
module WallHelper
def show_last_completed_problem_for_user(user, problem)
return "You are not logged in" if current_user.nil?
completed = user.last_completed_problem(problem)
return "You have not completed this problem" if completed.nil?
time_ago_in_words(completed.created_at)
end
end
app/views/walls/show.html.erb:
<%= render :partial => 'problem', :collection => #wall.problems %>
app/views/walls/_problem.html.erb:
<li>
<a id=<%= "problem_#{problem.id}" %>>
<h3><%= problem.name %></h3>
<p><%= show_last_completed_problem_for_user(current_user, problem) %></p>
</a>
</li>
So you want the LATEST completed problem, of which there may be many... or none.
We can do that by left-joining to completed_problems (in case there's not one there) then grouping on user/problem. The grouping means you get only one record per user/project. One final trick -- you need to specify your columns in 'select' so that we get the normal project fields and another for the last_attempted.
#problems = Problem.join("LEFT OUTER JOIN problems ON completed_problems.problem_id = problems.id").
group(:user_id, :problem_id).
select("projects.*", "MAX(completed_problems.updated_at) as last_attempted").
where(:wall_id => params[:wall_id])