intertwingly

It’s just data

Prototyping Intertwingly on Rails


It is said that Rails is opinionated software.  I’ve been exploring to see how it stacks up on a subject area where I have opinions: weblogging software.  So far, it has measured up pretty well. 

database.yml is used to configure the databases used.  Now lets look at the software.  I feel compelled to note that this is a work in progress, and very much incomplete.  This being said, even these initial results produces something recognizable as a weblog.

uris

URIs are definitely something I have an opinion on.  I don’t cotton to the idea of putting the action in the URI as I feel that URIs are intended to uniformly identify, well, resources.  I also have amassed a legacy of different URI conventions, all of which I wish to continue to support.

In Rails, when requests come in, the first thing that happens in that the request gets routed based on the URI.  URIs are composed of segments separated by slashes.  routes.rb enables you to name the segments, match segments against requirements you specify, and to do a first order partitioning of requests into actions.  Segments at the end of URIs can be defaulted, something that is defeated by my need to access the path component at the end.  This does, however, work for my archive URIs.

While I need to do additional parsing in the controller to distinguish index.html from index.atom for example, this initial partitioning is useful.

Controller

The next piece involved is blog_controller.rb, which serves as the nerve center of the weblogging application.  Its first responsibility is to field all the routed actions.  Each of these methods tend to be small.

Following this, I have a number of methods are specific to my weblog, which I have chosen to mark as private, even though this is not strictly necessary.  parse_date is a method to convert a date — or portions thereof — into an SQL query.  paginate_with_path will further parse the URI as mentioned above, and this method also serves as the branching off point for handling post requests. paginate optionally filters the query with a full text search and limits the results to a page (of typically 10) entries.  post handles previews and posts.

Finally, there are two small pieces of bookkeeping.  determine_layout determines the overall layout of the results.  This is useful for sites that generate mostly HTML with a common theme.  My site is borderline as has three classes of pages with a common theme, one with a different theme, and the rest of the responses (feeds, trackback, pingback, etc) are not HTML so don’t need any layout at all.  Ultimately, I may simply go with a set of partial templates (described below) and common CSS.  default_template_name completes the controller by utilizing the parsed flavour to determine the template name (as opposed to the rails tradition of using the action name).

Model

The controller makes use of entry.rb and author.rb for persistence.  Entry we have seen before, though I have now added an indication that an entry belongs_to an author, and unabashedly added code that David Heinemeier Hansson refers to as “muddling the model” by associating an entry with its resource identification.  Rails' approach of dealing with URIs as  a collection of named segments is helpful here.

View

Views start out with a blog.rhtml (and archive.rhtml) layouts which determine the overall look and feel of the HTML results.  Like JSP, ASP, and PHP, these pages are mostly HTML with a little bit of presentational logic mixed in.  While not strictly necessary, I’ve placed my sidebar into a separate partial as I tend to edit it with a different frequency than my layout.

html.rhtml contains the HTML flavor of my index page, search results , and the like.  Again, this is very much like JSP, ASP, and PHP; where blog_helper.rb effectively implements the equivalent of tag libraries.

atom.rxml provides support for Atom feeds for each of these pages, and utilizes a very different approach to building xml.  As my HTML pages are actually XHTML, I could productively use this approach for those pages too.

post.rhtml and preview.rhtml are two other rhtml files to support a individual post (with comments) and preview pages.  Both of these make use of a the partial commentform.

Finally, archive.rhtml produces my “month at a glance” page.

There also is the the various accompaniments of error pages, icons, images, javascripts, and stylesheets.

tests

entries.yml and authors.yml provide test data for entry_test.rb and author_test.rb.

Much more significant is the blog_controller_test.rb which issues mock HTTP requests against the various actions and verified the results - even to the point of parsing the resulting HTML for specific content or a given number of tags that match a specified criteria.  While this test suite only covers the basics at the moment, it has already proven quite helpful in flushing out a number of bugs.

Load

load.rb is the confidence builder with require 'config/environment' replacing the calls to ActiveRecord::establish_connection and the definition of the model.  Additionally, support for parsing authors and slugs (used in muddling the model) have been added.

If you would like to try this out yourself, make sure that you have rails and libxml-parser-ruby1.8 installed, download rails.tgz and atom.tgz, and run the following commands:

rails weblog
cd weblog
ruby script/generate model entry
ruby script/generate model author
ruby script/generate controller blog
tar xzf rails.tgz
tar xzf atom.tgz
ruby db/load.rb atom/*.atom
rake
ruby script/server

You should now be able to access a local copy of reasonable facsimile of my weblog at http://localhost:3000/blog/.

Conclusion

I still have much to explore: localization, logging, redcloth or equivalent, caching, spell checking, trackbacks, automated excerpts, Atom publishing protocol, comment throttles, etc., etc., etc.; however at this time I see nothing that will likely get in my way — to the contrary, I see quite a bit of things that I can build upon in Ruby on Rails.

And all with very few lines of code.  rake stats helpfully produces the following statistics:

+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Helpers              |    75 |    58 |       0 |       5 |   0 |     9 |
| Controllers          |   179 |   133 |       2 |      11 |   5 |    10 |
| APIs                 |     0 |     0 |       0 |       0 |   0 |     0 |
| Components           |     0 |     0 |       0 |       0 |   0 |     0 |
|   Functionals        |   196 |   141 |       2 |      21 |  10 |     4 |
| Models               |    75 |    56 |       2 |       3 |   1 |    16 |
|   Units              |    49 |    38 |       2 |       6 |   3 |     4 |
+----------------------+-------+-------+---------+---------+-----+-------+
| Total                |   574 |   426 |       8 |      46 |   5 |     7 |
+----------------------+-------+-------+---------+---------+-----+-------+
  Code LOC: 247     Test LOC: 179     Code to Test Ratio: 1:0.7