public
Description: ruby helpers for method chaining: tap, then, else
Homepage: http://projects.gregweber.info/methodchain
Clone URL: git://github.com/gregwebs/methodchain.git
Greg Weber (author)
Wed Nov 26 17:53:17 -0800 2008
commit  f9ff210ecfb3668c27bd841d672bf27e43ef23ee
tree    92ae0b296fece2c765ed8513670f34a2b092e76d
parent  6dc628681807d25065021e1427540a4f11913a9c
methodchain / README
100644 182 lines (129 sloc) 4.836 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
== 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