ruby helpers for method chaining: tap, then, else
Ruby
Switch branches/tags
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
lib updated documentation and specs Nov 2, 2008
spec updated documentation and specs Nov 2, 2008
tasks update project helpers Nov 27, 2008
README readme hyperlink Mar 24, 2008
Rakefile use allison stylesheet Mar 24, 2008

README

== 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:
http://blog.thoughtfolder.com/2008-03-16-navigating-nil-method-chaining-in-ruby.html

== Author and License
Copyright (c) 2008 Greg Weber, http://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
http://github.com/gregwebs/methodchain/tree/master
=== repository
git clone git://github.com/gregwebs/methodchain.git

== Homepage
http://gregweber.info/projects/methodchain.html

== RDoc documentation
included with gem