eager_beaver 0.0.3 → 0.0.4
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.
- data/README.md +176 -147
- data/lib/eager_beaver.rb +16 -16
- data/lib/eager_beaver/method_handler.rb +52 -0
- data/lib/eager_beaver/version.rb +1 -1
- data/spec/eager_beaver/context_spec.rb +9 -9
- data/spec/eager_beaver/includer_spec.rb +9 -9
- data/spec/eager_beaver/instance_spec.rb +18 -18
- metadata +2 -2
- data/lib/eager_beaver/method_matcher.rb +0 -60
data/README.md
CHANGED
@@ -5,12 +5,122 @@
|
|
5
5
|
`EagerBeaver` provides an interface for adding `#method_missing`-related abilities
|
6
6
|
to a class or module.
|
7
7
|
|
8
|
+
## Baseline Implementation
|
9
|
+
|
10
|
+
The following is a bare-bones implementation of class which defines `#method_missing`:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
class NeedsMethods
|
14
|
+
def method_missing(method_name, *args, &block)
|
15
|
+
if data = NeedsMethods.match_pattern1(method_name)
|
16
|
+
puts "pattern1: #{data[:val]}"
|
17
|
+
elsif data = NeedsMethods.match_pattern2(method_name)
|
18
|
+
puts "pattern2: #{data[:val1]} #{data[:val2]}"
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def respond_to_missing?(method_name, include_private=false)
|
25
|
+
NeedsMethods.match_pattern1(method_name) || NeedsMethods.match_pattern2(method_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.match_pattern1(method_name)
|
29
|
+
return {val: $1} if /\Apattern1_(\w+)/ =~ method_name
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.match_pattern2(method_name)
|
33
|
+
return {val1: $1, val2: $2} if /\Apattern2_(\w+)_(\w+)/ =~ method_name
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
nm1 = NeedsMethods.new
|
38
|
+
puts "#{nm1.methods.grep /pattern/}"
|
39
|
+
# => [] ## overriding #method_missing doesn't actually add methods
|
40
|
+
puts "#{nm1.respond_to? :pattern1_match}"
|
41
|
+
# => true ## #respond_to_missing? in action!
|
42
|
+
puts "#{nm1.method :pattern1_match}"
|
43
|
+
# => #<Method: NeedsMethods#pattern1_match> ## #respond_to_missing? in action!
|
44
|
+
nm1.pattern1_match
|
45
|
+
# => pattern1: match
|
46
|
+
|
47
|
+
nm2 = NeedsMethods.new
|
48
|
+
puts "#{nm2.methods.grep /pattern/}"
|
49
|
+
# => [] ## missing method was NOT added!
|
50
|
+
nm2.pattern1_match
|
51
|
+
# => pattern1: match
|
52
|
+
nm2.pattern2_another_match
|
53
|
+
# => pattern2: another match
|
54
|
+
puts "#{nm1.methods.grep /pattern/}"
|
55
|
+
# => [] ## missing methods were NOT added!
|
56
|
+
puts "#{nm2.methods.grep /pattern/}"
|
57
|
+
# => [] ## missing methods were NOT added!
|
58
|
+
|
59
|
+
nm.blah
|
60
|
+
# => undefined method `blah' for #<NeedsMethods:0x007fb37b086548> (NoMethodError)
|
61
|
+
```
|
62
|
+
|
63
|
+
## Downsides to the Baseline Implementation
|
64
|
+
|
65
|
+
### It's easy to forget something
|
66
|
+
|
67
|
+
Changes to `#method_missing` should be accompanied by corresponding changes to `#respond_to_missing?`,
|
68
|
+
which allows instances of a class to correcly respond to `#respond_to?` and `#method` calls. It's
|
69
|
+
easy to overlook this detail since it's likely not the primary focus of adding handlers to
|
70
|
+
`#method_missing`.
|
71
|
+
|
72
|
+
It's also easy to forget the call to `super` when no pattern match is found - in which case all
|
73
|
+
unmatched method patterns are silently handled!
|
74
|
+
|
75
|
+
### Extension requires changes in multiple places
|
76
|
+
|
77
|
+
To add handling of another method pattern, the following changes need to be made:
|
78
|
+
- addition of another `elsif` block in `#method_missing`
|
79
|
+
- addition of another `||`-ed value in `#respond_to_missing?`
|
80
|
+
- addition of another pattern-matching class method
|
81
|
+
|
82
|
+
### Large method size and method proliferation
|
83
|
+
|
84
|
+
As more and more method patterns are added, `#method_missing` and `#respond_to_missing?` will grow
|
85
|
+
endlessly, as will the number of pattern-matching class methods.
|
86
|
+
|
87
|
+
### Tight coupling
|
88
|
+
|
89
|
+
Pattern-matching class methods and their corresponding `elsif` blocks in `#method_missing` are
|
90
|
+
tightly coupled, despite their spatial separation in the code.
|
91
|
+
|
92
|
+
The shared code in `#method_missing` and `#respond_to_missing?` means that changes to one pattern
|
93
|
+
handler can break another if not done properly.
|
94
|
+
|
95
|
+
### Handled methods are not added to the class
|
96
|
+
|
97
|
+
Each time a matched method is called, the entire `#method_missing` infrastructure is executed.
|
98
|
+
|
99
|
+
### Dynamic updates
|
100
|
+
|
101
|
+
The baseline implementation assumes that all method patterns should be handled at all times,
|
102
|
+
which is not always the case. Sometimes the matched patterns are derived from data not
|
103
|
+
available to the class until the code is executing. Correctly redefining (or perpetually
|
104
|
+
re-aliasing) `#method_missing` and `#respond_to_missing?` can get tricky fast.
|
105
|
+
|
106
|
+
## Correcting the Downsides
|
107
|
+
|
108
|
+
Most of the downsides to the baseline implementation can be solved by adding an array
|
109
|
+
of `MethodHandler`s to the class. Each `MethodHandler` has two parts: one which checks
|
110
|
+
if the missing method should be handled, and one which does the work. `#method_missing`
|
111
|
+
and `#respond_to_missing?` could then be rewritten to iterate over the `MethodHandler`
|
112
|
+
array and act accordingly.
|
113
|
+
|
114
|
+
`EagerBeaver` does this (essentially) but goes one step further: it actually adds the
|
115
|
+
missing method to the including class and invokes it so that future calls to that
|
116
|
+
method won't need to invoke the `#method_missing` infrastructure.
|
117
|
+
|
8
118
|
## Key Features
|
9
119
|
|
10
|
-
- Method matchers can be added dynamically and
|
120
|
+
- Method matchers can be added dynamically and independently, reducing the risk
|
11
121
|
of accidentally altering or removing previously-added functionality.
|
12
122
|
- Matched methods are automatically reflected in calls to `#respond_to?` and
|
13
|
-
`#method
|
123
|
+
`#method`.
|
14
124
|
- Matched methods are automatically added to the including class/module and
|
15
125
|
invoked. Subsequent calls won't trigger `#method_missing`.
|
16
126
|
- When a method cannot be matched, `super`'s `#method_missing` is automatically
|
@@ -34,9 +144,8 @@ Or install it yourself as:
|
|
34
144
|
|
35
145
|
### Inclusion
|
36
146
|
|
37
|
-
Any class or module which includes `EagerBeaver` will gain the `
|
38
|
-
pseudo-keyword, which [indirectly] yields
|
39
|
-
given block:
|
147
|
+
Any class or module which includes `EagerBeaver` will gain the `add_method_handler`
|
148
|
+
pseudo-keyword, which [indirectly] yields a `MethodHandler` to the given block:
|
40
149
|
|
41
150
|
```ruby
|
42
151
|
require 'eager_beaver'
|
@@ -44,22 +153,22 @@ require 'eager_beaver'
|
|
44
153
|
class NeedsMethods
|
45
154
|
include EagerBeaver
|
46
155
|
|
47
|
-
|
156
|
+
add_method_handler do |mh|
|
48
157
|
...
|
49
158
|
end
|
50
159
|
end
|
51
160
|
```
|
52
161
|
|
53
|
-
In this case, the resulting `
|
162
|
+
In this case, the resulting `MethodHandler` is added to the end of a `MethodHandler` list
|
54
163
|
associated with `NeedsMethods`.
|
55
164
|
|
56
|
-
Each `
|
57
|
-
and a lambda for
|
165
|
+
Each `MethodHandler` needs two things: a lambda for matching missing method names
|
166
|
+
and a lambda for handling any method names it matches:
|
58
167
|
|
59
168
|
```ruby
|
60
|
-
|
61
|
-
|
62
|
-
|
169
|
+
add_method_handler do |mh|
|
170
|
+
mh.match = lambda { ... }
|
171
|
+
mh.handle = lambda { ... }
|
63
172
|
end
|
64
173
|
end
|
65
174
|
```
|
@@ -67,14 +176,12 @@ end
|
|
67
176
|
### Matching
|
68
177
|
|
69
178
|
The `match` lambda should return a true value if the missing method name is one
|
70
|
-
can be handled by the `
|
71
|
-
missing methods of the form `#
|
179
|
+
can be handled by the `MethodHandler`. The following example will match
|
180
|
+
missing methods of the form `#pattern1_<data>`:
|
72
181
|
|
73
182
|
```ruby
|
74
|
-
|
75
|
-
/\
|
76
|
-
context.attr_name = Regexp.last_match ? Regexp.last_match[1] : nil
|
77
|
-
return Regexp.last_match
|
183
|
+
mh.match = lambda {
|
184
|
+
context.data = $1 if /\Apattern1_(\w+)/ =~ context.missing_method_name
|
78
185
|
}
|
79
186
|
```
|
80
187
|
|
@@ -84,173 +191,95 @@ As the example shows, each `MethodMatcher` contains a `context` which provides:
|
|
84
191
|
|
85
192
|
- the name of the missing method (`context.missing_method_name`)
|
86
193
|
- the original method receiver instance (`context.original_receiver`)
|
87
|
-
- a place to stash information (`context.<attr_name>` and `context.<attr_name>=`)
|
194
|
+
- a place to stash information (dynamically-generated accessors `context.<attr_name>` and `context.<attr_name>=`)
|
88
195
|
|
89
|
-
This `context` is shared between the `match` and `
|
196
|
+
This `context` is shared between the `match` and `handle` lambdas, and
|
90
197
|
is reset between uses of each `MethodMatcher`.
|
91
198
|
|
92
|
-
###
|
199
|
+
### Handling
|
93
200
|
|
94
|
-
The `
|
201
|
+
The `handle` lambda should return a string which will create the
|
95
202
|
missing method in `NeedsMethods`:
|
96
203
|
|
97
204
|
```ruby
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
puts "\##{context.missing_method_name} was originally called on #{context.original_receiver}"
|
103
|
-
puts "#{context.attr_name} was passed from matching to code generation"
|
104
|
-
puts "the current call has arguments: \#{arg}"
|
105
|
-
return "result = \#{arg}"
|
106
|
-
end
|
107
|
-
}
|
108
|
-
return code
|
205
|
+
mh.handle = lambda {
|
206
|
+
%Q{ def #{context.missing_method_name}
|
207
|
+
puts "pattern1: #{context.data}"
|
208
|
+
end }
|
109
209
|
}
|
110
210
|
```
|
111
211
|
|
112
212
|
As the example shows, it is perfectly reasonable to take advantage of work done
|
113
|
-
by the `match` lambda (in this case, the parsing of `<
|
213
|
+
by the `match` lambda (in this case, the parsing of `<data>`).
|
114
214
|
|
115
215
|
After the generated code is inserted into `NeedsMethods`, the missing method
|
116
216
|
call is resent to the original receiver.
|
117
217
|
|
118
218
|
### Complete Example
|
119
219
|
|
220
|
+
The following is the baseline implementation above using `EagerBeaver`:
|
221
|
+
|
120
222
|
```ruby
|
121
223
|
require 'eager_beaver'
|
122
224
|
|
123
225
|
class NeedsMethods
|
124
226
|
include EagerBeaver
|
125
227
|
|
126
|
-
|
127
|
-
|
128
|
-
/\
|
129
|
-
context.attr_name = Regexp.last_match ? Regexp.last_match[1] : nil
|
130
|
-
return Regexp.last_match
|
228
|
+
add_method_handler do |mh|
|
229
|
+
mh.match = lambda {
|
230
|
+
context.data = $1 if /\Apattern1_(\w+)/ =~ context.missing_method_name
|
131
231
|
}
|
232
|
+
mh.handle = lambda {
|
233
|
+
%Q{ def #{context.missing_method_name}
|
234
|
+
puts "pattern1: #{context.data}"
|
235
|
+
end }
|
236
|
+
}
|
237
|
+
end
|
132
238
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
end
|
142
|
-
}
|
143
|
-
return code
|
239
|
+
add_method_handler do |mh|
|
240
|
+
mh.match = lambda {
|
241
|
+
context.data = {val1: $1, val2: $2} if /\Apattern2_(\w+)_(\w+)/ =~ context.missing_method_name
|
242
|
+
}
|
243
|
+
mh.handle = lambda {
|
244
|
+
%Q{ def #{context.missing_method_name}
|
245
|
+
puts "pattern2: #{context.data[:val1]} #{context.data[:val2]}"
|
246
|
+
end }
|
144
247
|
}
|
145
248
|
end
|
146
249
|
end
|
147
|
-
```
|
148
|
-
|
149
|
-
## Execution
|
150
|
-
|
151
|
-
Given the `NeedsMethods` class in the example above, let's work through the
|
152
|
-
following code:
|
153
250
|
|
154
|
-
```ruby
|
155
251
|
nm1 = NeedsMethods.new
|
156
|
-
puts nm1.make_thingy(10)
|
157
|
-
puts nm1.make_widget("hi")
|
158
|
-
|
159
|
-
nm2 = NeedsMethods.new
|
160
|
-
puts nm2.make_thingy(20)
|
161
|
-
puts nm2.make_widget("hello")
|
162
|
-
|
163
|
-
nm2.dont_make_this
|
164
|
-
```
|
165
|
-
|
166
|
-
As instances of `NeedsMethods`, `nm1` and `nm2` will automatically hande
|
167
|
-
methods of the form `#make_<attr_name>`.
|
168
|
-
|
169
|
-
The line:
|
170
|
-
```ruby
|
171
|
-
puts nm1.make_thingy(10)
|
172
|
-
```
|
173
|
-
will trigger `nm1`'s `#method_missing`, which `NeedsMethods` implements thanks to
|
174
|
-
`EagerBeaver`. Each `MethodMatcher` associated with `EagerBeaver` is run against
|
175
|
-
the method name `make_thingy`, and sure enough one matches. This causes the
|
176
|
-
following methods to be inserted to `NeedsMethods`:
|
177
|
-
```ruby
|
178
|
-
def make_thingy(arg)
|
179
|
-
puts "method #make_thingy has been called"
|
180
|
-
puts "#make_thingy was originally called on #<NeedsMethods:0x007fa1bc17f498>"
|
181
|
-
puts "thingy was passed from matching to code generation"
|
182
|
-
puts "the current call has arguments: #{arg}"
|
183
|
-
return "result = #{arg}"
|
184
|
-
end
|
185
|
-
```
|
186
|
-
and when `#make_thingy` is resent to `nm1`, the existing method is called and
|
187
|
-
outputs:
|
188
252
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
253
|
+
puts "#{nm1.methods.grep /pattern/}"
|
254
|
+
# => [] ## overriding #method_missing doesn't actually add methods
|
255
|
+
puts "#{nm1.respond_to? :pattern1_match}"
|
256
|
+
# => true ## #respond_to_missing? in action!
|
257
|
+
puts "#{nm1.method :pattern1_match}"
|
258
|
+
# => #<Method: NeedsMethods#pattern1_match> ## #respond_to_missing? in action!
|
259
|
+
nm1.pattern1_match
|
260
|
+
# => pattern1: match
|
194
261
|
|
195
|
-
|
196
|
-
```ruby
|
197
|
-
puts nm1.make_widget("hi")
|
198
|
-
```
|
199
|
-
generates the code:
|
200
|
-
```ruby
|
201
|
-
def make_widget(arg)
|
202
|
-
puts "method #make_widget has been called"
|
203
|
-
puts "#make_widget was originally called on #<NeedsMethods:0x007fa1bc17f498>"
|
204
|
-
puts "widget was passed from matching to code generation"
|
205
|
-
puts "the current call has arguments: #{arg}"
|
206
|
-
return "result = #{arg}"
|
207
|
-
end
|
208
|
-
```
|
209
|
-
and outputs:
|
210
|
-
> method \#make_widget has been called<br/>
|
211
|
-
> \#make_widget was originally called on \#\<NeedsMethods:0x007fa1bc17f498\><br/>
|
212
|
-
> widget was passed from matching to code generation<br/>
|
213
|
-
> the current call has arguments: hi<br/>
|
214
|
-
> result = hi
|
215
|
-
|
216
|
-
Note that the following lines do NOT trigger `#method_missing` because both methods
|
217
|
-
have already been added to `NeedsMethods`:
|
218
|
-
```ruby
|
219
|
-
puts nm2.make_thingy(20)
|
220
|
-
puts nm2.make_widget("hello")
|
221
|
-
```
|
222
|
-
This can be seen by examining the identity of the original receiver in the output:
|
223
|
-
|
224
|
-
> **method \#make_thingy has been called**<br/>
|
225
|
-
> **\#make_thingy was originally called on \#\<NeedsMethods:0x007fa1bc17f498\>**<br/>
|
226
|
-
> **thingy was passed from matching to code generation**<br/>
|
227
|
-
> the current call has arguments: 20<br/>
|
228
|
-
> result = 20
|
229
|
-
|
230
|
-
> **method \#make_widget has been called**<br/>
|
231
|
-
> **\#make_widget was originally called on \#\<NeedsMethods:0x007fa1bc17f498\>**<br/>
|
232
|
-
> **widget was passed from matching to code generation**<br/>
|
233
|
-
> the current call has arguments: hello<br/>
|
234
|
-
> result = hello
|
235
|
-
|
236
|
-
String substitutions which were part of the generated code body (emphasized)
|
237
|
-
reflect the circumstances of the first set of method calls, as opposed to
|
238
|
-
those which reflect the current call's argument.
|
262
|
+
nm2 = NeedsMethods.new
|
239
263
|
|
240
|
-
|
264
|
+
puts "#{nm2.methods.grep /pattern/}"
|
265
|
+
# => [:pattern1_match] ## missing method added to NeedsMethods!
|
266
|
+
nm2.pattern1_match
|
267
|
+
# => pattern1: match ## no call to #method_missing
|
268
|
+
nm2.pattern2_another_match
|
269
|
+
# => pattern2: another match
|
270
|
+
puts "#{nm1.methods.grep /pattern/}"
|
271
|
+
# => [:pattern1_match, :pattern2_another_match]
|
272
|
+
puts "#{nm2.methods.grep /pattern/}"
|
273
|
+
# => [:pattern1_match, :pattern2_another_match]
|
274
|
+
|
275
|
+
nm2.blah
|
276
|
+
# => undefined method `blah' for #<NeedsMethods:0x007fefac1a8080> (NoMethodError)
|
241
277
|
```
|
242
|
-
nm2.dont_make_this
|
243
|
-
```
|
244
|
-
will cause `NeedsMethods` to examine all of its `MethodMatcher`s and finally call
|
245
|
-
`super`'s `#method_missing`. Because no superclass of `NeedsMethods` handles
|
246
|
-
`#dont_make_this`, the output is:
|
247
|
-
|
248
|
-
> undefined method `dont_make_this' for \#\<NeedsMethods:0x007f8e2b991f90\> (NoMethodError)
|
249
278
|
|
250
279
|
## Contributing
|
251
280
|
|
252
281
|
1. Fork it
|
253
282
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
254
|
-
3.
|
283
|
+
3. Comhit your changes (`git commit -am 'Added some feature'`)
|
255
284
|
4. Push to the branch (`git push origin my-new-feature`)
|
256
285
|
5. Create new Pull Request
|
data/lib/eager_beaver.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require "eager_beaver/version"
|
2
|
-
require "eager_beaver/
|
2
|
+
require "eager_beaver/method_handler"
|
3
3
|
|
4
4
|
module EagerBeaver
|
5
5
|
|
@@ -8,10 +8,10 @@ module EagerBeaver
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def method_missing(method_name, *args, &block)
|
11
|
-
self.class.
|
12
|
-
|
13
|
-
if
|
14
|
-
method_string =
|
11
|
+
self.class.method_handlers.each do |method_handler|
|
12
|
+
mh = configure_handler method_handler
|
13
|
+
if mh.handles?(method_name)
|
14
|
+
method_string = mh.evaluate mh.handle
|
15
15
|
self.class.class_eval method_string, __FILE__, __LINE__ + 1
|
16
16
|
return self.send(method_name, *args, &block)
|
17
17
|
end
|
@@ -20,26 +20,26 @@ module EagerBeaver
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def respond_to_missing?(method_name, include_private=false)
|
23
|
-
self.class.
|
24
|
-
|
25
|
-
return true if
|
23
|
+
self.class.method_handlers.each do |method_handler|
|
24
|
+
mh = configure_handler method_handler
|
25
|
+
return true if mh.handles?(method_name)
|
26
26
|
end
|
27
27
|
super
|
28
28
|
end
|
29
29
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
def configure_handler(handler)
|
31
|
+
mh = handler.dup
|
32
|
+
mh.original_receiver = self
|
33
|
+
mh.original_receiver.class.context = mh
|
34
34
|
end
|
35
35
|
|
36
36
|
module ClassMethods
|
37
|
-
def
|
38
|
-
@
|
37
|
+
def method_handlers
|
38
|
+
@method_handlers ||= []
|
39
39
|
end
|
40
40
|
|
41
|
-
def
|
42
|
-
|
41
|
+
def add_method_handler(&block)
|
42
|
+
method_handlers << MethodHandler.new(&block)
|
43
43
|
end
|
44
44
|
|
45
45
|
attr_accessor :context
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module EagerBeaver
|
2
|
+
|
3
|
+
class MethodHandler
|
4
|
+
|
5
|
+
attr_accessor :original_receiver
|
6
|
+
attr_accessor :missing_method_name
|
7
|
+
attr_accessor :match
|
8
|
+
attr_accessor :handle
|
9
|
+
|
10
|
+
def initialize(&block)
|
11
|
+
block.call(self)
|
12
|
+
|
13
|
+
raise "match must be given" \
|
14
|
+
if match.nil?
|
15
|
+
raise "match must be a lambda" \
|
16
|
+
unless match.lambda?
|
17
|
+
|
18
|
+
raise "handle must be given" \
|
19
|
+
if handle.nil?
|
20
|
+
raise "handle must be a lambda" \
|
21
|
+
unless handle.lambda?
|
22
|
+
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def handles?(method_name)
|
27
|
+
self.missing_method_name = method_name.to_s
|
28
|
+
return evaluate(match)
|
29
|
+
end
|
30
|
+
|
31
|
+
def evaluate(inner)
|
32
|
+
outer = lambda { |*args|
|
33
|
+
args.shift
|
34
|
+
inner.call(*args)
|
35
|
+
}
|
36
|
+
self.instance_eval &outer
|
37
|
+
end
|
38
|
+
|
39
|
+
def method_missing(method_name, *args, &block)
|
40
|
+
if /\A(?<attr_name>[a-zA-Z]\w*)=?\z/ =~ method_name
|
41
|
+
code = %Q{
|
42
|
+
attr_accessor :#{attr_name}
|
43
|
+
}
|
44
|
+
self.singleton_class.instance_eval code, __FILE__, __LINE__ + 1
|
45
|
+
return self.send(method_name, *args, &block)
|
46
|
+
end
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/lib/eager_beaver/version.rb
CHANGED
@@ -7,13 +7,13 @@ describe "EagerBeaver matcher context" do
|
|
7
7
|
klass = Class.new do
|
8
8
|
include EagerBeaver
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
add_method_handler do |mh|
|
11
|
+
mh.match = lambda {
|
12
12
|
raise context.missing_method_name \
|
13
13
|
unless context.missing_method_name == "aaa"
|
14
14
|
/\Aaaa\z/ =~ context.missing_method_name
|
15
15
|
}
|
16
|
-
|
16
|
+
mh.handle = lambda {
|
17
17
|
raise context.missing_method_name \
|
18
18
|
unless context.missing_method_name == "aaa"
|
19
19
|
%Q{
|
@@ -32,11 +32,11 @@ describe "EagerBeaver matcher context" do
|
|
32
32
|
klass = Class.new do
|
33
33
|
include EagerBeaver
|
34
34
|
|
35
|
-
|
36
|
-
|
35
|
+
add_method_handler do |mh|
|
36
|
+
mh.match = lambda {
|
37
37
|
/\Aaaa\z/ =~ context.missing_method_name
|
38
38
|
}
|
39
|
-
|
39
|
+
mh.handle = lambda {
|
40
40
|
%Q{
|
41
41
|
def #{context.missing_method_name}
|
42
42
|
#{context.original_receiver.__id__}
|
@@ -56,12 +56,12 @@ describe "EagerBeaver matcher context" do
|
|
56
56
|
klass = Class.new do
|
57
57
|
include EagerBeaver
|
58
58
|
|
59
|
-
|
60
|
-
|
59
|
+
add_method_handler do |mh|
|
60
|
+
mh.match = lambda {
|
61
61
|
context.my_data = "hello"
|
62
62
|
/\Aaaa\z/ =~ context.missing_method_name
|
63
63
|
}
|
64
|
-
|
64
|
+
mh.handle = lambda {
|
65
65
|
%Q{
|
66
66
|
def #{context.missing_method_name}
|
67
67
|
"#{context.my_data}"
|
@@ -10,25 +10,25 @@ describe "EagerBeaver includer" do
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
it "has #
|
14
|
-
expect(@klass.methods).to include :
|
13
|
+
it "has #add_method_handler" do
|
14
|
+
expect(@klass.methods).to include :add_method_handler
|
15
15
|
end
|
16
16
|
|
17
|
-
it "has #
|
18
|
-
expect(@klass.methods).to include :
|
17
|
+
it "has #method_handlers" do
|
18
|
+
expect(@klass.methods).to include :method_handlers
|
19
19
|
end
|
20
20
|
|
21
21
|
end
|
22
22
|
|
23
|
-
describe "#
|
23
|
+
describe "#add_method_handler" do
|
24
24
|
|
25
25
|
it "registers a new method matcher" do
|
26
26
|
klass = Class.new do
|
27
27
|
include EagerBeaver
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
add_method_handler do |mh|
|
30
|
+
mh.match = lambda { true }
|
31
|
+
mh.handle = lambda {
|
32
32
|
return %Q{
|
33
33
|
def #{context.missing_method_name}
|
34
34
|
end
|
@@ -37,7 +37,7 @@ describe "EagerBeaver includer" do
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
klass.
|
40
|
+
klass.method_handlers.size.should == 1
|
41
41
|
end
|
42
42
|
|
43
43
|
end
|
@@ -22,9 +22,9 @@ describe "instance of EagerBeaver includer" do
|
|
22
22
|
klass = Class.new do
|
23
23
|
include EagerBeaver
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
add_method_handler do |mh|
|
26
|
+
mh.match = lambda { /\Aaaa_\w+\z/ =~ context.missing_method_name }
|
27
|
+
mh.handle = lambda {
|
28
28
|
%Q{
|
29
29
|
def #{context.missing_method_name}
|
30
30
|
end
|
@@ -53,9 +53,9 @@ describe "instance of EagerBeaver includer" do
|
|
53
53
|
klass = Class.new do
|
54
54
|
include EagerBeaver
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
add_method_handler do |mh|
|
57
|
+
mh.match = lambda { /\Aaaa_\w+\z/ =~ context.missing_method_name }
|
58
|
+
mh.handle = lambda {
|
59
59
|
%Q{
|
60
60
|
def #{context.missing_method_name}
|
61
61
|
end
|
@@ -83,9 +83,9 @@ describe "instance of EagerBeaver includer" do
|
|
83
83
|
klass = Class.new do
|
84
84
|
include EagerBeaver
|
85
85
|
|
86
|
-
|
87
|
-
|
88
|
-
|
86
|
+
add_method_handler do |mh|
|
87
|
+
mh.match = lambda { /\Aaaa\z/ =~ context.missing_method_name }
|
88
|
+
mh.handle = lambda {
|
89
89
|
%Q{
|
90
90
|
def #{context.missing_method_name}
|
91
91
|
1
|
@@ -94,9 +94,9 @@ describe "instance of EagerBeaver includer" do
|
|
94
94
|
}
|
95
95
|
end
|
96
96
|
|
97
|
-
|
98
|
-
|
99
|
-
|
97
|
+
add_method_handler do |mh|
|
98
|
+
mh.match = lambda { /\Abbb\z/ =~ context.missing_method_name }
|
99
|
+
mh.handle = lambda {
|
100
100
|
%Q{
|
101
101
|
def #{context.missing_method_name}
|
102
102
|
2
|
@@ -105,9 +105,9 @@ describe "instance of EagerBeaver includer" do
|
|
105
105
|
}
|
106
106
|
end
|
107
107
|
|
108
|
-
|
109
|
-
|
110
|
-
|
108
|
+
add_method_handler do |mh|
|
109
|
+
mh.match = lambda { /\Abbb\z/ =~ context.missing_method_name }
|
110
|
+
mh.handle = lambda {
|
111
111
|
%Q{
|
112
112
|
def #{context.missing_method_name}
|
113
113
|
3
|
@@ -130,9 +130,9 @@ describe "instance of EagerBeaver includer" do
|
|
130
130
|
klass2 = Class.new(klass1) do
|
131
131
|
include EagerBeaver
|
132
132
|
|
133
|
-
|
134
|
-
|
135
|
-
|
133
|
+
add_method_handler do |mh|
|
134
|
+
mh.match = lambda { /\Aaaa\z/ =~ context.missing_method_name }
|
135
|
+
mh.handle = lambda {
|
136
136
|
%Q{
|
137
137
|
def #{context.missing_method_name}
|
138
138
|
1
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: eager_beaver
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -43,7 +43,7 @@ files:
|
|
43
43
|
- Rakefile
|
44
44
|
- eager_beaver.gemspec
|
45
45
|
- lib/eager_beaver.rb
|
46
|
-
- lib/eager_beaver/
|
46
|
+
- lib/eager_beaver/method_handler.rb
|
47
47
|
- lib/eager_beaver/version.rb
|
48
48
|
- spec/eager_beaver/context_spec.rb
|
49
49
|
- spec/eager_beaver/includer_spec.rb
|
@@ -1,60 +0,0 @@
|
|
1
|
-
module EagerBeaver
|
2
|
-
|
3
|
-
class MethodMatcher
|
4
|
-
|
5
|
-
attr_accessor :original_receiver
|
6
|
-
attr_accessor :matcher
|
7
|
-
attr_accessor :new_method_code_maker
|
8
|
-
attr_accessor :missing_method_name
|
9
|
-
|
10
|
-
def initialize(&block)
|
11
|
-
block.call(self)
|
12
|
-
|
13
|
-
raise "matcher must be given" \
|
14
|
-
if matcher.nil?
|
15
|
-
raise "matcher lmust be a lambda" \
|
16
|
-
unless matcher.lambda?
|
17
|
-
|
18
|
-
raise "new_method_code_maker must be given" \
|
19
|
-
if new_method_code_maker.nil?
|
20
|
-
raise "new_method_code_maker must be a lambda" \
|
21
|
-
unless new_method_code_maker.lambda?
|
22
|
-
|
23
|
-
self
|
24
|
-
end
|
25
|
-
|
26
|
-
def match=(lambda_proc)
|
27
|
-
self.matcher = lambda_proc
|
28
|
-
end
|
29
|
-
|
30
|
-
def match?(method_name)
|
31
|
-
self.missing_method_name = method_name.to_s
|
32
|
-
return evaluate(matcher)
|
33
|
-
end
|
34
|
-
|
35
|
-
def new_method_code=(lambda_proc)
|
36
|
-
self.new_method_code_maker = lambda_proc
|
37
|
-
end
|
38
|
-
|
39
|
-
def evaluate(inner)
|
40
|
-
outer = lambda { |*args|
|
41
|
-
args.shift
|
42
|
-
inner.call(*args)
|
43
|
-
}
|
44
|
-
self.instance_eval &outer
|
45
|
-
end
|
46
|
-
|
47
|
-
def method_missing(method_name, *args, &block)
|
48
|
-
if /\A(?<attr_name>\w+)=?\z/ =~ method_name
|
49
|
-
code = %Q{
|
50
|
-
attr_accessor :#{attr_name}
|
51
|
-
}
|
52
|
-
self.singleton_class.instance_eval code, __FILE__, __LINE__ + 1
|
53
|
-
return self.send(method_name, *args, &block)
|
54
|
-
end
|
55
|
-
super
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
end
|