I'm working on a little time tracking application and ran into a problem I don't know how to solve. I have a Task
model and a Client
model. Each task belongs to a client.
class Task < ActiveRecord::Base
belongs_to :client
attr_accessible :client_id, :description, :start, :end
scope :yesterday, -> {
where('start > ?', Date.yesterday.to_time).where('start < ?', Date.today.to_time)
}
end
class Client < ActiveRecord::Base
attr_accessible :name
has_many :tasks
end
Right now I'm displaying a list of tasks scoped by the day the tasks were completed, and ordered by the time they were completed. I would like to display that same list, but grouped by the client, and sorted by the client name. Here's what I would like to do:
<div id="yesterday_summary">
<% @yesterday_clients.each do |client| %>
<h2><%= client.name %></h2>
<ul>
<% client.tasks.each do |task| %>
<li><%= task.description %></li>
<% end %>
</ul>
<% end %>
</div>
In my controller I currently have:
@tasks_yesterday = Task.yesterday
@yesterday_clients = group_tasks_by_client @tasks_yesterday
And in the group_tasks_by_client
method, I have some pretty ugly code that isn't even working at the moment:
def group_tasks_by_client(tasks)
clients = []
tasks.collect(&:client).each do |client|
clients << {client.id => client} unless clients.has_key? client.id
end
clients_with_tasks = []
clients.each do |client|
c = Struct.new(:name, :tasks)
cl = c.new(client.name, [])
tasks.each do |task|
cl.tasks << task if task.client_id = client.id
end
clients_with_tasks << cl
end
clients_with_tasks
end
I'm sure there is a clean, simple, rails-way to do this but I'm not sure how. How can this be done?
You can have the database do this for you like so:
@yesterdays_clients = Client.includes(:tasks).merge(Task.yesterday).order(:name)
Besides being cleaner, it's more efficient since it gets all your clients and tasks in one pass. The original code was subject to N+1 queries because there was no eager loading.
BTW, you can make your scope simpler as well:
scope :yesterday, -> { where(:start => (Date.yesterday.to_time...Date.today.to_time)) }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With