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:
app/controllers/walls_controller.rb:
app/helpers/wall_helper.rb:
app/views/walls/show.html.erb:
app/views/walls/_problem.html.erb: