Using Open Source projects as documentation

I’ve been gearing myself on learning Ruby On Rails for a month or so now. Having read “Agile web development with Rails” as well as the most interesting - in my opinion - recipes in “Rails Recipes” I’ve started my own project… and you’ve prolly guessed right, a(nother) CMS.

Anyhow, problems occurred rather quickly, or, were they problems? I guess that the troubles I’m running into are only related to the learning curve that is rather steep for somebody that passed the past six years working exclusively on ASP and ASP.NET applications (Although I had some PHP/Perl experience prior to that, but six years… it’s allot).

One way I cope with some issues when I don’t know the language, especially when the question is “what’s the most efficient way to…”, is to look at examples. One thing RoR isn’t lacking is open source projects you can look at to get some hints.

So what’s the most efficient way to…

Most blogs have an “archives” link section containing dates (months and years) where at least one article was posted. So I naturally needed to do the same for my own CMS.

That particular feature wouldn’t of caused me a sweat or even a thought in ASP.NET but since Rails works so closely with the database schema I felt kindof tied up by the framework which left me wondering howto achieve this properly and effectively. So I’ve looked in a popular Rails CMS application (that I wont name since I did find lots more useful hints than bad ones from this application).

Common configuration

The database schema was really similar for both the open source project and my project, plus or minus some fields. I find this happens a lot with RoR projects, must be what happens when convention is enforced (it’s really not, but considering the benefits, it’s as if it was…) and respected by the developers.

Here’s the - trimmed down - migration class:

class InitialSchema < ActiveRecord::Migration
  def self.up
    create_table :blogs do |table|
        table.column :title,        :string,  :limit => 255, :null => false
        table.column :description,  :string,  :limit => 255, :null => false
    end

    create_table :articles do |table|
      table.column :blog_id,    :integer, :null => false
      table.column :user_id,    :integer, :null => false
      table.column :title,      :string,  :limit => 255, :null => false
      table.column :content,    :text,    :null => false
      table.column :created_at, :datetime
      table.column :updated_at, :datetime
      table.column :published,  :boolean, :null => false, :default => 0
    end
  end

  def self.down
    drop_table :blogs
    drop_table :articles
  end
end

 

The solution that was proposed

The proposed solution was rather simple so I will not go through it in details here. Acknowledging you have a Blog model with a relationship to article model as follow:

class Blog < ActiveRecord::Base
  has_many :articles_published,
    :class_name => "Article",
    :conditions => "published = 1",
    :order => "created_at"
end

 

In the view used to render your archived date list you can keep the “current” year and month in a variable (initializing them at zero) and then in a for-loop on ALL of your articles display the dates when the article month or year is different than the “current” date. Sort of as follow:

<%
  currentmonth = 0
  currentyear = 0
  for article in @blog.articles_published
      if (article.created_at.month != currentmonth ||
        article.created_at.year != currentyear)
          currentmonth = article.created_at.month
          currentyear = article.created_at.year %>
      <!-- RENDER HTML -->
      <% end
  end
%>

 

Note: Rendering was sniped out there’s a more complete rendering example following.

Now this might work for a blog like mine (as of now, there’s not a whole lot of activity here). But on a larger, more active blog, if the page isn’t cached properly there’s lots of unneeded processing taking place: fetching all of the articles, for-looping unwanted items.

Integrating article archived dates in the Blog model

I didn’t like the proposed solution and knew enough to say that there probably was another way to achieve the same result without querying too much data having to for-loop that extra unneeded data. So this is what I came up with…

Consider the same Blog model but with an extra relationship as follow:

class Blog < ActiveRecord::Base
  has_many :articles_published,
    :class_name => "Article",
    :conditions => "published = 1"
    :order => "created_at"

  has_many :articles_archived_dates,
    :class_name => "Article",
    :select => "DATE_FORMAT(created_at, '%Y-%m-01') as
      ArchivedDate, Count(id) AS ArticleCount",
    :group => "ArchivedDate",
    :order => "ArchivedDate DESC",
    :conditions => "published = 1"
end

 

The second relationship adds an array of Articles objects in the Blog model containing only the date (first day of the month) and the count of articles published within that month.

Notes: For some reasons, even if you CAST AS DateTime in the SQL query, the ArchivedDate property of the Article child object of article_archived_dates will not be of DateTime type. I might also of missed something…

It basically resumes to doing the following query:

SELECT DATE_FORMAT(created_at, '%Y-%m-01') as ArchivedDate,
  Count(id) AS ArticleCount, created_at
FROM articles
WHERE (articles.blog_id = 1 AND (published = 1))
GROUP BY ArchivedDate
ORDER BY ArchivedDate DESC

 

So why not use the “:filter_sql” attribute. Well you actually can, that is, if you’re running a single blog on your site. I want to run multiple blogs on the same site. With a filter_sql statement you cannot set the needed relation blog_id dynamically according to the requested blog thus you would end up with an archived date list for all blogs.

You can then render the archive list in your view as follow:

<% for ad in @blog.articles_archived_dates -%>
  <li>
    <%= link_to
      "#{Date::MONTHNAMES[Date._parse(ad.ArchivedDate, true).values_at(:mon).to_s.to_i]}
       #{Date._parse(ad.ArchivedDate, true).values_at(:year)}",
      :controller => "yourcontroller"
      :action => "youraction",
      :month => sprintf("%02d", Date._strptime(ad.ArchivedDate).values_at(:mon).to_s),
      :year => Date._strptime(ad.ArchivedDate).values_at(:year).to_s  %>
    (<%= ad.ArticleCount %>)
  </li>
<% end -%>

 

Note: As mentioned, the ArchivedDate property is of String type so it will not carry the strftime() method allowing you to format a DateTime variable in a much more graceful manner.

This method uses a much more efficient SQL query (not querying all articles) thus minimize the for-loop. Now even if it’s included in the Blog model, unless you use “:include”, it will only be executed when invoked in the controller action or view (I personally thought it would be executed all the time).

2 Réponses à “Blog Archived Date List with Ruby on Rails”

  1. Oddly Zen » Blog Archive » links for 2007-01-23 a dit :

    [...] Blog Archived Date List with Ruby on Rails « Blogged I’ve been gearing myself on learning Ruby On Rails for a month or so now. Having read “Agile web development with Rails” as well as the most interesting - in my opinion - recipes in “Rails Recipes” I’ve started my own project… and you’ve p (tags: rubyonrails dates archives) [...]

  2. lol a dit :

    pornstar wrestler dragom lili wrestler

Laisser un commentaire