Sunday, February 24, 2008

Ruby: Creating Anonymous Classes

Classes in Ruby are first-class objects—each is an instance of class Class. -- ruby-doc.org
Most classes are defined using the syntax of the following example.

class Person
...
end

Defining classes this way is familiar to most developers. Ruby provides another syntax for defining classes using Class.new. The following example also defines the Person class.

Person = Class.new do
...
end
Class.new(super_class=Object) creates a new anonymous (unnamed) class with the given superclass (or Object if no parameter is given). You can give a class a name by assigning the class object to a constant. -- ruby-doc.org
The following example shows that the new class begins as an anonymous Class instance, but becomes a named class when it's assigned to a constant.

klass = Class.new
klass.ancestors # => [#<Class:0x21b38>, Object, Kernel]
klass.new.class # => #<Class:0x21b38>

Person = klass
Person.ancestors # => [Person, Object, Kernel]
Person.new.class # => Person

klass.ancestors # => [Person, Object, Kernel]
klass.new.class # => Person
klass # => Person

I generally use traditional syntax when defining named classes, but I have found anonymous classes very helpful when creating maintainable tests. For example, I often need to test instance methods of a Module. The following tests show how you can define a class, include a module, and assert the expected value all within one test method.

require 'test/unit'

module Gateway
def post(arg)
# implementation...
true
end
end

class GatewayTest < Test::Unit::TestCase
def test_post_returns_true
klass = Class.new do
include Gateway
end
assert_equal true, klass.new.post(1)
end
end

There are other ways to solve this issue, but I prefer this solution because it keeps everything necessary for the test within the test itself.

Another advantage to defining classes using Class.new is that you can pass a block to the new method; therefore, the block could access variables defined within the same scope. In practice, I've never needed this feature, but it's worth noting.

Labels: , ,




Saturday, January 27, 2007

Class Reopening Hints

In my previous entry about Class Definitions Aman King left the following comment:
I've always liked Ruby's open class feature but have also wondered how do you keep track of all the changes made to the class definition during runtime? ...
There isn't an easy way (that I know of) out of the box to know what files made what changes to your objects. But, are a few things that you can do that may give you hints as to where behavior is coming from.

Use modules instead of adding behavior directly.
On my current project we needed to add a % method to symbol. You can accomplish this with the following code.
class Symbol
def %(arg)
...
end
end
The above code does what you need, but leaves no hint that you've made a change to Symbol. An alternative solution is to define a module and include that module in Symbol.
module SymbolExtension
def %(arg)
...
end
end

Symbol.send :include, SymbolExtension
Granted, this isn't a huge hint, but if you check Symbol.ancestors you'll find the following list.
Symbol
SymbolExtension
Object
Kernel
Many people believe this is better than nothing, but the choice is yours.

Use the stack trace.
Another option is to use the stack trace to try to track down a specific method. If you are working with some code where a method is being modified somewhere, but it isn't in a module the following code could help you out.
class Symbol
def self.method_added(method)
raise caller.to_s if method == :%
end
end

class Symbol
def %(arg)
self
end
end
The above code uses the method_added hook method and raises and exception when the method you are looking for is defined. At this point the stack trace should show you where the method was defined.

Use conventions.
In practice I've had very little trouble with finding where behavior is defined. In all the projects I've been involved with we have a few conventions that we follow that help ensure we can find behavior definitions easily.As I previously stated, I'm on a 14 person team currently. Those 14 people create quite a few features at a rapid pace. Despite our size and rapid pace, I can't remember ever having trouble finding behavior.

Labels: , , , , , , ,




Friday, January 26, 2007

Class Definitions

One of the things that makes Ruby very interesting to me is the ability to do things that I previously could not do in C#. For example, Ruby has open classes so I can define behavior of a class in various places.

In this post I'll need a term to describe code written in Ruby that could be written in the same way using Java or C#. Since I worked primarily with C# before Ruby I'll use R#.

I'll start with a fairly easy example where I have a class that has some class and instance methods and some of the class methods need to be protected.
# R# version
class Navigator
class << self
def standard_navigator
self.new standard_path
end

def promo_navigator
self.new promotion_path
end

protected
def standard_path
[:index, :account_creation, :terms_of_service, :check_out]
end

def promotion_path
[:promo_landing, :account_creation, :check_out]
end
end

def initialize(path)
@path = path
@current = 0
end

def next
@current += 1
@path[@current]
end
end
The above example is a fairly standard implementation that puts the class methods at the top, public above the protected, and then the instance methods below. A possible problem with the above code is that if you don't have the class << self code visible when viewing the file (because you've scrolled down) it is not immediately obvious that the methods are class methods. You could argue that the using the class << self is the problem; however, it's the only clean way I've found that allows you to define protected class methods.

An alternative way to define the class can be found below.
class Navigator
def initialize(path)
@path = path
@current = 0
end

def next
@current += 1
@path[@current]
end
end

class << Navigator
def standard_navigator
self.new standard_path
end

def promo_navigator
self.new promotion_path
end

protected
def standard_path
[:index, :account_creation, :terms_of_service, :check_out]
end

def promotion_path
[:promo_landing, :account_creation, :check_out]
end
end
Is this actually better? I'm not sure, but I'd be willing to try it out.

Here's another example. I want a class that has three attributes (properties in C#), is able to initialize these properties in the constructor or default them, and contains some behavior.
# R# version
class PhoneNumber
attr_accessor :area_code, :exchange, :station

def initialize(area_code=nil, exchange=nil, station=nil)
@area_code, @exchange, @station = area_code, exchange, station
end

def formatted_number
"(#{area_code}) #{exchange} #{station}"
end
end
The above code is easy enough to read, but Ruby already has a class that encapsulates the attribute and constructor initialization pattern: Struct. The above code can also be written like below.
PhoneNumber = Struct.new :area_code, :exchange, :station

class PhoneNumber
def formatted_number
"(#{area_code}) #{exchange} #{station}"
end
end
Less readable? Perhaps, but is it if you know what behavior Struct.new encapsulates?

Here's another example in the context of a test.
# R# version
class ValidatableTest < Test::Unit::TestCase
class StubClass
include Validatable
attr_accessor :name
validates_presence_of :name
end

test "when a name is empty, then the instance is invalid" do
assert_equal false, StubClass.new.valid?
end
end
While the above code works, it begins to break down when you need multiple stub classes. You can use names such as StubClass2, StubClass3 or look for more descriptive names but when you are testing similar but slightly different situations it can be very hard to come up with good names. It is also less desirable to have the class defined outside the scope of the test that uses the class.

An alternative solution allows you to use an unnamed class within the test that needs it.
class ValidatableTest < Test::Unit::TestCase
test "when a name is empty, then the instance is invalid" do
klass = Class.new
klass.class_eval do
include Validatable
attr_accessor :name
validates_presence_of :name
end
assert_equal false, klass.new.valid?
end
end
The above test is very explicit about what behavior can be expected from the class under test. The above test also ensures that if the test fails you wont need to go elsewhere to see any set up code. This example is the one where I have an opinion, and I much prefer the 2nd version.

Ruby allows you to add behavior to classes in many ways. Using a combination of the various ways we may be able to find more descriptive class definitions, or prove that the traditional ways are superior.

Labels: ,




This page is powered by Blogger. Isn't yours?