blockenspiel 0.2.2-java → 0.3.0-java

Sign up to get free protection for your applications and to get access to all the features.
data/Blockenspiel.rdoc ADDED
@@ -0,0 +1,374 @@
1
+ == Blockenspiel
2
+
3
+ Blockenspiel is a helper library designed to make it easy to implement DSL
4
+ blocks. It is designed to be comprehensive and robust, supporting most
5
+ common usage patterns, and working correctly in the presence of nested
6
+ blocks and multithreading.
7
+
8
+ This is an introduction to DSL blocks and the features of Blockenspiel.
9
+
10
+ === What's a DSL block?
11
+
12
+ A DSL block is an API pattern in which a method call takes a block that can
13
+ provide further configuration for the call. A classic example is the
14
+ {Rails}[http://www.rubyonrails.org/] route definition:
15
+
16
+ ActionController::Routing::Routes.draw do |map|
17
+ map.connect ':controller/:action/:id'
18
+ map.connect ':controller/:action/:id.:format'
19
+ end
20
+
21
+ Some libraries go one step further and eliminate the need for a block
22
+ parameter. {RSpec}[http://rspec.info/] is a well-known example:
23
+
24
+ describe Stack do
25
+ before(:each) do
26
+ @stack = Stack.new
27
+ end
28
+ describe "(empty)" do
29
+ it { @stack.should be_empty }
30
+ it "should complain when sent #peek" do
31
+ lambda { @stack.peek }.should raise_error(StackUnderflowError)
32
+ end
33
+ end
34
+ end
35
+
36
+ In both cases, the caller provides descriptive information in the block,
37
+ using a domain-specific language. The second form, which eliminates the
38
+ block parameter, often appears cleaner; however it is also sometimes less
39
+ clear what is actually going on.
40
+
41
+ === How does one implement such a beast?
42
+
43
+ Implementing the first form is fairly straightforward. You would create a
44
+ class defining the methods (such as +connect+ in our Rails routing example
45
+ above) that should be available within the block. When, for example, the
46
+ <tt>draw</tt> method is called with a block, you instantiate the class and
47
+ yield it to the block.
48
+
49
+ The second form is perhaps more mystifying. Somehow you would need to make
50
+ the DSL methods available on the "self" object inside the block. There are
51
+ several plausible ways to do this, such as using <tt>instance_eval</tt>.
52
+ However, there are many subtle pitfalls in such techniques, and quite a bit
53
+ of discussion has taken place in the Ruby community regarding how--or
54
+ whether--to safely implement such a syntax.
55
+
56
+ I have included a critical survey of the discussion in the document
57
+ {ImplementingDSLblocks.rdoc}[link:ImplementingDSLblocks\_rdoc.html] for
58
+ the curious. Blockenspiel takes what I consider the best of the solutions
59
+ and implements them in a comprehensive way, shielding you from the
60
+ complexity of the Ruby metaprogramming while offering a simple way to
61
+ implement both forms of DSL blocks.
62
+
63
+ === So what _is_ Blockenspiel?
64
+
65
+ Blockenspiel operates on the following observations:
66
+
67
+ * Implementing a DSL block that takes a parameter is straightforward.
68
+ * Safely implementing a DSL block that <em>doesn't</em> take a parameter is
69
+ tricky.
70
+
71
+ With that in mind, Blockenspiel provides a set of tools that allow you to
72
+ take an implementation of the first form of a DSL block, one that takes a
73
+ parameter, and turn it into an implementation of the second form, one that
74
+ doesn't take a parameter.
75
+
76
+ Suppose you wanted to write a simple DSL block that takes a parameter:
77
+
78
+ configure_me do |config|
79
+ config.add_foo(1)
80
+ config.add_bar(2)
81
+ end
82
+
83
+ You could write this as follows:
84
+
85
+ class ConfigMethods
86
+ def add_foo(value)
87
+ # do something
88
+ end
89
+ def add_bar(value)
90
+ # do something
91
+ end
92
+ end
93
+
94
+ def configure_me
95
+ yield ConfigMethods.new
96
+ end
97
+
98
+ That was easy. However, now suppose you wanted to support usage _without_
99
+ the "config" parameter. e.g.
100
+
101
+ configure_me do
102
+ add_foo(1)
103
+ add_bar(2)
104
+ end
105
+
106
+ With Blockenspiel, you can do this in two quick steps.
107
+ First, tell Blockenspiel that your +ConfigMethods+ class is a DSL.
108
+
109
+ class ConfigMethods
110
+ include Blockenspiel::DSL # <--- Add this line
111
+ def add_foo(value)
112
+ # do something
113
+ end
114
+ def add_bar(value)
115
+ # do something
116
+ end
117
+ end
118
+
119
+ Next, write your <tt>configure_me</tt> method using Blockenspiel:
120
+
121
+ def configure_me(&block)
122
+ Blockenspiel.invoke(block, ConfigMethods.new)
123
+ end
124
+
125
+ Now, your <tt>configure_me</tt> method supports _both_ DSL block forms. A
126
+ caller can opt to use the first form, with a parameter, simply by providing
127
+ a block that takes a parameter. Or, if the caller provides a block that
128
+ doesn't take a parameter, the second form without a parameter is used.
129
+
130
+ === How does that help me? (Or, why not just use instance_eval?)
131
+
132
+ As noted earlier, some libraries that provide parameter-less DSL blocks use
133
+ <tt>instance_eval</tt>, and they could even support both the parameter and
134
+ parameter-less mechanisms by checking the block arity:
135
+
136
+ def configure_me(&block)
137
+ if block.arity == 1
138
+ yield ConfigMethods.new
139
+ else
140
+ ConfigMethods.new.instance_eval(&block)
141
+ end
142
+ end
143
+
144
+ That seems like a simple and effective technique that doesn't require a
145
+ separate library, so why use Blockenspiel? Because <tt>instance_eval</tt>
146
+ introduces a number of surprising problems. I discuss these issues in detail
147
+ in {ImplementingDSLblocks.rdoc}[link:ImplementingDSLblocks\_rdoc.html],
148
+ but just to get your feet wet, suppose the caller wanted to call its own
149
+ methods inside the block:
150
+
151
+ def callers_helper_method
152
+ # ...
153
+ end
154
+
155
+ configure_me do
156
+ add_foo(1)
157
+ callers_helper_method # Error! self is now an instance of ConfigMethods
158
+ # so this will fail with a NameError
159
+ add_bar(2)
160
+ end
161
+
162
+ Blockenspiel by default does _not_ use the <tt>instance_eval</tt> technique.
163
+ Instead, it implements a mechanism using mixin modules, a technique proposed
164
+ by the late {Why}[http://en.wikipedia.org/wiki/Why_the_lucky_stiff]. In this
165
+ technique, the <tt>add_foo</tt> and <tt>add_bar</tt> methods are temporarily
166
+ mixed into the caller's +self+ object. That is, +self+ does not change, as
167
+ it would if we used <tt>instance_eval</tt>, so helper methods like
168
+ <tt>callers_helper_method</tt> still remain available as expected. But, the
169
+ <tt>add_foo</tt> and <tt>add_bar</tt> methods are also made available
170
+ temporarily for the duration of the block. When called, they are intercepted
171
+ and redirected to your +ConfigMethods+ instance just as if you had called
172
+ them directly via a block parameter. Blockenspiel handles the object
173
+ redirection behind the scenes so you do not have to think about it. With
174
+ Blockenspiel, the caller retains access to its helper methods, and even its
175
+ own instance variables, within the block, because +self+ has not been
176
+ modified.
177
+
178
+ === Is that it?
179
+
180
+ Although the basic usage is very simple, Blockenspiel is designed to be
181
+ _comprehensive_. It supports all the use cases that I've run into during my
182
+ own implementation of DSL blocks. Notably:
183
+
184
+ By default, Blockenspiel lets the caller choose to use a parametered block
185
+ or a parameterless block, based on whether or not the block actually takes a
186
+ parameter. You can also disable one or the other, to force the use of either
187
+ a parametered or parameterless block.
188
+
189
+ You can control wich methods of the class are available from parameterless
190
+ blocks, and/or make some methods available under different names. Here are
191
+ a few examples:
192
+
193
+ class ConfigMethods
194
+ include Blockenspiel::DSL
195
+
196
+ def add_foo # automatically added to the dsl
197
+ # do stuff...
198
+ end
199
+
200
+ def my_private_method
201
+ # do stuff...
202
+ end
203
+ dsl_method :my_private_method, false # remove from the dsl
204
+
205
+ dsl_methods false # stop automatically adding methods to the dsl
206
+
207
+ def another_private_method # not added
208
+ # do stuff...
209
+ end
210
+
211
+ dsl_methods true # resume automatically adding methods to the dsl
212
+
213
+ def add_bar # this method is automatically added
214
+ # do stuff...
215
+ end
216
+
217
+ def add_baz
218
+ # do stuff
219
+ end
220
+ dsl_method :add_baz_in_dsl, :add_baz # Method named differently
221
+ # in a parameterless block
222
+ end
223
+
224
+ This is also useful, for example, when you use <tt>attr_writer</tt>.
225
+ Parameterless blocks do not support <tt>attr_writer</tt> (or, by corollary,
226
+ <tt>attr_accessor</tt>) well because methods with names of the form
227
+ "attribute=" are syntactically indistinguishable from variable assignments:
228
+
229
+ configure_me do |config|
230
+ config.foo = 1 # works fine when the block has a parameter
231
+ end
232
+
233
+ configure_me do
234
+ # foo = 1 # <--- Doesn't work: looks like a variable assignment
235
+ set_foo(1) # <--- Fix it by renaming to this instead
236
+ end
237
+
238
+ # This is implemented like this::
239
+ class ConfigMethods
240
+ include Blockenspiel::DSL
241
+ attr_writer :foo
242
+ dsl_method :set_foo, :foo= # Make "foo=" available as "set_foo"
243
+ end
244
+
245
+ This is in fact a common enough case that Blockenspiel includes
246
+ conveninence tools for a DSL-friendly attr_writer and attr_accessor,
247
+ providing an alternate syntax for setting attributes within a
248
+ parameterless block:
249
+
250
+ configure_me do
251
+ # foo = 1 # This syntax wouldn't work, but
252
+ foo 1 # this syntax is now supported.
253
+ puts "foo is #{foo}" # The getter still works.
254
+ end
255
+
256
+ # This is implemented like this::
257
+ class ConfigMethods
258
+ include Blockenspiel::DSL
259
+ dsl_attr_accessor :foo # DSL-friendly attr_accessor
260
+ end
261
+
262
+ In some cases, you might want to dynamically generate a DSL object rather
263
+ than defining a static class. Blockenspiel provides a tool to do just that.
264
+ Here's an example:
265
+
266
+ Blockenspiel.invoke(block) do
267
+ add_method(:set_foo) do |value|
268
+ my_foo = value
269
+ end
270
+ add_method(:set_things_using_block) do |value, &blk|
271
+ my_foo = value
272
+ my_bar = blk.call
273
+ end
274
+ end
275
+
276
+ That API is itself a DSL block, and yes, Blockenspiel uses itself to
277
+ implement this feature.
278
+
279
+ By default Blockenspiel uses mixins, which usually exhibit fairly safe and
280
+ non-surprising behavior. However, there are a few cases when you might
281
+ want the <tt>instance_eval</tt> behavior anyway. RSpec is a good example of
282
+ such a case, since the DSL is being used to construct objects, so it makes
283
+ sense for instance variables inside the block to belong to the object
284
+ being constructed. Blockenspiel gives you the option of choosing
285
+ <tt>instance_eval</tt> in case you need it. Blockenspiel also provides a
286
+ compromise behavior that uses a proxy to dispatch methods to the DSL object
287
+ or the block's context.
288
+
289
+ Blockenspiel also correctly handles nested blocks. e.g.
290
+
291
+ configure_me do
292
+ set_foo(1)
293
+ configure_another do # A block within another block
294
+ set_bar(2)
295
+ configure_another do # A block within itself
296
+ set_bar(3)
297
+ end
298
+ end
299
+ end
300
+
301
+ Finally, it is thread safe, correctly handling, for example, the case of
302
+ multiple threads trying to mix methods into the same object concurrently.
303
+
304
+ === Requirements
305
+
306
+ * Ruby 1.8.6 or later (1.8.7 recommended), Ruby 1.9.1 or later, or JRuby 1.2
307
+ or later (1.4 recommended).
308
+
309
+ === Installation
310
+
311
+ gem install blockenspiel
312
+
313
+ === Known issues and limitations
314
+
315
+ * Implementing wildcard DSL methods using <tt>method_missing</tt> doesn't
316
+ work. I haven't yet figured out the right semantics for this case.
317
+
318
+ === Development and support
319
+
320
+ Documentation is available at http://virtuoso.rubyforge.org/blockenspiel/README_rdoc.html
321
+
322
+ Source code is hosted on Github at http://github.com/dazuma/blockenspiel
323
+
324
+ Report bugs on Github issues at http://github.org/dazuma/blockenspiel/issues
325
+
326
+ Contact the author at dazuma at gmail dot com.
327
+
328
+ === Author / Credits
329
+
330
+ Blockenspiel is written by Daniel Azuma (http://www.daniel-azuma.com/).
331
+
332
+ The mixin implementation is based on a concept by the late Why The Lucky
333
+ Stiff, documented in his 6 October 2008 blog posting entitled "Mixing Our
334
+ Way Out Of Instance Eval?". The original link is gone, but you may find
335
+ copies or mirrors out there.
336
+
337
+ The unmixer code is based on {Mixology}[http://rubyforge.org/projects/mixology],
338
+ version 0.1 by Patrick Farley, anonymous z, Dan Manges, and Clint Bishop.
339
+ The code has been stripped down and modified to support MRI 1.9 and JRuby 1.2.
340
+ I know Mixology 0.2 is available, but I'm keeping the unmixer bundled with
341
+ Blockenspiel for now, to reduce dependencies.
342
+
343
+ The dsl_attr_writer and dsl_attr_accessor feature came from a suggestion by
344
+ Luis Lavena.
345
+
346
+ === License
347
+
348
+ Copyright 2008-2009 Daniel Azuma.
349
+
350
+ All rights reserved.
351
+
352
+ Redistribution and use in source and binary forms, with or without
353
+ modification, are permitted provided that the following conditions are met:
354
+
355
+ * Redistributions of source code must retain the above copyright notice,
356
+ this list of conditions and the following disclaimer.
357
+ * Redistributions in binary form must reproduce the above copyright notice,
358
+ this list of conditions and the following disclaimer in the documentation
359
+ and/or other materials provided with the distribution.
360
+ * Neither the name of the copyright holder, nor the names of any other
361
+ contributors to this software, may be used to endorse or promote products
362
+ derived from this software without specific prior written permission.
363
+
364
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
365
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
366
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
367
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
368
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
369
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
370
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
371
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
372
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
373
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
374
+ POSSIBILITY OF SUCH DAMAGE.
data/History.rdoc CHANGED
@@ -1,3 +1,12 @@
1
+ === 0.3.0 / 2009-11-04
2
+
3
+ * dsl_attr_writer and dsl_attr_accessor convenience methods are available
4
+ for creating DSL-friendly attributes.
5
+ * Dynamic DSL methods can now take real block arguments, if supported by
6
+ the Ruby interpreter.
7
+ * Shortened README.rdoc and renamed the longer version to Blockenspiel.rdoc.
8
+ * Some documentation updates.
9
+
1
10
  === 0.2.2 / 2009-10-28
2
11
 
3
12
  * Support for gemcutter hosting in the build/release scripts.