eager_beaver 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|