docile 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 75054a395059cd55ba3132c00dc49ae15cc6b954
4
- data.tar.gz: 71f6114e6ac5d727240bd50c8e967127af688a53
3
+ metadata.gz: 57d90aa5d94c06fab36bcf2ad9c058e8b3c7ec3b
4
+ data.tar.gz: c73f0d3af5a1d87fcc3ae562e2c90bdd7e06b724
5
5
  SHA512:
6
- metadata.gz: 57476b49adfb0e49f909c391420f6c7b1fdacd2fcf5aaced53443bd3c138ca6ca70ba047b261c9c20dc899a1a35e70950d3582f0dc267d33a2d5748dfd53aee1
7
- data.tar.gz: 46b4708db7b9cb68b3f1415edb0f07d5dcc49ba6d86831a51b96e1774abcea346aa3f7412e106572d32893f2473ff7ccddae0596470efad85a1c08a1d737b91c
6
+ metadata.gz: 2ed37d882ad062a8ae1dc02963582c57ed51383b7916176964a55ad4833ee34e93a98743382d527113554f8cef5774ef96e8e681396de00b11bfdcb4bda80bef
7
+ data.tar.gz: a000461e5ffe9649801875b2d0228d1a5d61510813713a90fe5cfb4aa594abaaa52e7f2554ec57113eb0b679712f39be469664ccbe82f0ed9afbb9c1f1324d8b
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/HISTORY.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # HISTORY
2
2
 
3
+ ## [v1.1.0 (Jul 29, 2013)](http://github.com/ms-ati/docile/compare/v1.0.5...v1.1.0)
4
+
5
+ - add functional-style DSL objects via `Docile#dsl_eval_immutable`
6
+
3
7
  ## [v1.0.5 (Jul 28, 2013)](http://github.com/ms-ati/docile/compare/v1.0.4...v1.0.5)
4
8
 
5
9
  - achieve 100% yard docs coverage
data/README.md CHANGED
@@ -1,19 +1,21 @@
1
1
  # Docile
2
-
3
- Definition: *Ready to accept control or instruction; submissive* [[1]]
4
-
5
- Tired of overly complex DSL libraries and hairy meta-programming?
6
-
7
- Let's make our Ruby DSLs more docile...
8
-
9
- [1]: http://www.google.com/search?q=docile+definition "Google"
10
-
11
2
  [![Gem Version](https://badge.fury.io/rb/docile.png)](http://badge.fury.io/rb/docile)
12
3
  [![Build Status](https://travis-ci.org/ms-ati/docile.png)](https://travis-ci.org/ms-ati/docile)
13
4
  [![Dependency Status](https://gemnasium.com/ms-ati/docile.png)](https://gemnasium.com/ms-ati/docile)
14
5
  [![Code Climate](https://codeclimate.com/github/ms-ati/docile.png)](https://codeclimate.com/github/ms-ati/docile)
15
6
  [![Coverage Status](https://coveralls.io/repos/ms-ati/docile/badge.png)](https://coveralls.io/r/ms-ati/docile)
16
7
 
8
+ Ruby makes it possible to create very expressive **Domain Specific
9
+ Languages**, or **DSL**'s for short. However, it requires some deep knowledge and
10
+ somewhat hairy meta-programming to get the interface just right.
11
+
12
+ "Docile" means *Ready to accept control or instruction; submissive* [[1]]
13
+
14
+ Instead of each Ruby project reinventing this wheel, let's make our Ruby DSL
15
+ coding a bit more docile...
16
+
17
+ [1]: http://www.google.com/search?q=docile+definition "Google"
18
+
17
19
  ## Basic Usage
18
20
 
19
21
  Let's say that we want to make a DSL for modifying Array objects.
@@ -26,12 +28,12 @@ with_array([]) do
26
28
  pop
27
29
  push 3
28
30
  end
29
- # => [1, 3]
31
+ #=> [1, 3]
30
32
  ```
31
33
 
32
34
  No problem, just define the method `with_array` like this:
33
35
 
34
- ``` ruby
36
+ ```ruby
35
37
  def with_array(arr=[], &block)
36
38
  Docile.dsl_eval(arr, &block)
37
39
  end
@@ -53,7 +55,7 @@ pizza do
53
55
  pepperoni
54
56
  sauce @sauce_level
55
57
  end
56
- # => #<Pizza:0x00001009dc398 @cheese=true, @pepperoni=true, @bacon=false, @sauce=:extra>
58
+ #=> #<Pizza:0x00001009dc398 @cheese=true, @pepperoni=true, @bacon=false, @sauce=:extra>
57
59
  ```
58
60
 
59
61
  And let's say we have a PizzaBuilder, which builds a Pizza like this:
@@ -83,7 +85,7 @@ def pizza(&block)
83
85
  end
84
86
  ```
85
87
 
86
- It's just that easy!
88
+ It's just that easy!
87
89
 
88
90
  [2]: http://stackoverflow.com/questions/328496/when-would-you-use-the-builder-pattern "Builder Pattern"
89
91
 
@@ -147,12 +149,49 @@ end
147
149
 
148
150
  [3]: http://www.sinatrarb.com "Sinatra"
149
151
 
152
+ ## Functional-Style DSL Objects
153
+
154
+ Sometimes, you want to use an object as a DSL, but it doesn't quite fit the
155
+ [imperative](http://en.wikipedia.org/wiki/Imperative_programming) pattern shown
156
+ above.
157
+
158
+ Instead of methods like
159
+ [Array#push](http://www.ruby-doc.org/core-2.0/Array.html#method-i-push), which
160
+ modifies the object at hand, it has methods like
161
+ [String#reverse](http://www.ruby-doc.org/core-2.0/String.html#method-i-reverse),
162
+ which returns a new object without touching the original. Perhaps it's even
163
+ [frozen](http://www.ruby-doc.org/core-2.0/Object.html#method-i-freeze) in
164
+ order to enforce [immutability](http://en.wikipedia.org/wiki/Immutable_object).
165
+
166
+ Wouldn't it be great if we could just treat these methods as a DSL as well?
167
+
168
+ ```ruby
169
+ with_immutable_string("I'm immutable!".freeze) do
170
+ reverse
171
+ upcase
172
+ end
173
+ #=> "!ELBATUMMI M'I"
174
+ ```
175
+
176
+ No problem, just define the method `with_immutable_string` like this:
177
+
178
+ ```ruby
179
+ def with_immutable_string(str="", &block)
180
+ Docile.dsl_eval_immutable(str, &block)
181
+ end
182
+ ```
183
+
184
+ All set!
185
+
150
186
  ## Features
151
187
 
152
- 1. method lookup falls back from the DSL object to the block's context
153
- 2. local variable lookup falls back from the DSL object to the block's context
154
- 3. instance variables are from the block's context only
155
- 4. nested dsl evaluation
188
+ 1. Method lookup falls back from the DSL object to the block's context
189
+ 2. Local variable lookup falls back from the DSL object to the block's
190
+ context
191
+ 3. Instance variables are from the block's context only
192
+ 4. Nested DSL evaluation, correctly chaining method and variable handling
193
+ from the inner to the outer DSL scopes
194
+ 5. Alternatives for both imperative and functional styles of DSL objects
156
195
 
157
196
  ## Installation
158
197
 
@@ -173,12 +212,14 @@ Version 1.0.x works on [all ruby versions since 1.8.7](https://github.com/ms-ati
173
212
  ## Note on Patches/Pull Requests
174
213
 
175
214
  * Fork the project.
176
- * Setup your development environment with: gem install bundler; bundle install
215
+ * Setup your development environment with:
216
+ `gem install bundler; bundle install`
177
217
  * Make your feature addition or bug fix.
178
- * Add tests for it. This is important so I don't break it in a
179
- future version unintentionally.
218
+ * Add tests for it. This is important so I don't break it in a future version
219
+ unintentionally.
180
220
  * Commit, do not mess with rakefile, version, or history.
181
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
221
+ (if you want to have your own version, that is fine but bump version in a
222
+ commit by itself I can ignore when I pull)
182
223
  * Send me a pull request. Bonus points for topic branches.
183
224
 
184
225
  ## Copyright
@@ -1,9 +1,12 @@
1
1
  require "docile/version"
2
+ require "docile/execution"
2
3
  require "docile/fallback_context_proxy"
4
+ require "docile/chaining_fallback_context_proxy"
3
5
 
4
- # Docile keeps your Ruby DSLs tame and well-behaved
5
- # @see http://ms-ati.github.com/docile/
6
+ # Docile keeps your Ruby DSLs tame and well-behaved.
6
7
  module Docile
8
+ extend Execution
9
+
7
10
  # Execute a block in the context of an object whose methods represent the
8
11
  # commands in a DSL.
9
12
  #
@@ -11,8 +14,9 @@ module Docile
11
14
  #
12
15
  # Use this method to execute an *imperative* DSL, which means that:
13
16
  #
14
- # 1. each command mutates the state of the DSL context object
15
- # 2. the return values of the commands are ignored
17
+ # 1. Each command mutates the state of the DSL context object
18
+ # 2. The return value of each command is ignored
19
+ # 3. The final return value is the original context object
16
20
  #
17
21
  # @example Use a String as a DSL
18
22
  # Docile.dsl_eval("Hello, world!") do
@@ -36,21 +40,45 @@ module Docile
36
40
  # `dsl` context object
37
41
  # @return [Object] the `dsl` context object after executing the block
38
42
  def dsl_eval(dsl, *args, &block)
39
- block_context = eval("self", block.binding)
40
- proxy_context = FallbackContextProxy.new(dsl, block_context)
41
- begin
42
- block_context.instance_variables.each do |ivar|
43
- value_from_block = block_context.instance_variable_get(ivar)
44
- proxy_context.instance_variable_set(ivar, value_from_block)
45
- end
46
- proxy_context.instance_exec(*args, &block)
47
- ensure
48
- block_context.instance_variables.each do |ivar|
49
- value_from_dsl_proxy = proxy_context.instance_variable_get(ivar)
50
- block_context.instance_variable_set(ivar, value_from_dsl_proxy)
51
- end
52
- end
43
+ exec_in_proxy_context(dsl, FallbackContextProxy, *args, &block)
53
44
  dsl
54
45
  end
55
46
  module_function :dsl_eval
47
+
48
+ # Execute a block in the context of an immutable object whose methods,
49
+ # and the methods of their return values, represent the commands in a DSL.
50
+ #
51
+ # @note Use with a *functional* DSL (commands return successor
52
+ # context objects)
53
+ #
54
+ # Use this method to execute a *functional* DSL, which means that:
55
+ #
56
+ # 1. The original DSL context object is never mutated
57
+ # 2. Each command returns the next DSL context object
58
+ # 3. The final return value is the value returned by the last command
59
+ #
60
+ # @example Use a frozen String as a DSL
61
+ # Docile.dsl_eval_immutable("I'm immutable!".freeze) do
62
+ # reverse
63
+ # upcase
64
+ # end
65
+ # #=> "!ELBATUMMI M'I"
66
+ #
67
+ # @example Use a Float as a DSL
68
+ # Docile.dsl_eval_immutable(84.5) do
69
+ # fdiv(2)
70
+ # floor
71
+ # end
72
+ # #=> 42
73
+ #
74
+ # @param dsl [Object] immutable context object whose methods make up the
75
+ # initial DSL
76
+ # @param args [Array] arguments to be passed to the block
77
+ # @yield the block of DSL commands to be executed against the
78
+ # `dsl` context object and successor return values
79
+ # @return [Object] the return value of the final command in the block
80
+ def dsl_eval_immutable(dsl, *args, &block)
81
+ exec_in_proxy_context(dsl, ChainingFallbackContextProxy, *args, &block)
82
+ end
83
+ module_function :dsl_eval_immutable
56
84
  end
@@ -0,0 +1,20 @@
1
+ require "docile/fallback_context_proxy"
2
+
3
+ module Docile
4
+ # @api private
5
+ #
6
+ # Operates in the same manner as {FallbackContextProxy}, but replacing
7
+ # the primary `receiver` object with the result of each proxied method.
8
+ #
9
+ # This is useful for implementing DSL evaluation for immutable context
10
+ # objects.
11
+ #
12
+ # @see Docile#dsl_eval_immutable
13
+ class ChainingFallbackContextProxy < FallbackContextProxy
14
+ # Proxy methods as in {FallbackContextProxy#method_missing}, replacing
15
+ # `receiver` with the returned value.
16
+ def method_missing(method, *args, &block)
17
+ @__receiver__ = super(method, *args, &block)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ module Docile
2
+ # @api private
3
+ #
4
+ # A namespace for functions relating to the execution of a block agsinst a
5
+ # proxy object.
6
+ module Execution
7
+ # Execute a block in the context of an object whose methods represent the
8
+ # commands in a DSL, using a specific proxy class.
9
+ #
10
+ # @param dsl [Object] context object whose methods make up the
11
+ # (initial) DSL
12
+ # @param proxy_type [FallbackContextProxy,
13
+ # ChainingFallbackContextProxy]
14
+ # class to instantiate as the proxy context
15
+ # @param args [Array] arguments to be passed to the block
16
+ # @yield the block of DSL commands to be executed
17
+ # @return [Object] the return value of the block
18
+ def exec_in_proxy_context(dsl, proxy_type, *args, &block)
19
+ block_context = eval("self", block.binding)
20
+ proxy_context = proxy_type.new(dsl, proxy_type.new(dsl, block_context))
21
+ begin
22
+ block_context.instance_variables.each do |ivar|
23
+ value_from_block = block_context.instance_variable_get(ivar)
24
+ proxy_context.instance_variable_set(ivar, value_from_block)
25
+ end
26
+ proxy_context.instance_exec(*args, &block)
27
+ ensure
28
+ block_context.instance_variables.each do |ivar|
29
+ value_from_dsl_proxy = proxy_context.instance_variable_get(ivar)
30
+ block_context.instance_variable_set(ivar, value_from_dsl_proxy)
31
+ end
32
+ end
33
+ end
34
+ module_function :exec_in_proxy_context
35
+ end
36
+ end
@@ -1,12 +1,18 @@
1
1
  require 'set'
2
2
 
3
3
  module Docile
4
+ # @api private
5
+ #
4
6
  # A proxy object with a primary receiver as well as a secondary
5
7
  # fallback receiver.
6
8
  #
7
9
  # Will attempt to forward all method calls first to the primary receiver,
8
10
  # and then to the fallback receiver if the primary does not handle that
9
11
  # method.
12
+ #
13
+ # This is useful for implementing DSL evaluation in the context of an object.
14
+ #
15
+ # @see Docile#dsl_eval
10
16
  class FallbackContextProxy
11
17
  # The set of methods which will **not** be proxied, but instead answered
12
18
  # by this object directly.
@@ -1,4 +1,4 @@
1
1
  module Docile
2
2
  # The current version of this library
3
- VERSION = "1.0.5"
3
+ VERSION = "1.1.0"
4
4
  end
@@ -2,24 +2,36 @@ require "spec_helper"
2
2
 
3
3
  describe Docile do
4
4
 
5
- context :dsl_eval do
5
+ describe "#dsl_eval" do
6
+
7
+ context "when DSL context object is an Array" do
8
+ let(:array) { [] }
9
+ let!(:result) { execute_dsl_against_array }
10
+
11
+ def execute_dsl_against_array
12
+ Docile.dsl_eval(array) do
13
+ push 1
14
+ push 2
15
+ pop
16
+ push 3
17
+ end
18
+ end
6
19
 
7
- it "should return the DSL object" do
8
- Docile.dsl_eval([]) do
9
- push 1
10
- push 2
11
- pop
12
- push 3
13
- end.should == [1, 3]
14
- end
20
+ it "executes the block against the DSL context object" do
21
+ array.should == [1, 3]
22
+ end
15
23
 
16
- it "should use the __id__ method of the proxy object" do
17
- arr = []
18
- Docile.dsl_eval(arr) { __id__.should_not == arr.__id__ }
19
- end
24
+ it "returns the DSL object after executing block against it" do
25
+ result.should == array
26
+ end
27
+
28
+ it "doesn't proxy #__id__" do
29
+ Docile.dsl_eval(array) { __id__.should_not == array.__id__ }
30
+ end
20
31
 
21
- it "should raise NoMethodError if the DSL object doesn't implement the method" do
22
- expect { Docile.dsl_eval([]) { no_such_method } }.to raise_error(NoMethodError)
32
+ it "raises NoMethodError if the DSL object doesn't implement the method" do
33
+ expect { Docile.dsl_eval(array) { no_such_method } }.to raise_error(NoMethodError)
34
+ end
23
35
  end
24
36
 
25
37
  Pizza = Struct.new(:cheese, :pepperoni, :bacon, :sauce)
@@ -34,13 +46,22 @@ describe Docile do
34
46
  end
35
47
  end
36
48
 
37
- it "should handle a Builder pattern" do
38
- @sauce = :extra
39
- Docile.dsl_eval(PizzaBuilder.new) do
40
- bacon
41
- cheese
42
- sauce @sauce
43
- end.build.should == Pizza.new(true, false, true, :extra)
49
+ context "when DSL context object is a Builder pattern" do
50
+ let(:builder) { PizzaBuilder.new }
51
+ let(:result) { execute_dsl_against_builder_and_call_build }
52
+
53
+ def execute_dsl_against_builder_and_call_build
54
+ @sauce = :extra
55
+ Docile.dsl_eval(builder) do
56
+ bacon
57
+ cheese
58
+ sauce @sauce
59
+ end.build
60
+ end
61
+
62
+ it "returns correctly built object" do
63
+ result.should == Pizza.new(true, false, true, :extra)
64
+ end
44
65
  end
45
66
 
46
67
  class InnerDSL
@@ -65,12 +86,12 @@ describe Docile do
65
86
  Docile.dsl_eval(OuterDSL.new, &block)
66
87
  end
67
88
 
68
- def parameterized(*args, &block)
69
- Docile.dsl_eval(OuterDSL.new, *args, &block)
70
- end
89
+ context "when given parameters for the DSL block" do
90
+ def parameterized(*args, &block)
91
+ Docile.dsl_eval(OuterDSL.new, *args, &block)
92
+ end
71
93
 
72
- context "parameters" do
73
- it "should pass parameters to the block" do
94
+ it "passes parameters to the block" do
74
95
  parameterized(1,2,3) do |x,y,z|
75
96
  x.should == 1
76
97
  y.should == 2
@@ -78,11 +99,11 @@ describe Docile do
78
99
  end
79
100
  end
80
101
 
81
- it "should find parameters before methods" do
102
+ it "finds parameters before methods" do
82
103
  parameterized(1) { |a| a.should == 1 }
83
104
  end
84
105
 
85
- it "should find outer parameters in inner dsl scope" do
106
+ it "find outer dsl parameters in inner dsl scope" do
86
107
  parameterized(1,2,3) do |a,b,c|
87
108
  inner_with_params(c) do |d,e|
88
109
  a.should == 1
@@ -95,140 +116,207 @@ describe Docile do
95
116
  end
96
117
  end
97
118
 
98
- context "methods" do
99
- it "should find method of outer dsl in outer dsl scope" do
100
- outer { a.should == 'a' }
101
- end
119
+ context "when DSL blocks are nested" do
102
120
 
103
- it "should find method of inner dsl in inner dsl scope" do
104
- outer { inner { b.should == 'b' } }
105
- end
121
+ context "method lookup" do
122
+ it "finds method of outer dsl in outer dsl scope" do
123
+ outer { a.should == 'a' }
124
+ end
106
125
 
107
- it "should find method of outer dsl in inner dsl scope" do
108
- outer { inner { a.should == 'a' } }
109
- end
126
+ it "finds method of inner dsl in inner dsl scope" do
127
+ outer { inner { b.should == 'b' } }
128
+ end
110
129
 
111
- it "should find method of block's context in outer dsl scope" do
112
- def c; 'c'; end
113
- outer { c.should == 'c' }
114
- end
130
+ it "finds method of outer dsl in inner dsl scope" do
131
+ outer { inner { a.should == 'a' } }
132
+ end
115
133
 
116
- it "should find method of block's context in inner dsl scope" do
117
- def c; 'c'; end
118
- outer { inner { c.should == 'c' } }
119
- end
134
+ it "finds method of block's context in outer dsl scope" do
135
+ def c; 'c'; end
136
+ outer { c.should == 'c' }
137
+ end
138
+
139
+ it "finds method of block's context in inner dsl scope" do
140
+ def c; 'c'; end
141
+ outer { inner { c.should == 'c' } }
142
+ end
120
143
 
121
- it "should find method of outer dsl in preference to block context" do
122
- def a; 'not a'; end
123
- outer { a.should == 'a' }
124
- outer { inner { a.should == 'a' } }
144
+ it "finds method of outer dsl in preference to block context" do
145
+ def a; 'not a'; end
146
+ outer { a.should == 'a' }
147
+ outer { inner { a.should == 'a' } }
148
+ end
125
149
  end
126
- end
127
150
 
128
- context "local variables" do
129
- it "should find local variable from block context in outer dsl scope" do
130
- foo = 'foo'
131
- outer { foo.should == 'foo' }
151
+ context "local variable lookup" do
152
+ it "finds local variable from block context in outer dsl scope" do
153
+ foo = 'foo'
154
+ outer { foo.should == 'foo' }
155
+ end
156
+
157
+ it "finds local variable from block definition in inner dsl scope" do
158
+ bar = 'bar'
159
+ outer { inner { bar.should == 'bar' } }
160
+ end
132
161
  end
133
162
 
134
- it "should find local variable from block definition in inner dsl scope" do
135
- bar = 'bar'
136
- outer { inner { bar.should == 'bar' } }
163
+ context "instance variable lookup" do
164
+ it "finds instance variable from block definition in outer dsl scope" do
165
+ @iv1 = 'iv1'; outer { @iv1.should == 'iv1' }
166
+ end
167
+
168
+ it "proxies instance variable assignments in block in outer dsl scope back into block's context" do
169
+ @iv1 = 'foo'; outer { @iv1 = 'bar' }; @iv1.should == 'bar'
170
+ end
171
+
172
+ it "finds instance variable from block definition in inner dsl scope" do
173
+ @iv2 = 'iv2'; outer { inner { @iv2.should == 'iv2' } }
174
+ end
175
+
176
+ it "proxies instance variable assignments in block in inner dsl scope back into block's context" do
177
+ @iv2 = 'foo'; outer { inner { @iv2 = 'bar' } }; @iv2.should == 'bar'
178
+ end
137
179
  end
180
+
138
181
  end
139
182
 
140
- context "instance variables" do
141
- it "should find instance variable from block definition in outer dsl scope" do
142
- @iv1 = 'iv1'; outer { @iv1.should == 'iv1' }
183
+ context "when DSL context object is a Dispatch pattern" do
184
+ class DispatchScope
185
+ def params
186
+ { :a => 1, :b => 2, :c => 3 }
187
+ end
143
188
  end
144
189
 
145
- it "should proxy instance variable assignments in block in outer dsl scope back into block's context" do
146
- @iv1 = 'foo'; outer { @iv1 = 'bar' }; @iv1.should == 'bar'
147
- end
190
+ class MessageDispatch
191
+ include Singleton
192
+
193
+ def initialize
194
+ @responders = {}
195
+ end
196
+
197
+ def add_responder path, &block
198
+ @responders[path] = block
199
+ end
148
200
 
149
- it "should find instance variable from block definition in inner dsl scope" do
150
- @iv2 = 'iv2'; outer { inner { @iv2.should == 'iv2' } }
201
+ def dispatch path, request
202
+ Docile.dsl_eval(DispatchScope.new, request, &@responders[path])
203
+ end
151
204
  end
152
205
 
153
- it "should proxy instance variable assignments in block in inner dsl scope back into block's context" do
154
- @iv2 = 'foo'; outer { inner { @iv2 = 'bar' } }; @iv2.should == 'bar'
206
+ def respond(path, &block)
207
+ MessageDispatch.instance.add_responder(path, &block)
155
208
  end
156
- end
157
209
 
158
- class DispatchScope
159
- def params
160
- { :a => 1, :b => 2, :c => 3 }
210
+ def send_request(path, request)
211
+ MessageDispatch.instance.dispatch(path, request)
161
212
  end
162
- end
163
213
 
164
- class MessageDispatch
165
- include Singleton
214
+ it "dispatches correctly" do
215
+ @first = @second = nil
166
216
 
167
- def initialize
168
- @responders = {}
169
- end
217
+ respond '/path' do |request|
218
+ @first = request
219
+ end
170
220
 
171
- def add_responder path, &block
172
- @responders[path] = block
173
- end
221
+ respond '/new_bike' do |bike|
222
+ @second = "Got a new #{bike}"
223
+ end
224
+
225
+ def x(y) ; "Got a #{y}"; end
226
+ respond '/third' do |third|
227
+ x(third).should == 'Got a third thing'
228
+ end
229
+
230
+ fourth = nil
231
+ respond '/params' do |arg|
232
+ fourth = params[arg]
233
+ end
234
+
235
+ send_request '/path', 1
236
+ send_request '/new_bike', 'ten speed'
237
+ send_request '/third', 'third thing'
238
+ send_request '/params', :b
174
239
 
175
- def dispatch path, request
176
- Docile.dsl_eval(DispatchScope.new, request, &@responders[path])
240
+ @first.should == 1
241
+ @second.should == 'Got a new ten speed'
242
+ fourth.should == 2
177
243
  end
178
- end
179
244
 
180
- def respond path, &block
181
- MessageDispatch.instance.add_responder path, &block
182
245
  end
183
246
 
184
- def send_request path, request
185
- MessageDispatch.instance.dispatch path, request
186
- end
247
+ end
187
248
 
188
- it "should handle the dispatch pattern" do
189
- @first = @second = nil
190
- respond '/path' do |request|
191
- @first = request
192
- end
249
+ describe "#dsl_eval_immutable" do
250
+
251
+ context "when DSL context object is a frozen String" do
252
+ let(:original) { "I'm immutable!".freeze }
253
+ let!(:result) { execute_non_mutating_dsl_against_string }
193
254
 
194
- respond '/new_bike' do |bike|
195
- @second = "Got a new #{bike}"
255
+ def execute_non_mutating_dsl_against_string
256
+ Docile.dsl_eval_immutable(original) do
257
+ reverse
258
+ upcase
259
+ end
196
260
  end
197
261
 
198
- def x(y) ; "Got a #{y}"; end
199
- respond '/third' do |third|
200
- x(third).should == 'Got a third thing'
262
+ it "doesn't modify the original string" do
263
+ original.should == "I'm immutable!"
201
264
  end
202
265
 
203
- fourth = nil
204
- respond '/params' do |arg|
205
- fourth = params[arg]
266
+ it "chains the commands in the block against the DSL context object" do
267
+ result.should == "!ELBATUMMI M'I"
206
268
  end
269
+ end
207
270
 
208
- send_request '/path', 1
209
- send_request '/new_bike', 'ten speed'
210
- send_request '/third', 'third thing'
211
- send_request '/params', :b
271
+ context "when DSL context object is a number" do
272
+ let(:original) { 84.5 }
273
+ let!(:result) { execute_non_mutating_dsl_against_number }
212
274
 
213
- @first.should == 1
214
- @second.should == 'Got a new ten speed'
215
- fourth.should == 2
216
- end
275
+ def execute_non_mutating_dsl_against_number
276
+ Docile.dsl_eval_immutable(original) do
277
+ fdiv(2)
278
+ floor
279
+ end
280
+ end
217
281
 
282
+ it "chains the commands in the block against the DSL context object" do
283
+ result.should == 42
284
+ end
285
+ end
218
286
  end
219
287
 
220
- context "FallbackContextProxy#instance_variables" do
288
+ end
221
289
 
222
- it "should preserve the class (String or Symbol) normally returned by current ruby version" do
223
- @a = 1
224
- expected_type = instance_variables.first.class
290
+ describe Docile::FallbackContextProxy do
291
+
292
+ describe "#instance_variables" do
293
+ subject { create_fcp_and_set_one_instance_variable.instance_variables }
294
+ let(:expected_type_of_names) { type_of_ivar_names_on_this_ruby }
295
+ let(:actual_type_of_names) { subject.first.class }
296
+ let(:excluded) { Docile::FallbackContextProxy::NON_PROXIED_INSTANCE_VARIABLES }
225
297
 
298
+ def create_fcp_and_set_one_instance_variable
226
299
  fcp = Docile::FallbackContextProxy.new(nil, nil)
227
300
  fcp.instance_variable_set(:@foo, "foo")
301
+ fcp
302
+ end
228
303
 
229
- fcp.instance_variables.first.class.should == expected_type
304
+ def type_of_ivar_names_on_this_ruby
305
+ @a = 1
306
+ instance_variables.first.class
230
307
  end
231
308
 
309
+ it "returns proxied instance variables" do
310
+ subject.map(&:to_sym).should include(:@foo)
311
+ end
312
+
313
+ it "doesn't return non-proxied instance variables" do
314
+ subject.map(&:to_sym).should_not include(*excluded)
315
+ end
316
+
317
+ it "preserves the type (String or Symbol) of names on this ruby version" do
318
+ actual_type_of_names.should == expected_type_of_names
319
+ end
232
320
  end
233
321
 
234
322
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docile
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc Siegel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-07-28 00:00:00.000000000 Z
11
+ date: 2013-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -104,6 +104,7 @@ extra_rdoc_files: []
104
104
  files:
105
105
  - .coveralls.yml
106
106
  - .gitignore
107
+ - .rspec
107
108
  - .ruby-gemset
108
109
  - .ruby-version
109
110
  - .travis.yml
@@ -115,6 +116,8 @@ files:
115
116
  - Rakefile
116
117
  - docile.gemspec
117
118
  - lib/docile.rb
119
+ - lib/docile/chaining_fallback_context_proxy.rb
120
+ - lib/docile/execution.rb
118
121
  - lib/docile/fallback_context_proxy.rb
119
122
  - lib/docile/version.rb
120
123
  - spec/docile_spec.rb