I starting programming Ruby (via an initial introduction by Rails) some two years ago. I thoroughly enjoy the language. It’s fun, expansive, creative and economical. Nonetheless, the language throws down interesting challenges – it regularly reveals a new pattern, a new construct, or a new conceptual framework to get things done far more creatively. The language opens up a new way of thinking. I provide an example below.
Code as a Story
Ruby has many features that allow the programmer to define a story in their code. With a bit of coaching, it’s conceivable that a power user (with no coding experience) could read the code and understand what it does. Other languages are designed according to a strict code hierarchy of what goes where, with little license given to the programmer to define their own metaphors. Ruby encourages the development of metaphors that lucidly refer to real world concepts that power users could recognize. The language releases the programmer from the abstract world in code.
Ruby and Rails define class methods that are so well disguised that, although it’s easy to tell what’s going on, it’s not clear how it is being done:
class Message < ActiveRecord::Base
has_one :sender
has_many :receivers
validates_presence_of :subject
validates_length_of :subject, :maximum => 200
validates_length_of :body, :maximum => 2000
end
A power user should be able to read this Rails code to validate that the business rules have been correctly captured (such as that a message must have a subject). I have always tried to maintain a level of readability of business objects and processes in code that a power user could conceivably digest and discuss.
These methods are class methods on the Message class (or more correctly, they’re singleton methods on the Message class object). This is all very nice, but we’re used to the idea (from statically-typed languages such as Java) that class methods can’t get access to instances of that class. This limits class methods to concepts that are common without variation to all instances of that class.
Not so Ruby. At the occasion of the definition of the Message class, we call the class method has_one, passing in a concept that is common to all messages, that is that every message instance has one sender. But here’s the Ruby benefit that’s so valuable but so disguised. Each message has of course their own unique sender. In statically typed languages, since the has_one method is a class method, and class methods are without variation for all instances of the class, the ridiculous conclusion that all messages have the same sender results.
Ruby’s class methods allow for a declarative syntax to mimic the business’ processes and conventions. This increases readability and code quality by allowing the code to be self-documenting. Concepts in the real-world are translated into code without a lot of intervening abstractness.
Method
Declarative syntax (using class methods) uses Ruby’s dynamical trickery. The class method defines a new instance method on the fly. This newly created instance method can refer to other instance methods and variables. For example: I want to be able to declare the carrier of a message when a define a new class of message (based upon some sort of business domain), like thus:
class SlowMessage < Message
deliver_by :pigeon
end
class FastMessage < Message
deliver_by :airplane
end
class ElectronicMessage < Message
deliver_by :email
end
I then want to be able to call the business-rule inspired code:
message = SlowMessage.new
message.pigeon ...
I break open the Message class to add a new class method delivered_by. In the literature I’ve discovered a couple of ways that Ruby allows you to do this:
class Message
def self.deliver_by(carrier)
class_eval %Q{
def #{carrier}
"Deliver by #{carrier}"
end
}
end
end
class Message
def self.deliver_by(carrier)
define_method(carrier) { "Deliver by #{carrier}" }
end
end
class Message
def self.deliver_by(carrier)
define_method carrier, Proc.new { "Deliver by #{carrier}" }
end
end
(Admittedly, the second and third methods are essentially the same – passing a block vs. passing a Proc, but the third example brings to mind interesting ideas with that Proc).
Using class_methods and dynamic instance methods, I’ve been able to create a declarative syntax for classes that model the business domain. It has come to be very handy to provide to power users and has significantly increased productivity.
References
- Black, David A.; Ruby on Rails, 2008, p347
- Olsen, Russ; Design Patterns in Ruby, 2006, p308
- Flanagan, David & Matsumoto, Yukihiro; The Ruby Programming Language, 2008
- Fields, Jay; Ruby: instance_eval and class_eval method definitions, March 9, 2007