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.
Mechanics
- Return self from methods you wish to allow chaining from
- Test
- Remove the local variable and chain the method calls
- Test
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.
@options ||= []
end
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.
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.
# ...
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".
select = self.new
select.options << option
select
end
@options ||= []
end
options << arg
self
end
end
select = Select.with_option(1999).and(2000).and(2001).and(2002)
select # => #<Select:0x28578 @options=[1999, 2000, 2001, 2002]>
I find that this is one of those places where I wish the ruby parser was a little smarter so I could write:
ReplyDeleteSelect.with_option(1999)
.and(2000)
.and(2001)
.and(2002)
rather than:
Select.with_option(1999) \
.and(2000) \
.and(2001) \
.and(2002)
Javascript gets it right after all.
Not sure if Object#tap in Ruby 1.9 makes fluent interface implementations any cleaner.
ReplyDeleteRuby parser will get it if you end with the dot
ReplyDeleteSelect.with_option(1999).
and(2000).
and(2001).
and(2002)
javascript gets it right because it has ; terminator.