methodchain

- ruby helpers for method chaining

Summary

methodchain - ruby helpers for method chaining: chain, tap, then, else, and, or

Easy ways to navigate around nil without creating local variables.

Initial blog post describing previous ideas: blog.thoughtfolder.com/2008-03-16-navigating-nil-method-chaining-in-ruby.html

Author and License

Copyright (c) 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}
              # or
              name = person.then {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. #else is the opposite of then, and the two methods can be used together

              'a'.then{'b'} #=> 'b'
              nil.then{'b'}.else{'c'} #=> 'c'
            

message sending

The normal conditional for #then and #else is self

              if self # inside MethodChain#then
                # evaluate block
              end
            

#then and #else allow message sending as the conditional. See more examples of message sending with the MethodChain#chain examples below

              "not empty".then(:empty?) {"N/A"} # => "not empty"
                       "".then(:empty?) {"N/A"} # => "N/A"
            

#and, #or

old way

Return a default value or the original value depending on whether multiple conditions are met

              Person = Struct.new(:phone )
              blank = Person.new('') # or {:phone => nil}
              blank.phone && (not blank.phone.empty?) ? blank.phone : "N/A" # => "N/A"
              p = Person.new('123')
              p.phone && (not p.phone.empty?) ? p.phone : "N/A" # => "123"
            

new way

              blank.phone.and {not empty?} || "N/A" # => "N/A"
              p.phone.and {not empty?} || "N/A" # => "123"
            

#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

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