Code in layout files, and minimizing database usage


#1

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


#2

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|
%>

<%= year %>

<% posts_by_year = posts.select{|hash| hash["year"] == year} %>
    <% (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 %>
  • <%= link_to "#{Date::MONTHNAMES[index]} #{year}", month_path(year, ("%02d" % index)) %> (<%= posts_by_month.length %>)
  • <% end %> <% end %>
<% 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.


#3

Chris H. wrote:
[…]

It’s working well. It’s all still stuck in the view, but oh well.

That’s way too much logic to put in a view, even by my standards. :slight_smile:

If
anyone knows a better place, let me know.

The easiest refactoring would be to put it in a helper. Or perhaps you
can get creative with a class method on Post.

Best,

Marnen Laibow-Koser
http://www.marnen.org
removed_email_address@domain.invalid


#4

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
a :limit to show the last 10 posts and a “all” link to show all posts
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.


#5

On 28/05/2009, at 11:06 AM, Chris H. wrote:

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

<% posts = Post.all(:select => “year, month”, :order => “year,
month”).collect(&:attributes) %>

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.


Learn: http://sensei.zenunit.com/
Last updated 20-May-09 (Rails, Basic Unix)
Blog: http://random8.zenunit.com/
Twitter: http://twitter.com/random8r


#6

On May 28, 9:03 am, Mukund removed_email_address@domain.invalid wrote:

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