Recently in Code Category

Ruby on Rails is an excellent framework for building web applications. Perhaps the best. But it's not currently very well suited to what I call web sites.

The difference is simple. In a web site, the unique business value comes from the content creators (authors, bloggers, photographers, etc). In a web application, the business value comes directly from the programmers. Twitter, Google, Basecamp and eBay are web applications. CBSSports.com, KentuckyDerby.com, corporate brand sites and original news sources are all web sites.

For these projects where it's the content that matters, there's no reason to spend time hand-coding models, controllers, views and test code for things called Pages, Articles, BlogPosts, CalendarEntries, and all the other unique types of content. You're just re-inventing the wheel if you do, because there are numerous existing commercial and open source CMS tools that can do the job cheaper and better than you.

Unfortunately, I find that one of the most common scenarios is a hybrid web site that needs an attached web application. A corporate web site that needs an attached scheduling tool. A news site that needs an interactive, custom-searchable database. A sports site which aggregates content from an old NNTP-based data source.

Depending on the specifics, you usually have 3 different approaches to this problem:

  1. Two side-by-side applications. - Pair an off-the-shelf CMS with a custom built application. Copy/paste the visual elements, and loosely wire the two together with a subdomain or some kind of web proxy to share the URL structure
  2. Extend the CMS - Build a proprietary plugin or extension for the CMS to implement the application features.
  3. Soup-to-nuts custom application. Build a complete CMS as part of the project.

If scenario #1 is truly possible, it's the best approach. Unfortunately, it's often the case that the two systems need to share user accounts, group permissions (or worse, business logic) and data. Once you link the code and data of the two applications, you actually have a particularly "smelly" case of scenario #2.

And scenario #2 is usually a bad idea, unless you are really committed to the CMS you've chosen. This approach tightly couples your value-adding intellectual property directly to a CMS that generally has nothing to do with your business. If your CMS vendor is out of business in 5 years, you'll probably need to completely rewrite that custom extension to work in some new system. Unfortunately, this seems to be the most commonly-used approach. Radiant's extensions have this problem, and so does the Block/Portlet system in BrowserCMS.

Finally, scenario #3 is just plain wasteful. If your CMS needs are anything but trivial, you're going to spend a lot of time and money building CMS features that have nothing to do with your business enterprise. Unless you accidentally build an exceptionally good CMS tool that you then sell to others, all that money is wasted.

The reason for this problem is that Ruby on Rails is a framework for applications, not systems. The error in all of the reasoning above is that the described CMS project is not a single application, but a system of two (or more) applications.

I've seen some very attractive developments that will help to address this issue in future Rails applications. First, the inclusion of Rails Engines in 2.3 is going to lower the level of coupling for cases where the CMS can be an "engine" within the application. There's some hope that an even more flexible solution may be forthcoming. These solutions help to flip scenario #2 on its head. Unfortunately, this still leaves your application largely dependent on the CMS vendor. You may have trouble customizing the CMS' features without hacking the engine (or "mounted app") code directly.

Over the past few months, I've been working to find better solutions to the issues I've raised here. I'm in the process of building an experimental engines-based CMS plugin, through which I hope to gain some active insight into what makes this problem so difficult. I'll be posting my analysis as it crystalizes. If the CMS turns out to be effective for my uses, I'll be releasing it to GitHub.

For now, I'll simply leave it at this: I believe the solution lies in finding a common "language" for an application to communicate with its plugins, and for those plugins to communicate with one another. In the case of authentication and authorization, this means a plugin needs to know how to ask about the identity and rights of the active user.

Django and Drupal, have made the framework itself answer that question (both Django and Drupal have admin interfaces and user management features somewhat baked in). I'm not convinced that's the best approach, but it seems worthwhile to consider that approach for Rails.

My CMS plugin is opting instead to look for API functions at the controller level to answer these questions (i.e. the plugin assumes you have a current_user() method in ApplicationController, but allows you to customize the name and behavior of that).

Update: Thank you for all the encouraging comments. There are sure some good people out there in the Rails community. I wanted to draw some attention to ferrisoxide's blog post: "Rails is a CMS". This thought is very much along the lines of the experimental development I'm doing. The goal for my approach is to leverage as much of the flexibility of Rails itself for it's feature set.... in that way I'm planning more of a CMS-enabler plugin than an actual out-of-the box CMS.

Update 2: I wanted to also draw attention to Aaron Bedra's comments about CAS, specifically Castronaut as an implementation pattern for scenario #1. It didn't even cross my mind when I was writing this post... and CAS seems to be one of the most under-hyped technologies out there. More on that later.

Rails' built-in Javascript view helpers are excellent in terms of convenience, but they make it difficult to follow Yahoo's wise advice that scripts at the bottom of the page make for faster loading pages than scripts at the top of the page (obviously, using inline Javascript like the Rails helpers do requires that your <%= javascript_include_tag :defaults %> be above the main yield in the template.

The solution to this problem is simple. Every time you need to call a Javascript helper method in a view, just wrap it in a <% content_for :footer do %> block, then put a <%= yield :footer > just before the </body> in your template.

I've just solved an issue with my GNU Screen setup on my new Mac for quite a while, and I thought I'd share the solution in case anyone else has this issue.

I use a shell script to initialize a customized screen session when I'm editing a Ruby on Rails application. This script launches a database console, AutoTest session, and development Mongrel instance, and organizes and titles all the screen windows.

I developed it some time ago on a MacBook Pro, and it suddenly stopped working properly on my new Mac Pro. The symptom was that all the shell-based windows worked fine, but the database console, rSpec, and AutoTest sessions all failed to boot, leaving blank screen windows.

The problem turns out to be Leopard's default screen command. It breaks the $PATH variable. The solution quoted in that post is to hard-code a shell command in .screenrc, but my solution was a little different. I just opted for installing the MacPorts version of screen instead. It's more up-to-date, and doesn't clobber the path.

sudo port install screen

Just stumbled across this nice, succinct gallery of the most commonly-used CSS patterns: Using CSS to Fix Anything.

Here's a nice trick to use when you need to do a selective commit in you version control system. Say you have 100's of files in different directories that have been changed, but you only want to commit a certain subset of those. This tip is especially useful if those files can be selected using shell commands.


1 svn st | grep \.install$ | cut -c 8- | xargs svn commit -m "Commit message."
2          # select files
3                           # Remove the status indicators
4                                        # Pass the resulting filenames  to svn commit

This particular command is selecting only files that end in ".install". You could use another argument to grep or other shell mechanisms in place of that.

The real trick here is the use of cut -c 8-, which chops off the first 7 characters (where svn st shows status information), and xargs, which reformats the line-delimited output of svn st and turns it into an argument list.

Weird problem, simple solution. I have an external Maxtor backup disk connected to my Macbook Pro, and I cannot eject it. I get the error:

"DRIVENAME is in use and could not be ejected."

First, I try to use the lsof command to figure out what program still had open files on the drive. Unfortunately, I get no output, meaning no files are open on the drive.

Next I resorted to the Mac OSX hdiutil command. Using sudo hdiutil unmount reported that it was already unmounted.

Finally:

sudo hdiutil eject -force diskname

And the drive immediately disappeared from the Desktop. Hallelujah!

Who's this guy?

Aaron Longwell is Chief Web Craftsman at New Media Logic Corporation in Coeur d' Alene, Idaho. As a professional software developer for 12 years and a student of public policy, he occasionally has interesting things to say about software, technology, culture and politics.

Subscribe to feed Subscribe to my RSS Feed

  • View Aaron Longwell's profile on LinkedIn
  • Recommend Me