Summary
methodchain - ruby helpers for method chaining: chain, tap, then, else Easy ways to navigate around nil without creating local variables.
Author and License
Copyright © 2008 Greg Weber, gregweber.info Licensed under the MIT license
Examples
##then and ##else
old way
person = nil
name = person ? person.name : nil
new way
name = person.then {|p| p.name}
not a huge savings. But sometimes the person variable is actually a function call, and then we must save it in a variable first.
old way
def find(*args)
# do some expensive database queries
end
person = find(:first)
@phone = person && person.phone # => nil
new way
@phone = find(:first).then {phone} # => nil
We have reduced a line of code and removed a local variable. #then and #else can return a default value instead of evaluating a block
'a'.then('b') #=> 'b'
nil.then('b').else('c') #=> 'c'
##tap
if you don’t already know about this method, look it up on the net. The tap included here allows message sending.
old way
arr = [1]
arr.compact! # => nil
arr.first # => 1
normal ##tap (still valid)
[1].tap {|arr| arr.compact!}.first # => 1
new ##tap
[1].tap(:compact!).first # => 1
normal ##tap (still valid)
[1].tap {|arr| arr.compact!}.tap {|arr| arr * 2}.first # => 1
new ##tap
[1].tap( :compact!, [:*, 2] ).first # => 1
You can also pass Procs as arguments
[1].tap( :compact!, lambda{|arr| arr * 2} ).first # => 1
##chain
chain is like tap, but instead of always returning self, it will return the result of the method call.
[1].chain(:first) == [1].first
But there is an important difference- chain guards against certain results (by default it guards against nil and false)
old way
customer = nil
customer && customer.order && customer.order.id
new way
customer.chain(:order, :id)
note that this is equivalent to
customer.then {order}.then {id}
##chain - Custom guards, multiple arguments, and Procs
old way - guarding against zero
value = 0
result = if value == 0 then value else
tmp = value.abs
if tmp == 0 then tmp else
tmp * 20
end
end
result # => 0
new way
value.chain(:abs, [:*, 20]) {|s| s == 0 } # => 0
Procs can be used, so this is equivalent to
value.chain(:abs, lambda {|n| n * 20 }) {|s| s == 0 } # => 0
Usage
require 'rubygems'
import all MethodChain methods into Object
require 'methodchain'
selectively import MethodChain methods
require 'methodchain/not-included'
You can then include methodchain into selected classes, or you can use the module-import gem to include only certain methods
gem install module-import
require 'module-import'
class Object
import MethodChain, :chain # I only want Object#chain
end
import will still load all the private methods from the module:
-
yield_or_eval
-
send_as_function
-
send_as_functions
not into Object
To import nothing into the Object namespace
require 'methodchain/no-import'
Then include where needed:
class MyObject
include MethodChain
end
Implementation
There are no proxy objects and no use of method_missing- these are simply function calls, so it should be fast.
private methods:
-
yield_or_eval: allows the two different block forms {|p| p.name} and name, where the first form yields self and the second form is called using instance_eval.
-
send_as_function: allows symbols and arrays to be sent as messages, and calls yield_or_eval on Proc arguments
-
send_as_functions:
def send_arguments_as_functions *args
args.each {|arg| send_as_function arg} self
end
Install
gem install methodchain
Source
browser
github.com/gregwebs/methodchain/tree/master
repository
git clone git://github.com/gregwebs/methodchain.git
Homepage
gregweber.info/projects/methodchain.html
RDoc documentation
included with gem