blockenspiel 0.2.2 → 0.3.0

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.
data/README.rdoc CHANGED
@@ -5,106 +5,29 @@ blocks. It is designed to be comprehensive and robust, supporting most common
5
5
  usage patterns, and working correctly in the presence of nested blocks and
6
6
  multithreading.
7
7
 
8
- === What's a DSL block?
8
+ === Summary
9
9
 
10
- A DSL block is an API pattern in which a method call takes a block that can
11
- provide further configuration for the call. A classic example is the
12
- {Rails}[http://www.rubyonrails.org/] route definition:
13
-
14
- ActionController::Routing::Routes.draw do |map|
15
- map.connect ':controller/:action/:id'
16
- map.connect ':controller/:action/:id.:format'
17
- end
18
-
19
- Some libraries go one step further and eliminate the need for a block
20
- parameter. {RSpec}[http://rspec.info/] is a well-known example:
21
-
22
- describe Stack do
23
- before(:each) do
24
- @stack = Stack.new
25
- end
26
- describe "(empty)" do
27
- it { @stack.should be_empty }
28
- it "should complain when sent #peek" do
29
- lambda { @stack.peek }.should raise_error(StackUnderflowError)
30
- end
31
- end
32
- end
33
-
34
- In both cases, the caller provides descriptive information in the block,
35
- using a domain-specific language. The second form, which eliminates the block
36
- parameter, often appears cleaner; however it is also sometimes less clear
37
- what is actually going on.
38
-
39
- === How does one implement such a beast?
40
-
41
- Implementing the first form is fairly straightforward. You would create a
42
- class defining the methods (such as +connect+ in our Rails routing example
43
- above) that should be available within the block. When, for example, the
44
- <tt>draw</tt> method is called with a block, you instantiate the class and
45
- yield it to the block.
46
-
47
- The second form is perhaps more mystifying. Somehow you would need to make
48
- the DSL methods available on the "self" object inside the block. There are
49
- several plausible ways to do this, such as using <tt>instance_eval</tt>.
50
- However, there are many subtle pitfalls in such techniques, and quite a bit
51
- of discussion has taken place in the Ruby community regarding how--or
52
- whether--to safely implement such a syntax.
53
-
54
- I have included a critical survey of the debate in the document
55
- {ImplementingDSLblocks.txt}[link:files/ImplementingDSLblocks\_txt.html] for
56
- the curious. Blockenspiel takes what I consider the best of the solutions and
57
- implements them in a comprehensive way, shielding you from the complexity of
58
- the Ruby metaprogramming while offering a simple way to implement both forms
59
- of DSL blocks.
60
-
61
- === So what _is_ Blockenspiel?
62
-
63
- Blockenspiel operates on the following observations:
64
-
65
- * Implementing a DSL block that takes a parameter is straightforward.
66
- * Safely implementing a DSL block that <em>doesn't</em> take a parameter is tricky.
67
-
68
- With that in mind, Blockenspiel provides a set of tools that allow you to
69
- take an implementation of the first form of a DSL block, one that takes a
70
- parameter, and turn it into an implementation of the second form, one that
71
- doesn't take a parameter.
72
-
73
- Suppose you wanted to write a simple DSL block that takes a parameter:
10
+ Blockenspiel is a helper library providing several different strategies for
11
+ implementing DSL blocks. It supports both DSLs that take a block parameter
12
+ and those that do not. For example:
74
13
 
14
+ # Call DSL block with parameter
75
15
  configure_me do |config|
76
16
  config.add_foo(1)
77
17
  config.add_bar(2)
78
18
  end
79
-
80
- You could write this as follows:
81
-
82
- class ConfigMethods
83
- def add_foo(value)
84
- # do something
85
- end
86
- def add_bar(value)
87
- # do something
88
- end
89
- end
90
19
 
91
- def configure_me
92
- yield ConfigMethods.new
93
- end
94
-
95
- That was easy. However, now suppose you wanted to support usage _without_
96
- the "config" parameter. e.g.
97
-
20
+ # Call DSL block without parameter
98
21
  configure_me do
99
- add_foo(1)
100
- add_bar(2)
22
+ add_foo(3)
23
+ add_bar(4)
101
24
  end
102
25
 
103
- With Blockenspiel, you can do this in two quick steps.
104
- First, tell Blockenspiel that your +ConfigMethods+ class is a DSL.
26
+ To support the above usage, you can do this:
105
27
 
28
+ # Implement DSL block methods
106
29
  class ConfigMethods
107
- include Blockenspiel::DSL # <--- Add this line
30
+ include Blockenspiel::DSL
108
31
  def add_foo(value)
109
32
  # do something
110
33
  end
@@ -112,179 +35,28 @@ First, tell Blockenspiel that your +ConfigMethods+ class is a DSL.
112
35
  # do something
113
36
  end
114
37
  end
115
-
116
- Next, write your <tt>configure_me</tt> method using Blockenspiel:
117
-
38
+
39
+ # Implement configure_me method
118
40
  def configure_me(&block)
119
41
  Blockenspiel.invoke(block, ConfigMethods.new)
120
42
  end
121
43
 
122
- Now, your <tt>configure_me</tt> method supports _both_ DSL block forms. A
123
- caller can opt to use the first form, with a parameter, simply by providing
124
- a block that takes a parameter. Or, if the caller provides a block that
125
- doesn't take a parameter, the second form without a parameter is used.
126
-
127
- === How does that help me? (Or, why not just use instance_eval?)
128
-
129
- As noted earlier, some libraries that provide parameter-less DSL blocks use
130
- <tt>instance_eval</tt>, and they could even support both the parameter and
131
- parameter-less mechanisms by checking the block arity:
132
-
133
- def configure_me(&block)
134
- if block.arity == 1
135
- yield ConfigMethods.new
136
- else
137
- ConfigMethods.new.instance_eval(&block)
138
- end
139
- end
140
-
141
- That seems like a simple and effective technique that doesn't require a
142
- separate library, so why use Blockenspiel? Because <tt>instance_eval</tt>
143
- introduces a number of surprising problems. I discuss these issues in detail
144
- in {ImplementingDSLblocks.txt}[link:files/ImplementingDSLblocks\_txt.html],
145
- but just to get your feet wet, suppose the caller wanted to call its own
146
- methods inside the block:
44
+ By default, Blockenspiel uses a mixin technique (proposed by the late Why
45
+ The Lucky Stiff) to support parameterless blocks without the complications
46
+ introduced by <tt>instance_eval</tt>. It supports nested blocks and
47
+ multithreaded access, and provides a variety of tools for handling the
48
+ typical issues you may encounter when writing DSLs.
147
49
 
148
- def callers_helper_method
149
- # ...
150
- end
151
-
152
- configure_me do
153
- add_foo(1)
154
- callers_helper_method # Error! self is now an instance of ConfigMethods
155
- # so this will fail with a NameError
156
- add_bar(2)
157
- end
158
-
159
- Blockenspiel by default does _not_ use the <tt>instance_eval</tt> technique.
160
- Instead, it implements a mechanism using mixin modules, a technique first
161
- {proposed}[http://hackety.org/2008/10/06/mixingOurWayOutOfInstanceEval.html]
162
- by Why. In this technique, the <tt>add_foo</tt> and <tt>add_bar</tt> methods
163
- are temporarily mixed into the caller's +self+ object. That is, +self+ does
164
- not change, as it would if we used <tt>instance_eval</tt>, so helper methods
165
- like <tt>callers_helper_method</tt> still remain available as expected. But,
166
- the <tt>add_foo</tt> and <tt>add_bar</tt> methods are also made available
167
- temporarily for the duration of the block. When called, they are intercepted
168
- and redirected to your +ConfigMethods+ instance just as if you had called
169
- them directly via a block parameter. Blockenspiel handles the object
170
- redirection behind the scenes so you do not have to think about it. With
171
- Blockenspiel, the caller retains access to its helper methods, and even its
172
- own instance variables, within the block, because +self+ has not been
173
- modified.
174
-
175
- === Is that it?
176
-
177
- Although the basic usage is very simple, Blockenspiel is designed to be
178
- _comprehensive_. It supports all the use cases that I've run into during my
179
- own implementation of DSL blocks. Notably:
180
-
181
- By default, Blockenspiel lets the caller choose to use a parametered block
182
- or a parameterless block, based on whether or not the block actually takes a
183
- parameter. You can also disable one or the other, to force the use of either
184
- a parametered or parameterless block.
185
-
186
- You can control wich methods of the class are available from parameterless
187
- blocks, and/or make some methods available under different names. Here are
188
- a few examples:
189
-
190
- class ConfigMethods
191
- include Blockenspiel::DSL
192
-
193
- def add_foo # automatically added to the dsl
194
- # do stuff...
195
- end
196
-
197
- def my_private_method
198
- # do stuff...
199
- end
200
- dsl_method :my_private_method, false # remove from the dsl
201
-
202
- dsl_methods false # stop automatically adding methods to the dsl
203
-
204
- def another_private_method # not added
205
- # do stuff...
206
- end
207
-
208
- dsl_methods true # resume automatically adding methods to the dsl
209
-
210
- def add_bar # this method is automatically added
211
- # do stuff...
212
- end
213
-
214
- def add_baz
215
- # do stuff
216
- end
217
- dsl_method :add_baz_in_dsl, :add_baz # Method named differently
218
- # in a parameterless block
219
- end
220
-
221
- This is also useful, for example, when you use <tt>attr_writer</tt>.
222
- Parameterless blocks do not support <tt>attr_writer</tt> (or, by corollary,
223
- <tt>attr_accessor</tt>) well because methods with names of the form
224
- "attribute=" are syntactically indistinguishable from variable assignments:
225
-
226
- configure_me do |config|
227
- config.foo = 1 # works fine when the block has a parameter
228
- end
229
-
230
- configure_me do
231
- # foo = 1 # <--- Doesn't work: looks like a variable assignment
232
- set_foo(1) # <--- Renamed to this instead
233
- end
234
-
235
- # This is implemented like this::
236
- class ConfigMethods
237
- include Blockenspiel::DSL
238
- attr_writer :foo
239
- dsl_method :set_foo, :foo= # Make "foo=" available as "set_foo"
240
- end
241
-
242
- In some cases, you might want to dynamically generate a DSL object rather
243
- than defining a static class. Blockenspiel provides a tool to do just that.
244
- Here's an example:
245
-
246
- Blockenspiel.invoke(block) do
247
- add_method(:set_foo) do |value|
248
- my_foo = value
249
- end
250
- add_method(:set_things_using_block, :receive_block => true) do |value, blk|
251
- my_foo = value
252
- my_bar = blk.call
253
- end
254
- end
255
-
256
- That API is in itself a DSL block, and yes, Blockenspiel uses itself to
257
- implement this feature.
258
-
259
- By default Blockenspiel uses mixins, which usually exhibit fairly safe and
260
- non-surprising behavior. However, there are a few cases when you might
261
- want the <tt>instance_eval</tt> behavior anyway. RSpec is a good example of
262
- such a case, since the DSL is being used to construct objects, so it makes
263
- sense for instance variables inside the block to belong to the object
264
- being constructed. Blockenspiel gives you the option of choosing
265
- <tt>instance_eval</tt> in case you need it. Blockenspiel also provides a
266
- compromise behavior that uses a proxy to dispatch methods to the DSL object
267
- or the block's context.
268
-
269
- Blockenspiel also correctly handles nested blocks. e.g.
270
-
271
- configure_me do
272
- set_foo(1)
273
- configure_another do # A block within another block
274
- set_bar(2)
275
- configure_another do # A block within itself
276
- set_bar(3)
277
- end
278
- end
279
- end
50
+ For more detailed usage and examples, see
51
+ {Blockenspiel.rdoc}[link:Blockenspiel\_rdoc.html].
280
52
 
281
- Finally, it is completely thread safe, correctly handling, for example, the
282
- case of multiple threads trying to mix methods into the same object
283
- concurrently.
53
+ For an extended analysis of different ways to implement DSL blocks, see
54
+ {ImplementingDSLblocks.rdoc}[link:ImplementingDSLblocks\_rdoc.html].
284
55
 
285
56
  === Requirements
286
57
 
287
- * Ruby 1.8.6 or later (1.8.7 recommended), Ruby 1.9.1 or later, or JRuby 1.2 or later (1.4 recommended).
58
+ * Ruby 1.8.6 or later (1.8.7 recommended), Ruby 1.9.1 or later, or JRuby 1.2
59
+ or later (1.4 recommended).
288
60
 
289
61
  === Installation
290
62
 
@@ -317,6 +89,8 @@ copies or mirrors out there.
317
89
  The unmixer code is based on {Mixology}[http://rubyforge.org/projects/mixology],
318
90
  version 0.1 by Patrick Farley, anonymous z, Dan Manges, and Clint Bishop.
319
91
  The code has been stripped down and modified to support MRI 1.9 and JRuby 1.2.
92
+ I know Mixology 0.2 is available, but I'm keeping the unmixer bundled with
93
+ Blockenspiel for now, to reduce dependencies.
320
94
 
321
95
  === License
322
96