Keep your acts_as_attachment models DRY

I think acts_as_attachment is my favourite Rails plugin. Not only does it provide some fundamentally necessary functionality (read: upload and manage files), it does some handy stuff like validate content types, maintain proper parent-children relationships for thumbnails, and comes packaged with a pretty thorough set of test cases. It totally upstages its predecessor, the file_column plugin by Sebastian Kanthak, which isn’t being actively maintained these days anyways.

If there’s one thing I like about Kanthak’s plugin though, and keep in mind this is also its greatest flaw, is that file_column attachments have a minimal impact on your database schema. You just need an extra column for the models you’re attaching files to – totally simple. Acts_as_attachment, on the other hand, wants separate tables for each of your attachment models. Here’s two taken from an imaginary online video store, complete with customer profiles (they’ve jumped on the “community driven” bandwagon):

class DvdCover < ActiveRecord::Base
  belongs_to :dvd # through dvd_id
  acts_as_attachment :content_type => :image
end

class Mugshot < ActiveRecord::Base
  belongs_to :profile # through profile_id
  acts_as_attachment :content_type => :image
end

If you use acts_as_attachment this way, you’ll need two tables named dvd_covers and mugshots. Personally, I’ve got enough tables as it is, and since they’re really both just images, I’d like to store them in one. The solution? Subclass from a generic Image model using the power of Rails’ single table inheritance.

class Image < ActiveRecord::Base
end

class DvdCover < Image
  belongs_to :dvd, :foreign_key => :owner_id
  acts_as_attachment :content_type => :image
End

class Mugshot < Image
  belongs_to :profile, :foreign_key => :owner_id
  acts_as_attachment :content_type => :image
end

Pretty simple huh? Now you’ve got all your images stored in a single ‘images’ table. This may not be for everyone, but I think it’s tidy.

In case you’re wondering, here’s the full database migration for the Image model.

create_table :images do |t|
  t.column "owner_id", :integer   # generic owner
  t.column “type”, :string        # holds the class

  t.column "content_type", :string
  t.column "filename", :string     
  t.column "size", :integer
  t.column "parent_id",  :integer 
  t.column "thumbnail", :string
  t.column "width", :integer  
  t.column "height", :integer
end

There’s just one more thing – we’ve broken the relationship between these image models and their owners. We’ve already fixed up belongs_to to use the owner_id foreign key; we need to do the same for the corresponding has_[one|many] clauses for Dvd and Profile.

class Dvd < ActiveRecord::Base
  has_many :dvd_covers, :foreign_key => :owner_id
  ...
end

class Profile < ActiveRecord::Base
  has_one :mugshot, :foreign_key => :owner_id
  ...
end

Note: Your uploaded DvdCover files will still wind up in /public/dvd_covers, and Mugshots in /public/mugshots.

This technique could be re-used for any number of different attachment types – audio files, video files, etc. For that matter, you could make all your files subclass from a a single generic Attachment model, but that might be overkill.

Announcing the Mephisto Theme Gallery

Justin Hernandez and I just launched the Mephisto Theme Gallery, an unofficial showcase site for open-source themes and templates for the Mephisto blogging engine. Mephisto’s becoming increasingly popular, especially within the Rails community, so we thought it was about time it had a good and proper theme browser. This uninspiring wiki page just wasn’t cutting it anymore.

The app itself was developed using Rails in around 30-40 hours spread over 2 weeks. Neither of us are web designers per say, so we decided to work within an open source layout put together by Will Rossiter. We leveraged plugins wherever possible, and I implemented the image validation based on Chris Wanstrath’s recent captchator on Rails article. The end result? Not too shabby if I say so myself.

Anyways, please take a look, and let us know what you think.

Legacy Rails - beware of 'type' columns

Earlier this week I was writing an app that had models connected to a number of legacy databases. To do this, I followed PragDave’s example of subclassing from ‘gatekeeper’ models that establish separate database connections. Without going into too much detail, here’s an example:

class Finance < ActiveRecord::Base
  self.abstract_class = true
  establish_connection(
    ActiveRecord::Base.configurations["finance_#{ENV['RAILS_ENV']}"]
  )
end

class Receipt < Finance
end

class Income< Finance
end

Looks good, but I hit a snag – one of my legacy tables had a ‘type’ column defined. When Rails sees a column named ‘type’ for models that aren’t immediate children of ActiveRecord::Base, it assumes that column holds the class name associated with your model. This is how Rails implements single table inheritance, and if that’s not what you intended, you’ve got a little extra work ahead of you.

EnterpriseCamp Toronto

Just came back from Enterprise Camp, one of many BarCamp-style events organized by the Toronto technology community, this time for discussing enterprise development, infrastructure, solutions, and so on.

If you’re unfamiliar with the BarCamp format, as I was going in, anyone’s allowed (and encouraged) to lead their own session. Topics included how to apply web 2.0 technology and concepts to enterprise-level software (think tagging, news syndication), getting users and managers to buy into it, other domains for the BarCamp format, and at least one software demonstration that I totally missed. Okay, maybe that sounds a little dry, but every session enjoyed a high level of participation, and I feel I sat in on some pretty exciting conversations.

When Enterprise Camp rolls around again, I would love to lead a session on how Ruby on Rails can work inside the enterprise. How can we convince managers and IT departments that Rails-solutions are robust, cost less, scale well, and will play nice with their legacy systems? Jay Fields and the ThoughtWorks bunch seem to be leading the charge on that front.

Edit: Will Pate has since put up some photos.

Haml reaches version 1.0

Haml, the templating language created by fellow Torontonian Hampton Catlin, very quietly reached version 1.0 over the holidays. In case you’re not familiar …

Haml is a markup language that’s used to cleanly and simply describe the XHTML of any web document without the use of inline code. Haml functions as a replacement for inline page templating systems such PHP, ASP, and ERB, the templating language used in most Ruby on Rails applications. However, Haml avoids the need for explicitly coding XHTML into the template, because it iself is a description of the XHTML, with some code to generate dynamic content.

If you’re looking for more, John Philip Green wrote an introductory article about Haml back in September, and Xavier Shay has a good set of examples up at RHNH.