Sunday, February 24, 2008
Ruby: Creating Anonymous Classes
Classes in Ruby are first-class objects—each is an instance of class Class. -- ruby-doc.orgMost classes are defined using the syntax of the following example.
...
endDefining 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
...
endClass.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.orgThe 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.
# implementation...
true
end
end
klass = Class.new do
include Gateway
end
assert_equal true, klass.new.post(1)
end
endThere 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: class, metaprogramming, ruby
Saturday, January 27, 2007
Class Reopening Hints
In my previous entry about Class Definitions Aman King left the following comment:
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.
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.
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.
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 SymbolThe 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.
def %(arg)
...
end
end
module SymbolExtensionGranted, this isn't a huge hint, but if you check
def %(arg)
...
end
end
Symbol.send :include, SymbolExtension
Symbol.ancestors you'll find the following list.SymbolMany people believe this is better than nothing, but the choice is yours.
SymbolExtension
Object
Kernel
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 SymbolThe 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.
def self.method_added(method)
raise caller.to_s if method == :%
end
end
class Symbol
def %(arg)
self
end
end
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.
- Define behavior for the class PhoneNumber (for example) in the phone_number.rb file
- If you need to open a class that you have not created, create a [Class]Extension module and include it as shown above.
- Avoid dynamically adding behavior in unexpected places when possible. For example we don't define behavior in associations, such as:
# not recommended
has_many :people do
def by_name
find(:all)
end
end
Labels: caller, class, extension, include, module, open, ruby, stack trace
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.
An alternative way to define the class can be found below.
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.
Here's another example in the context of a test.
An alternative solution allows you to use an unnamed class within the test that needs it.
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.
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# versionThe 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 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
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 NavigatorIs this actually better? I'm not sure, but I'd be willing to try it out.
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
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# versionThe 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.
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
PhoneNumber = Struct.new :area_code, :exchange, :stationLess readable? Perhaps, but is it if you know what behavior Struct.new encapsulates?
class PhoneNumber
def formatted_number
"(#{area_code}) #{exchange} #{station}"
end
end
Here's another example in the context of a test.
# R# versionWhile 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.
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
An alternative solution allows you to use an unnamed class within the test that needs it.
class ValidatableTest < Test::Unit::TestCaseThe 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.
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
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.


