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