Code in layout files, and minimizing database usage.

So, I'm putting together a blog, and I want the posts on it to be easily browsable by date. So far I've given each post attributes for the month and year it was created. That way, when someone visits example.com/blog/2008 Rails does a find_all_by_year and displays and paginates the results - similarly, when someone visits example.com/blog/2008/09 Rails does find_all_by_year_and_month and does the same.

Now, I'm trying to change the blog layout so that it provides links to each month as well as the post count for that month. This would look something like:

August 2008 (24) September 2008 (33) October 2008 (17)

And so on. So, two questions:

#1 - What's the best way to do this? The most direct method I see is to do a Post.find_all_by_year_and_month(...).count for each month of each year, but that would be pretty abusive of the database, right?

#2 - How do I do this and remain MVC? The above display is happening in the layout, and I know I shouldn't be getting very code-heavy in the view, but I don't know where else to put this kind of functionality.

Thanks! Chris

Ok, after some thinking and experimentation, I came up with:

<% posts = Post.all(:select => "year, month", :order => "year, month").collect(&:attributes) %> <% (posts.first["year"]..posts.last["year"]).to_a.reverse.each do |year| %>   <p><%= year %></p>   <% posts_by_year = posts.select{|hash| hash["year"] == year} %>   <ul class="nostyle">     <% (1..12).to_a.reverse.each do |index| %>       <% posts_by_month = posts_by_year.select{|hash| hash["month"] == ("%02d" % index)}%>       <% unless posts_by_month.length == 0 %>         <li><%= link_to "#{Date::MONTHNAMES[index]} #{year}", month_path(year, ("%02d" % index)) %> (<%= posts_by_month.length %>)</li>       <% end %>     <% end %>   </ul> <% end %>

It's working well. It's all still stuck in the view, but oh well. If anyone knows a better place, let me know.

Any time you're doing equalities against a model object (here Post),
you could put it in the controller and assign it to an instance
varaible.

Also I'm not sure why you're getting the attributes of the objects,
when you could be getting the objects by themselves.

controller: @posts = Post.all(:select => "year, month", :order => "year DESC,
month DESC")

view:

<% @posts.group_by(&:year).each do |year, year_of_posts | %>   <%= year %>   <% year_of_posts.group_by(&:month) do |month, month_of_posts| %>     <% month_of_posts.each do |post| %>       <%= "some data about the posts or links or whatever %>     <% end %>   <% end %> <% end %>

obviously you should change this to be more in line with what you
actually want (probably the inner each is unnecessary for your
requirements.

Rather than reversing in ruby, use order with DESC sql fragment to get
the data the way you want it from the database. This is what the
database is good at. Also, have you thought of actually storing a datetime for the post,
instead of doing all this crazy number stuff? Then you could use the
strftime method on the datetime to print out the dates nicely. Much
more elegant.

Julian.

Why do you want to load all the posts into memory? For doing a count, rely on SQL instead of Ruby. It is faster and more efficient.

Post.count(:conditions=>"xxx") translates into a select count(*) from posts where "xxx"

When someone clicks on the month title, you can always do a query with in that month.

I would have class level functions in the Post model provide me information regarding count and data. The model alone knows how to get that data, other parts of the application have no need to dig into those details. This will ensure your app doesn't break if you change the implementation of Post in the future.

Why do you want to load all the posts into memory? For doing a count, rely on SQL instead of Ruby. It is faster and more efficient.

Post.count(:conditions=>"xxx") translates into a select count(*) from posts where "xxx"

And you could even do Post.count(:all, :group => 'year, month' ) (and then cache that :slight_smile: )

Fred