Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 132 lines (83 sloc) 7.003 kb
a25e6ef better titles
Reg Braithwaite authored
1 The Enchaining Kestrel
f706640 the obdurate kestrel
Reg Braithwaite authored
2 ---
3
a25e6ef better titles
Reg Braithwaite authored
4 In [Kestrels](http://github.com/raganwald/homoiconic/tree/master/2008-10-29/kestrel.markdown), we looked at `#tap` from Ruby 1.9 and `returning` from Ruby on Rails. Today we're going to look at another use for `tap`.
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
5
6 [![Kestrel Composite (c) 2007 Mark Kilner](http://farm3.static.flickr.com/2165/1902016010_6f007bf3f0.jpg)](http://flickr.com/photos/markkilner/1902016010/"Kestrel Composite (c) 2007 Mark Kilner")
f706640 the obdurate kestrel
Reg Braithwaite authored
7
8 As already explained, Ruby 1.9 includes the new method `Object#tap`. It passes the receiver to a block, then returns the receiver no matter what the block contains. The canonical example inserts some logging in the middle of a chain of method invocations:
9
10 address = Person.find(...).tap { |p| logger.log "person #{p} found" }.address
11
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
12 `Object#tap` is also useful when you want to execute several method on the same object without having to create a lot of temporary variables, a practice Martin Fowler calls [Method Chaining](http://martinfowler.com/dslwip/MethodChaining.html ""). Typically, you design such an object so that it returns itself in response to every modifier message. This allows you to write things like:
13
14 HardDrive.new.capacity(150).external.speed(7200)
15
16 Instead of:
17
18 hd = HardDrive.new
19 hd.capacity = 150
20 hd.external = true
21 hd.speed = 7200
22
23 And if you are a real fan of the Kestrel, you would design your class with an object initializer block so you could write:
24
25 hd = HardDrive.new do
26 @capacity = 150
27 @external = true
28 @speed = 7200
29 end
30
31 But what do you do when handed a class that was not designed with method chaining in mind? For example, `Array#pop` returns the object being popped, not the array. Before you validate every criticism leveled against Ruby for allowing programmers to rewrite methods in core classes, consider using `#tap` with `Symbol#to_proc` or `String#to_proc` to chain methods without rewriting them.
32
33 So instead of
f706640 the obdurate kestrel
Reg Braithwaite authored
34
35 def fizz(arr)
36 arr.pop
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
37 arr.map! { |n| n * 2 }
f706640 the obdurate kestrel
Reg Braithwaite authored
38 end
39
40 We can write:
41
42 def fizz(arr)
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
43 arr.tap(&:pop).map! { |n| n * 2 }
f706640 the obdurate kestrel
Reg Braithwaite authored
44 end
45
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
46 I often use `#tap` to enchain methods for those pesky array methods that sometimes do what you expect and sometimes don't. My most hated example is [`Array#uniq!`](http://ruby-doc.org/core/classes/Array.html#M002238 "Class: Array"):
f706640 the obdurate kestrel
Reg Braithwaite authored
47
48 arr = [1,2,3,3,4,5]
49 arr.uniq, arr
50 => [1,2,3,4,5], [1,2,3,3,4,5]
51 arr = [1,2,3,3,4,5]
52 arr.uniq!, arr
53 => [1,2,3,4,5], [1,2,3,4,5]
54 arr = [1,2,3,4,5]
55 arr.uniq, arr
56 => [1,2,3,4,5], [1,2,3,4,5]
57 arr = [1,2,3,4,5]
58 arr.uniq!, arr
59 => nil, [1,2,3,4,5]
60
61 Let's replay that last one in slow motion:
62
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
63 [ 1, 2, 3, 4, 5 ].uniq!
f706640 the obdurate kestrel
Reg Braithwaite authored
64 => nil
65
66 That might be a problem. For example:
67
68 [1,2,3,4,5].uniq!.sort!
69 => NoMethodError: undefined method `sort!' for nil:NilClass
70
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
71 `Object#tap` to the rescue: When using a method like `#uniq!` that modifies the array in place and sometimes returns the modified array but sometimes helpfully returns `nil`, I can use `#tap` to make sure I always get the array, which allows me to enchain methods:
f706640 the obdurate kestrel
Reg Braithwaite authored
72
73 [1,2,3,4,5].tap(&:uniq!).sort!
74 => [1,2,3,4,5]
75
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
76 So there's another use for `#tap` (along with `Symbol#to_proc` for simple cases): We can use it when we want to enchain methods, but the methods do not return the receiver.
77
78 > In Ruby 1.9, `#tap` works exactly as described above. Ruby 1.8 does not have `#tap`, but you can obtain it by installing the andand gem. This version of `#tap` also works like a [quirky bird](http://github.com/raganwald/homoiconic/tree/master/2008-11-04/quirky_birds_and_meta_syntactic_programming.markdown ""), so you can write things like `HardDrive.new.tap.capacity(150)` for enchaining methods that take parameters and/or blocks. To get andand, `sudo gem install andand`. Rails users can also drop [andand.rb](http:andand.rb) in `config/initializers`.
f706640 the obdurate kestrel
Reg Braithwaite authored
79
a25e6ef better titles
Reg Braithwaite authored
80 The Obdurate Kestrel
81 ---
f706640 the obdurate kestrel
Reg Braithwaite authored
82
e6cd4c3 what good is a story about a kestrel without a picture?
Reg Braithwaite authored
83 [![Kestrel (c) 2007 The Hounds of Shadow](http://farm3.static.flickr.com/2402/2115973156_f4fcfca811.jpg)](http://flickr.com/photos/thehoundsofshadow/2115973156/"Kestrel (c) 2007 The Hounds of Shadow")
84
f706640 the obdurate kestrel
Reg Braithwaite authored
85 The [andand gem](http://github.com/raganwald/andand/tree "raganwald's andand") includes `Object#tap` for Ruby 1.8. It also includes another kestrel called `#dont`. Which does what it says, or rather *doesn't* do what it says.
86
87 :foo.tap { p 'bar' }
88 bar
89 => :foo # printed 'bar' before returning a value!
90
91 :foo.dont { p 'bar' }
92 => :foo # without printing 'bar'!
93
94 `Object#dont` simply ignores the block passed to it. So what is it good for? Well, remember our logging example for `#tap`?
95
96 address = Person.find(...).tap { |p| logger.log "person #{p} found" }.address
97
98 Let's turn the logging off for a moment:
99
100 address = Person.find(...).dont { |p| logger.log "person #{p} found" }.address
101
102 And back on:
103
104 address = Person.find(...).tap { |p| logger.log "person #{p} found" }.address
105
106 I typically use it when doing certain kinds of primitive debugging. And it has another trick up its sleeve:
107
108 arr.dont.sort!
109
110 Look at that, it works with method calls like a quirky bird! So you can use it to `NOOP` methods. Now, you could have done that with `Symbol#to_proc`:
111
112 arr.dont(&:sort!)
113
114 But what about methods that take parameters and blocks?
115
116 JoinBetweenTwoModels.dont.create!(...) do |new_join|
117 # ...
118 end
119
c195ca4 @raganwald
raganwald authored
120 `Object#dont` is the Ruby-semantic equivalent of commenting out a method call, only it can be inserted inside of an existing expression. That's why it's called the *obdurate kestrel*. It refuses to do anything!
f706640 the obdurate kestrel
Reg Braithwaite authored
121
7012e69 revised to include the enchaining idea
Reg Braithwaite authored
122 If you want to try `Object#dont`, or want to use `Object#tap` with Ruby 1.8, `sudo gem install andand`. Rails users can also drop [andand.rb](http:andand.rb) in `config/initializers` as mentioned above. Enjoy!
f706640 the obdurate kestrel
Reg Braithwaite authored
123
f820cdd updated link titles
Reg Braithwaite authored
124 _Our aviary so far_: [Kestrels](http://github.com/raganwald/homoiconic/tree/master/2008-10-29/kestrel.markdown), [The Thrush](http://github.com/raganwald/homoiconic/tree/master/2008-10-30/thrush.markdown), [Songs of the Cardinal](http://github.com/raganwald/homoiconic/tree/master/2008-10-31/songs_of_the_cardinal.markdown), [Quirky Birds and Meta-Syntactic Programming](http://github.com/raganwald/homoiconic/tree/master/2008-11-04/quirky_birds_and_meta_syntactic_programming.markdown), [Aspect-Oriented Programming in Ruby using Combinator Birds](http://github.com/raganwald/homoiconic/tree/master/2008-11-07/from_birds_that_compose_to_method_advice.markdown), and [The Enchaining and Obdurate Kestrels](http://github.com/raganwald/homoiconic/tree/master/2008-11-12/the_obdurate_kestrel.md).
74318fe updated the aviary
Reg Braithwaite authored
125
f706640 the obdurate kestrel
Reg Braithwaite authored
126 ---
127
128 [homoiconic](http://github.com/raganwald/homoiconic/tree/master "Homoiconic on GitHub")
129
130 Subscribe here to [a constant stream of updates](http://github.com/feeds/raganwald/commits/homoiconic/master "Recent Commits to homoiconic"), or subscribe here to [new posts and daily links only](http://feeds.feedburner.com/raganwald "raganwald's rss feed").
131
132 <a href="http://feeds.feedburner.com/raganwald"><img src="http://feeds.feedburner.com/~fc/raganwald?bg=&amp;fg=&amp;anim=" height="26" width="88" style="border:0" alt="" align="top"/></a>
Something went wrong with that request. Please try again.