Occasional encounters with Ruby on Rails.

Tuesday, February 05, 2008

Static thinking about dynamic languages


If you've approached Ruby from a background in programming with static (compiled) languages it's easy to get a lot of "static" in your thinking about Ruby. One of the most common places to lose sight of t dynamic nature of the Ruby language is in class definitions. A class definition is really a call to a method in Kernel that creates a global constant with a reference to the object that represents the class, but Ruby has so faithfully repeated the class-definition language of static languages that we forget that we're actually executing code.


Who cares if class definitions are dynamic?


The dynamic nature of class definitions in Ruby can be a big deal if you take advantage of it. Mike Clark points out a nice use of the dynamic nature of Ruby in the forthcoming Advanced Rails Recipes. The basic scenario is that you've got an ActiveRecord model that contains data that is primarily intended to be constant for the life of the application -- something like a table of states and provinces (so Clark) or menu items. Rather than look for performance improvements through caching, you could instead create a constant array on the ActiveRecord class that is populated as the class is created.



  1. class MenuItem < ActiveRecord::Base

  2. MENUS = { :start_menu=>1, ... }

  3. START_MENU_ITEMS = find(:all, :order=>"position, name", :conditions=>{:menu_id=>MENUS[:start_menu]})

  4. end



The MenuItem class above has already inherited the class-level find() method from ActiveRecord::Base we we make use of it during the class creation process. By doing this the constant data stays in memory and need not be cached. Of course, care should be taken to make sure that the amount of data stored in this constant array is not so large as to reduce performance elsewhere.



The dynamic nature of Ruby class definitions can also help to keep your class-definitions DRY. I've recently been working on a project in which a Person may have many Addresses. Simple enough. But the Addresses need to be subclassed, using single table inheritance, so that I can distinguish a home address from a work address. Oh, yeah, the Addresses also have a date range associated with them so that we can track when you plan on moving to a new Address, which in turn leads me to a need to know the current home address and work address and ... This could get very messy. But it's not if we take advantage of the dynamic nature of Ruby.



  1. class Address < ActiveRecord::Base

  2. self.inheritance_column = 'address_type'

  3. ADDRESS_TYPES = %w(HomeAddress WorkAddress SchoolAddress TemporaryAddress)

  4. belongs_to :addressable, :polymorphic=>true

  5. end


  6. Address::ADDRESS_TYPES.each do |address_type|

  7. eval "class #{address_type} < Address; end"

  8. end


  9. require 'address.rb'

  10. class Person < ActiveRecord::Base

  11. has_many :addresses, :as=>:addressable

  12. Address.subclass_names.each do |address_type|

  13. self.send(:has_one, "current_#{address_type.underscore}",

  14. :as=>:addressable,

  15. :class_name => address_type,

  16. :conditions=>['activated_on <= ? AND deactivated_on IS NULL OR deactivated_on>?', Date.today, Date.today])

  17. end

  18. end



In the code above I'm actually taking advantage of Ruby's dynamics twice. In lines 7-9 I'm iterating through each of the ADDRESS_TYPES declared in the Address class above and declaring a new subclass for each entry in the array. This may not work in every situation; certainly in most instances of subclassing you'll want to add code to the subclass. It works here, though, because the subclass name will be used in rendering (e.g., putting a nice home or work image next to the address with css).


Lines 14-18 is where I've used the dynamic nature of Ruby class declarations to keep things DRY. The Address.subclass_names method simply iterates over the subclassess of Address and returns an array of the names. This might seem overkill since ADDRESS_TYPES already has this information but it will allow me to know if classes are created in some way other than through the loop in lines 7-9. That said, lines 14-18 simply iterate over all the names of the subclasses and creates an association between person and one member of the addresses collection. Specifically, it creates an Address#current_home_address for the HomeAddress class and so on.

No comments: