Friday, September 12, 2008

Refactoring: Ruby Edition available on Safari

Refactoring: Ruby Edition is now available on Safari as a Rough Cut.

Labels: ,




Wednesday, July 30, 2008

Refactoring: Ruby Edition available on Amazon

It's been about 18 months since my first entry about Refactoring: Ruby Edition. It's been a long journey, but the book should be seeing day light in the next few months.

It's available for pre-order on Amazon now, and it should also be available on Safari in the near future.

Thanks to everyone who helped, and I hope the book is as fun to read as it was to write.

Labels: , ,




Wednesday, March 19, 2008

Ruby: Replace Temp with Chain

You have methods that can be chained for greater maintainability.

mock = Mock.new
expectation = mock.expects(:a_method_name)
expectation.with("arguments")
expectation.returns([1, :array])

becomes

mock = Mock.new
mock.expects(:a_method_name).with("arguments").returns([1, :array])


Motivation

Calling methods on different lines technically gets the job done, but at times it makes sense to chain method calls together and provide a more fluent interface. In the above examples, assigning an expectation to a local variable is only necessary so that the arguments and return value can be specified. The solution utilizing Method Chaining removes the need for the local variable. Method Chaining can also improve maintainability by providing an interface that allows you to compose code that reads naturally.

MechanicsExample

Suppose you were designing a library for creating html elements. This library would likely contain a method that created a select drop down and allowed you to add options to the select. The following code contains the Select class that could enable creating the example html and an example usage of the select class.

class Select
def options
@options ||= []
end

def add_option(arg)
options << arg
end
end

select = Select.new
select.add_option(1999)
select.add_option(2000)
select.add_option(2001)
select.add_option(2002)
select # => #<Select:0x28708 @options=[1999, 2000, 2001, 2002]>

The first step in creating a Method Chained solution is to create a method that creates the Select instance and adds an option.

class Select
def self.with_option(option)
select = self.new
select.options << option
select
end

# ...
end

select = Select.with_option(1999)
select.add_option(2000)
select.add_option(2001)
select.add_option(2002)
select # => #<Select:0x28488 @options=[1999, 2000, 2001, 2002]>

Next, change the method that adds options to return self so that it can be chained.

class Select
# ...

def add_option(arg)
options << arg
self
end
end

select = Select.with_option(1999).add_option(2000).add_option(2001).add_option(2002)
select # => #<Select:0x28578 @options=[1999, 2000, 2001, 2002]>

Finally, rename the add_option method to something that reads more fluently, such as "and".

class Select
def self.with_option(option)
select = self.new
select.options << option
select
end

def options
@options ||= []
end

def and(arg)
options << arg
self
end
end

select = Select.with_option(1999).and(2000).and(2001).and(2002)
select # => #<Select:0x28578 @options=[1999, 2000, 2001, 2002]>

Labels: ,




Tuesday, March 18, 2008

Ruby: Isolate Dynamic Receptor

Isolate Dynamic Receptor

A class utilizing method_missing has become painful to alter

Introduce a new class and move the method_missing logic to that class.

Motivation

As I previously mentioned, objects that use method_missing often raise NoMethodError errors unexpectedly, or worse you get no more information than: stack level too deep (SystemStackError).

Despite the added complexity, method_missing is a powerful tool that needs to be used when the interface of a class can not be predetermined. On those occasions I like to use Isolate Dynamic Receptor to limit the behavior of an object that also relies on method_missing.

The ActiveRecord::Base (AR::B) class defines method_missing to handle dynamic find messages. The implementation of method_missing allows you to send find messages that use attributes of a class as limiting conditions for the results that will be returned by the dynamic find messages. For example, given a Person subclass of AR::B that has both a first name and a ssn attribute it's possible to send the messages Person.find_by_first_name, Person.find_by_ssn, and Person.find_by_first_name_and_ssn.

It's possible, but not realistic to dynamically define methods for all possible combinations of the attributes of an AR::B subclass. Instead utilizing method_missing is a good solution; however, by defining method_missing on the AR::B class itself the complexity of the class is increased significantly. AR::B would benefit from a maintainability perspective if instead the dynamic finder logic were defined on a class whose single responsibility was to handle dynamic find messages. For example, the above Person class could support find with the following syntax: Person.find.by_first_name, Person.find.by_ssn, or Person.find.by_first_name_and_ssn

Note: very often it's possible to know all valid method calls ahead of time, in which case I prefer Replace Dynamic Receptor with Dynamically Define Method.

MechanicsExample

Here's a recorder class that records all calls to method_missing.

class Recorder
instance_methods.each do |meth|
undef_method meth unless meth =~ /^(__|inspect)/
end

def messages
@messages ||= []
end

def method_missing(sym, *args)
messages << [sym, args]
self
end
end

The recorder class may need additional behavior such as the ability to play back all the messages on an object and the ability to represent all the calls as strings.

class Recorder
def play_for(obj)
messages.inject(obj) do |result, message|
result.send message.first, *message.last
end
end

def to_s
messages.inject([]) do |result, message|
result << "#{message.first}(args: #{message.last.inspect})"
end.join(".")
end
end

As the behavior of Recorder grows it becomes harder to understand what messages are dynamically handled and what messages are actually explicitly defined. By design the functionality of method_missing should handle any unknown message, but how do you know if you've broken something by adding a explicitly defined method?

The solution to this problem is to introduce an additional class that has the single responsibility of handling the dynamic method calls. In this case we have a class Recorder that handles recording unknown messages as well as playing back the messages or printing them. To reduce complexity we will introduce the MessageCollector class that handles the method_missing calls.

class MessageCollector
instance_methods.each do |meth|
undef_method meth unless meth =~ /^(__|inspect)/
end

def messages
@messages ||= []
end

def method_missing(sym, *args)
messages << [sym, args]
self
end
end

The record method of Recorder will create a new instance of the MessageCollector class and each additional chained call will be recorded. The play back and printing capabilities will remain on the Recorder object.

class Recorder
def play_for(obj)
@message_collector.messages.inject(obj) do |result, message|
result.send message.first, *message.last
end
end

def record
@message_collector ||= MessageCollector.new
end

def to_s
@message_collector.messages.inject([]) do |result, message|
result << "#{message.first}(args: #{message.last.inspect})"
end.join(".")
end
end

Labels: , ,




Monday, March 17, 2008

Move eval from Run-time to Parse-time

You need to use eval, but want to limit the number of times eval is necessary.

class Person
def self.attr_with_default(options)
options.each_pair do |attribute, default_value|
define_method attribute do
eval "@#{attribute} ||= #{default_value}"
end
end
end

attr_with_default :emails => "[]", :employee_number => "EmployeeNumberGenerator.next"
end

becomes

class Person
def self.attr_with_default(options)
options.each_pair do |attribute, default_value|
eval "def #{attribute}
@#{attribute} ||= #{default_value}
end"

end
end

attr_with_default :emails => "[]", :employee_number => "EmployeeNumberGenerator.next"
end

Motivation
premature optimization is the root of all evil -- Knuth, Donald
I'll never advocate for premature optimization, but this refactoring can be helpful when you determine that eval is a source of performance pain. The Kernel#eval method can be the right solution in some cases; but it is almost always more expensive (in terms of performance) than it's alternatives. In the cases where eval is necessary, it's often better to move an eval call from run-time to parse-time.

MechanicsExample

The following Person class uses eval to define the logic the readers rely upon for returning a default value if no value has previously been set.

class Person
def self.attr_with_default(options)
options.each_pair do |attribute, default_value|
define_method attribute do
eval "@#{attribute} ||= #{default_value}"
end
end
end

attr_with_default :emails => "[]", :employee_number => "EmployeeNumberGenerator.next"
end

The above example executes without issue, but it relies upon eval each time a reader is called. If multiple calls to eval are determined to be problematic the solution is to expand the eval to include defining the method itself.

class Person
def self.attr_with_default(options)
options.each_pair do |attribute, default_value|
eval "def #{attribute}
@#{attribute} ||= #{default_value}
end"

end
end

attr_with_default :emails => "[]", :employee_number => "EmployeeNumberGenerator.next"
end

Labels: , ,




Thursday, February 28, 2008

Ruby: Replace method_missing with dynamic method definitions

You have methods you want to handle dynamically without the pain of debugging method_missing.

class Decorator
def initialize(subject)
@subject = subject
end

def method_missing(sym, *args, &block)
@subject.send sym, *args, &block
end
end

becomes

class Decorator
def initialize(subject)
subject.public_methods(false).each do |meth|
(class << self; self; end).class_eval do
define_method meth do |*args|
subject.send meth, *args
end
end
end
end
end

Motivation

Debugging classes that use method_missing can often be painful. At best you often get a NoMethodError on an object that you didn't expect, and at worst you get stack level too deep (SystemStackError).

There are times that method_missing is required. If the usage of an object is unknown, but must support unexpected method calls you may not be able to avoid the use of method_missing.

However, often you know how an object will be used and using Dynamically Define Method you can achieve the same behavior without relying on method_missing.

MechanicsExample: Dynamic delegation without method_missing

Delegation is a common task while developing software. Delegation can be handled explicitly by defining methods yourself or by utilizing something from the Ruby Standard Library such as Forwardable. Using these techniques gives you control over what methods you want to delegate to the subject object; however, sometimes you want to delegate all methods without specifying them. Ruby's Standard Library also provides this capability with the delegate library, but we'll assume we need to implement our own for this example.

The simple way to handle delegation (ignoring the fact that you would want to undefine all the standard methods a class gets by default) is to use method_missing to pass any method calls straight to the subject.

class Decorator
def initialize(subject)
@subject = subject
end

def method_missing(sym, *args, &block)
@subject.send sym, *args, &block
end
end

This solution does work, but it can be problematic when mistakes are made. For example, calling a method that does not exist on the subject will result in the subject raising a NoMethodError. Since the method call is being called on the decorator, but the subject is raising the error it may be painful to track down where the problem resides.

The wrong object raising a NoMethodError is significantly better than the dreaded stack level too deep (SystemStackError). This can be caused by something as simple as forgetting to use the subject instance variable and trying to use a non-existent subject method or any misspelled method. When this happens the only feedback you have is that something went wrong, but Ruby isn't sure exactly what it was.

These problems can be avoided entirely by using the available data to dynamically define methods at run time. The following example defines an instance method on the decorator for each public method of the subject.

class Decorator
def initialize(subject)
subject.public_methods(false).each do |meth|
(class << self; self; end).class_eval do
define_method meth do |*args|
subject.send meth, *args
end
end
end
end
end

Using this technique any invalid method calls will be correctly reported as NoMethodErrors on the decorator. Additionally, there's no method_missing definition, which should help avoid the stack level too deep problem entirely.

Example: Using user defined data to define methods

Often you can use the information from a class definition to define methods instead of relying on method_missing. For example, the following code relies on method_missing to determine if any of the attributes are nil.

class Person
attr_accessor :name, :age

def method_missing(sym, *args, &block)
empty?(sym.to_s.sub