missingly 0.0.4 → 0.0.5
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 +4 -4
- data/.travis.yml +8 -0
- data/README.md +164 -0
- data/Rakefile +9 -0
- data/lib/missingly/array_block_matcher.rb +19 -0
- data/lib/missingly/array_delegate_matcher.rb +17 -0
- data/lib/missingly/block_matcher.rb +34 -0
- data/lib/missingly/delegate_matcher.rb +11 -0
- data/lib/missingly/matchers.rb +84 -12
- data/lib/missingly/regex_block_matcher.rb +19 -0
- data/lib/missingly/regex_delegate_matcher.rb +17 -0
- data/lib/missingly/version.rb +1 -1
- data/lib/missingly.rb +6 -2
- data/missingly.gemspec +3 -2
- data/spec/array_spec.rb +0 -7
- data/spec/class_inheritance_spec.rb +46 -1
- data/spec/class_methods_spec.rb +54 -0
- data/spec/custom_matcher_spec.rb +61 -0
- data/spec/delegation_spec.rb +2 -2
- data/spec/matchers_spec.rb +39 -0
- data/spec/regex_spec.rb +0 -7
- data/spec/spec_helper.rb +3 -0
- metadata +33 -8
- data/lib/missingly/array_matcher.rb +0 -40
- data/lib/missingly/regex_matcher.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a83705cf44cec6dd38ce2c141dc8b01056c8c93
|
4
|
+
data.tar.gz: 33d31bba15f08fb1ca5a7fe815a5c10d83763a54
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 210ccf7b8492f0c25c09795578ee69a367a836e3e4878a10da0b1fb8eddce9c19bc3c30bf37843b9ead71762680bc76192fb65c37776cc046a2e3dac132301cc
|
7
|
+
data.tar.gz: 6031dd4754856133dfcfa7f24fef13b47d152fdcc30e73eb2a593784cf5c0b6017fb0b9a9673dd6c2d863a4a944216c679122d82c1080d45a3ac90cb5487c021
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
A DSL for handling method\_missing hooks.
|
4
4
|
|
5
|
+
[](https://codeclimate.com/github/moger777/missingly)
|
6
|
+
[](https://travis-ci.org/moger777/missingly)
|
7
|
+
[](https://coveralls.io/r/moger777/missingly?branch=master)
|
8
|
+
|
5
9
|
## Installation
|
6
10
|
|
7
11
|
Add this line to your application's Gemfile:
|
@@ -110,6 +114,164 @@ class UserDecorator
|
|
110
114
|
end
|
111
115
|
```
|
112
116
|
|
117
|
+
### Use custom matchers
|
118
|
+
|
119
|
+
In the example with the regex block matchers, our code has to do a
|
120
|
+
fair amount of work which is not looking up a value in a hash, for example:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
fields = matches[1].split("_and_")
|
124
|
+
```
|
125
|
+
|
126
|
+
will run every time and can have a performance impact. Likewise we
|
127
|
+
are always running:
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
field.to_sym
|
131
|
+
```
|
132
|
+
|
133
|
+
In the hash lookup. If the field was already a symbol, there would be less work. And
|
134
|
+
the fields were already split up, there would be less work each time. Custom block
|
135
|
+
matchers can be done as follows:
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
class OurMatcher < Missingly::BlockMatcher
|
139
|
+
attr_reader :some_matcher, :options_hash, :method_block
|
140
|
+
|
141
|
+
def initialize(some_matcher, options_hash, method_block)
|
142
|
+
@some_matcher, @method_block = some_matcher, method_block
|
143
|
+
end
|
144
|
+
|
145
|
+
def should_respond_to?(instance, name)
|
146
|
+
# our custom code
|
147
|
+
end
|
148
|
+
|
149
|
+
def setup_method_name_args(method_name)
|
150
|
+
# args we will pass to block
|
151
|
+
end
|
152
|
+
|
153
|
+
def matchable; some_matcher; end
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
Since we essentially want to re-use the regex block helper, we can inherit and override
|
158
|
+
setup_method_name_args. These args will be passed to the block in the handle_missingly
|
159
|
+
call:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
class FindByFieldsWithAndsMatcher < Missingly::RegexBlockMatcher
|
163
|
+
attr_reader :options
|
164
|
+
|
165
|
+
def initialize(regex, options, block)
|
166
|
+
super regex, block
|
167
|
+
end
|
168
|
+
|
169
|
+
def setup_method_name_args(method_name)
|
170
|
+
matches = regex.match(method_name)
|
171
|
+
fields = matches[1].split("_and_")
|
172
|
+
fields.map(&:to_sym)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
From here, we can use our custom matcher:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
class ArrayWithHashes
|
181
|
+
include Missingly::Matchers
|
182
|
+
|
183
|
+
handle_missingly /^find_by_(\w+)$/, with: FindByFieldsWithAndsMatcher do |fields, *args, &block|
|
184
|
+
hashes.find do |hash|
|
185
|
+
fields.inject(true) do |fields_match, field|
|
186
|
+
index_of_field = fields.index(field)
|
187
|
+
arg_for_field = args[index_of_field]
|
188
|
+
|
189
|
+
fields_match = fields_match && hash[field] == arg_for_field
|
190
|
+
break false unless fields_match
|
191
|
+
true
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
attr_reader :hashes
|
197
|
+
|
198
|
+
def initialize(hashes)
|
199
|
+
@hashes = hashes
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
hashes = [
|
204
|
+
{ id: 1, name: 'Pat', gender: 'f' },
|
205
|
+
{ id: 2, name: 'Pat', gender: 'm' },
|
206
|
+
{ id: 3, name: 'Steve', gender: 'm' },
|
207
|
+
{ id: 4, name: 'Sue', gender: 'f' },
|
208
|
+
]
|
209
|
+
|
210
|
+
instance = ArrayWithHashes.new(hashes)
|
211
|
+
instance.find_by_name_and_gender('Pat', 'm') # { id: 2, name: 'Pat', gender: 'm' }
|
212
|
+
instance.respond_to?(:find_by_name_and_gender) # true
|
213
|
+
instance.method(:find_by_name_and_gender) # method object
|
214
|
+
```
|
215
|
+
|
216
|
+
For more fine grain controll, you can write should_respond_to? which should
|
217
|
+
return true if method responds to, and handle, which should define method and
|
218
|
+
return results of first run of method.
|
219
|
+
|
220
|
+
### How inheritance works
|
221
|
+
|
222
|
+
The handle_missingly method is designed to be both inherited and overwritable by
|
223
|
+
child classes. The following scenarios should work:
|
224
|
+
|
225
|
+
Straight up inheritance:
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
class Parent
|
229
|
+
handle_missingly /foo/ do
|
230
|
+
:foo
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
class Child < Parent
|
235
|
+
end
|
236
|
+
|
237
|
+
Child.new.foo # should return :foo
|
238
|
+
```
|
239
|
+
|
240
|
+
Overwriting:
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
class Parent
|
244
|
+
handle_missingly /foo/ do
|
245
|
+
:foo
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
class Child < Parent
|
250
|
+
handle_missingly /foo/ do
|
251
|
+
:bar
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
Child.new.foo # should return :bar
|
256
|
+
```
|
257
|
+
|
258
|
+
Missingly handlers are based off of "matchable" passed to matcher, so the following
|
259
|
+
will also be overwritten:
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
class Parent
|
263
|
+
handle_missingly /foo/ do
|
264
|
+
:foo
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
class Child < Parent
|
269
|
+
handle_missingly /foo/, to: :something
|
270
|
+
end
|
271
|
+
|
272
|
+
Child.new.foo # should return whatever something returns
|
273
|
+
```
|
274
|
+
|
113
275
|
## Contributing
|
114
276
|
|
115
277
|
1. Fork it
|
@@ -117,3 +279,5 @@ end
|
|
117
279
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
118
280
|
4. Push to the branch (`git push origin my-new-feature`)
|
119
281
|
5. Create new Pull Request
|
282
|
+
6. Please no tabs or trailing whitespace
|
283
|
+
7. Features and bug fixes should have specs
|
data/Rakefile
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Missingly
|
2
|
+
class ArrayBlockMatcher < BlockMatcher
|
3
|
+
attr_reader :array, :method_block, :options
|
4
|
+
|
5
|
+
def initialize(array, options, method_block)
|
6
|
+
@array, @method_block, @options = array, method_block, options
|
7
|
+
end
|
8
|
+
|
9
|
+
def should_respond_to?(instance, name)
|
10
|
+
array.include?(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup_method_name_args(method_name)
|
14
|
+
method_name
|
15
|
+
end
|
16
|
+
|
17
|
+
def matchable; array; end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Missingly
|
2
|
+
class ArrayDelegateMatcher < DelegateMatcher
|
3
|
+
attr_reader :array, :options, :delegate_name
|
4
|
+
|
5
|
+
def initialize(array, options, delegate_name)
|
6
|
+
@array, @options, @delegate_name = array, options, delegate_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def should_respond_to?(instance, name)
|
10
|
+
included_in_array = array.include?(name)
|
11
|
+
delegate_responds_to = instance.send(delegate_name).respond_to?(name)
|
12
|
+
included_in_array && delegate_responds_to
|
13
|
+
end
|
14
|
+
|
15
|
+
def matchable; array; end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Missingly
|
2
|
+
class BlockMatcher
|
3
|
+
def define(instance, method_name)
|
4
|
+
|
5
|
+
klass = instance.class == Class ? instance : instance.class
|
6
|
+
|
7
|
+
if instance.class == Class
|
8
|
+
define_class_method(klass, method_name)
|
9
|
+
else
|
10
|
+
define_instance_method(klass, method_name)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def define_class_method(klass, method_name)
|
15
|
+
sub_name = "_#{method_name}_submethod"
|
16
|
+
method_name_args = setup_method_name_args(method_name)
|
17
|
+
|
18
|
+
klass.define_singleton_method method_name do |*the_args, &the_block|
|
19
|
+
public_send(sub_name, method_name_args, *the_args, &the_block)
|
20
|
+
end
|
21
|
+
klass.define_singleton_method(sub_name, &method_block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def define_instance_method(klass, method_name)
|
25
|
+
sub_name = "_#{method_name}_submethod"
|
26
|
+
method_name_args = setup_method_name_args(method_name)
|
27
|
+
|
28
|
+
klass._define_method method_name do |*the_args, &the_block|
|
29
|
+
public_send(sub_name, method_name_args, *the_args, &the_block)
|
30
|
+
end
|
31
|
+
klass._define_method(sub_name, &method_block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/missingly/matchers.rb
CHANGED
@@ -11,20 +11,64 @@ module Missingly
|
|
11
11
|
|
12
12
|
module Matchers
|
13
13
|
module ClassMethods
|
14
|
-
def handle_missingly(
|
15
|
-
undef_parent_missingly_methods
|
14
|
+
def handle_missingly(matcher, options={}, &block)
|
15
|
+
undef_parent_missingly_methods matcher
|
16
|
+
undef_normal_missingly_methods matcher
|
17
|
+
|
18
|
+
if options[:with]
|
19
|
+
setup_custom_handler(matcher, options, &block)
|
20
|
+
elsif block_given?
|
21
|
+
setup_block_handlers(matcher, options, &block)
|
22
|
+
elsif options[:to]
|
23
|
+
setup_delegation_handlers(matcher, options, options[:to])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def setup_custom_handler(matcher, options, &block)
|
28
|
+
missingly_matchers[matcher] = options[:with].new(matcher, options, block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def setup_block_handlers(matcher, options, &block)
|
32
|
+
case matcher
|
33
|
+
when Array then missingly_matchers[matcher] = ArrayBlockMatcher.new(matcher, options, block)
|
34
|
+
when Regexp then missingly_matchers[matcher] = RegexBlockMatcher.new(matcher, options, block)
|
35
|
+
end
|
36
|
+
end
|
16
37
|
|
17
|
-
|
18
|
-
|
19
|
-
when
|
38
|
+
def setup_delegation_handlers(matcher, options, to)
|
39
|
+
case matcher
|
40
|
+
when Array then missingly_matchers[matcher] = ArrayDelegateMatcher.new(matcher, options, to)
|
41
|
+
when Regexp then missingly_matchers[matcher] = RegexDelegateMatcher.new(matcher, options, to)
|
20
42
|
end
|
21
43
|
end
|
22
44
|
|
23
45
|
def undef_parent_missingly_methods(matcher)
|
24
|
-
|
46
|
+
superclass = self.superclass
|
47
|
+
matchers = []
|
25
48
|
|
26
|
-
superclass.missingly_methods_for_matcher
|
27
|
-
|
49
|
+
while superclass.respond_to?(:missingly_methods_for_matcher)
|
50
|
+
matchers.concat superclass.missingly_methods_for_matcher(matcher)
|
51
|
+
superclass = superclass.superclass
|
52
|
+
end
|
53
|
+
|
54
|
+
undef_missingly_methods(matchers)
|
55
|
+
end
|
56
|
+
|
57
|
+
def undef_normal_missingly_methods(matcher)
|
58
|
+
undef_missingly_methods(missingly_methods_for_matcher(matcher))
|
59
|
+
end
|
60
|
+
|
61
|
+
def undef_missingly_methods(methods)
|
62
|
+
methods.each do |method|
|
63
|
+
begin
|
64
|
+
undef_method method
|
65
|
+
rescue NameError
|
66
|
+
eval <<-RUBY
|
67
|
+
class << self
|
68
|
+
undef_method #{method.inspect}
|
69
|
+
end
|
70
|
+
RUBY
|
71
|
+
end
|
28
72
|
end
|
29
73
|
end
|
30
74
|
|
@@ -55,11 +99,38 @@ module Missingly
|
|
55
99
|
end
|
56
100
|
missingly_subclasses << subclass
|
57
101
|
end
|
102
|
+
|
103
|
+
def method_missing(method_name, *args, &block)
|
104
|
+
missingly_matchers.values.each do |matcher|
|
105
|
+
next unless matcher.should_respond_to?(self, method_name)
|
106
|
+
next unless matcher.options[:class_method]
|
107
|
+
|
108
|
+
Missingly::Mutex.synchronize do
|
109
|
+
missingly_methods_for_matcher(matcher.matchable) << method_name
|
110
|
+
|
111
|
+
returned_value = matcher.define(self, method_name, *args, &block)
|
112
|
+
|
113
|
+
missingly_subclasses.each do |subclass|
|
114
|
+
subclass.undef_parent_missingly_methods matcher.matchable
|
115
|
+
end
|
116
|
+
|
117
|
+
return public_send(method_name, *args, &block)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
super
|
121
|
+
end
|
122
|
+
|
123
|
+
def respond_to_missing?(method_name, include_all)
|
124
|
+
self.missingly_matchers.values.each do |matcher|
|
125
|
+
return true if matcher.should_respond_to?(self, method_name.to_sym) && matcher.options[:class_method]
|
126
|
+
end
|
127
|
+
super
|
128
|
+
end
|
58
129
|
end
|
59
130
|
|
60
131
|
def respond_to_missing?(method_name, include_all)
|
61
132
|
self.class.missingly_matchers.values.each do |matcher|
|
62
|
-
return true if matcher.should_respond_to?(method_name.to_sym)
|
133
|
+
return true if matcher.should_respond_to?(self, method_name.to_sym) && !(matcher.options[:class_method])
|
63
134
|
end
|
64
135
|
super
|
65
136
|
end
|
@@ -67,18 +138,19 @@ module Missingly
|
|
67
138
|
|
68
139
|
def method_missing(method_name, *args, &block)
|
69
140
|
self.class.missingly_matchers.values.each do |matcher|
|
70
|
-
next unless matcher.should_respond_to?(method_name)
|
141
|
+
next unless matcher.should_respond_to?(self, method_name)
|
142
|
+
next if matcher.options[:class_method]
|
71
143
|
|
72
144
|
Missingly::Mutex.synchronize do
|
73
145
|
self.class.missingly_methods_for_matcher(matcher.matchable) << method_name
|
74
146
|
|
75
|
-
|
147
|
+
matcher.define(self, method_name)
|
76
148
|
|
77
149
|
self.class.missingly_subclasses.each do |subclass|
|
78
150
|
subclass.undef_parent_missingly_methods matcher.matchable
|
79
151
|
end
|
80
152
|
|
81
|
-
return
|
153
|
+
return public_send(method_name, *args, &block)
|
82
154
|
end
|
83
155
|
end
|
84
156
|
super
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Missingly
|
2
|
+
class RegexBlockMatcher < BlockMatcher
|
3
|
+
attr_reader :regex, :method_block, :options
|
4
|
+
|
5
|
+
def initialize(regex, options, method_block)
|
6
|
+
@regex, @options, @method_block = regex, options, method_block
|
7
|
+
end
|
8
|
+
|
9
|
+
def should_respond_to?(instance, name)
|
10
|
+
regex.match(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup_method_name_args(method_name)
|
14
|
+
regex.match method_name
|
15
|
+
end
|
16
|
+
|
17
|
+
def matchable; regex; end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Missingly
|
2
|
+
class RegexDelegateMatcher < DelegateMatcher
|
3
|
+
attr_reader :regex, :options, :delegate_name
|
4
|
+
|
5
|
+
def initialize(regex, options, delegate_name)
|
6
|
+
@regex, @options, @delegate_name = regex, options, delegate_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def should_respond_to?(instance, name)
|
10
|
+
matches = regex.match name
|
11
|
+
delegate_responds_to = instance.send(delegate_name).respond_to?(name)
|
12
|
+
matches && delegate_responds_to
|
13
|
+
end
|
14
|
+
|
15
|
+
def matchable; regex; end
|
16
|
+
end
|
17
|
+
end
|
data/lib/missingly/version.rb
CHANGED
data/lib/missingly.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
require "missingly/version"
|
2
2
|
require "missingly/matchers"
|
3
|
-
require "missingly/
|
4
|
-
require "missingly/
|
3
|
+
require "missingly/block_matcher"
|
4
|
+
require "missingly/array_block_matcher"
|
5
|
+
require "missingly/regex_block_matcher"
|
6
|
+
require "missingly/delegate_matcher"
|
7
|
+
require "missingly/array_delegate_matcher"
|
8
|
+
require "missingly/regex_delegate_matcher"
|
5
9
|
|
6
10
|
module Missingly
|
7
11
|
|
data/missingly.gemspec
CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
22
|
spec.add_development_dependency "rake"
|
23
|
-
spec.add_development_dependency "rspec"
|
24
|
-
spec.add_development_dependency "pry"
|
23
|
+
spec.add_development_dependency "rspec", "~> 2.14.0"
|
24
|
+
spec.add_development_dependency "pry", "0.9.12"
|
25
|
+
spec.add_development_dependency "coveralls"
|
25
26
|
end
|
data/spec/array_spec.rb
CHANGED
@@ -55,13 +55,6 @@ module Missingly
|
|
55
55
|
instance.block.should == prock
|
56
56
|
end
|
57
57
|
|
58
|
-
it "should define the method on call preventing further method missing calls on same class" do
|
59
|
-
args = [1, 2, 3]
|
60
|
-
prock = Proc.new { puts 'foo' }
|
61
|
-
instance.derp(*args, &prock)
|
62
|
-
Method.should === instance.method(:derp)
|
63
|
-
end
|
64
|
-
|
65
58
|
it "should work with subsequent calls" do
|
66
59
|
args = [1, 2, 3]
|
67
60
|
prock = Proc.new { puts 'foo' }
|
@@ -22,8 +22,13 @@ describe Missingly::Matchers do
|
|
22
22
|
end
|
23
23
|
|
24
24
|
describe "overriding methods" do
|
25
|
-
let(:
|
25
|
+
let(:another_inheritance_layer) do
|
26
26
|
Class.new(super_class) do
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
let(:subclass_with_overrides) do
|
31
|
+
Class.new(another_inheritance_layer) do
|
27
32
|
handle_missingly [:foo] do |method|
|
28
33
|
:super_duper
|
29
34
|
end
|
@@ -45,4 +50,44 @@ describe Missingly::Matchers do
|
|
45
50
|
subclass_with_overrides.new.foo.should eq :super_duper
|
46
51
|
end
|
47
52
|
end
|
53
|
+
|
54
|
+
describe "overriding class methods" do
|
55
|
+
let(:super_class) do
|
56
|
+
Class.new do
|
57
|
+
include Missingly::Matchers
|
58
|
+
|
59
|
+
handle_missingly [:foo], class_method: true do |method|
|
60
|
+
return method
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
let(:another_inheritance_layer) do
|
66
|
+
Class.new(super_class) do
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
let(:subclass_with_overrides) do
|
71
|
+
Class.new(another_inheritance_layer) do
|
72
|
+
handle_missingly [:foo], class_method: true do |_|
|
73
|
+
:super_duper
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should work when called before parent" do
|
79
|
+
subclass_with_overrides.foo.should eq :super_duper
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should work when called after parent" do
|
83
|
+
super_class.foo
|
84
|
+
subclass_with_overrides.foo.should eq :super_duper
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should work when subclass initiated before parent method defined" do
|
88
|
+
subclass_with_overrides
|
89
|
+
super_class.foo
|
90
|
+
subclass_with_overrides.foo.should eq :super_duper
|
91
|
+
end
|
92
|
+
end
|
48
93
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Missingly::Matchers do
|
4
|
+
let(:search_class) do
|
5
|
+
Class.new do
|
6
|
+
include Missingly::Matchers
|
7
|
+
|
8
|
+
handle_missingly [:find_by_name], class_method: true do |name|
|
9
|
+
return {foo: 'bar'}
|
10
|
+
end
|
11
|
+
|
12
|
+
handle_missingly /^find_all_by_(\w+)$/, class_method: true do |matches, *args, &block|
|
13
|
+
return matches
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:delegation_test) do
|
19
|
+
Class.new(search_class) do
|
20
|
+
handle_missingly [:find_by_foo], to: :proxy, class_method: true
|
21
|
+
|
22
|
+
def self.proxy
|
23
|
+
OpenStruct.new({find_by_foo: "foo"})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
it "should not break normal method_missing" do
|
30
|
+
search_class.new.respond_to?("foo_bar_widget").should be_false
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should allow you to define class methods" do
|
34
|
+
search_class.respond_to?("find_by_name").should be_true
|
35
|
+
search_class.respond_to?("find_all_by_name").should be_true
|
36
|
+
search_class.find_all_by_name.should be_a MatchData
|
37
|
+
search_class.find_by_name.should be_a Hash
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should support delegation matchers" do
|
41
|
+
delegation_test.respond_to?("find_by_foo").should be_true
|
42
|
+
delegation_test.find_by_foo.should be_true
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should not make class methods avliable to instances" do
|
46
|
+
search_class.new.respond_to?("find_by_name").should be_false
|
47
|
+
lambda { search_class.new.find_by_name("foo") }.should raise_exception
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should work through inheritence" do
|
51
|
+
delegation_test.respond_to?("find_all_by_name").should be_true
|
52
|
+
delegation_test.find_all_by_name.should be_a MatchData
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Missingly
|
4
|
+
describe Matchers do
|
5
|
+
let(:find_by_matcher) do
|
6
|
+
FindByMatcher = Class.new(Missingly::RegexBlockMatcher) do
|
7
|
+
attr_reader :method_block, :options
|
8
|
+
|
9
|
+
def initialize(regex, options, block)
|
10
|
+
@regex = regex
|
11
|
+
@options = options
|
12
|
+
@method_block = block
|
13
|
+
end
|
14
|
+
|
15
|
+
def setup_method_name_args(method_name)
|
16
|
+
matches = regex.match(method_name)
|
17
|
+
matches[1].split("_and_").map(&:to_sym)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:our_class) do
|
23
|
+
find_by_matcher
|
24
|
+
Class.new do
|
25
|
+
include Missingly::Matchers
|
26
|
+
attr_reader :hashes
|
27
|
+
|
28
|
+
def initialize(hashes)
|
29
|
+
@hashes = hashes
|
30
|
+
end
|
31
|
+
|
32
|
+
handle_missingly /^find_by_(\w+)$/, with: FindByMatcher do |fields, *args|
|
33
|
+
hashes.find do |hash|
|
34
|
+
fields.inject(true) do |fields_match, field|
|
35
|
+
index_of_field = fields.index(field)
|
36
|
+
arg_for_field = args[index_of_field]
|
37
|
+
|
38
|
+
fields_match = fields_match && hash[field.to_sym] == arg_for_field
|
39
|
+
break false unless fields_match
|
40
|
+
true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
let(:our_instance) do
|
48
|
+
hashes = [
|
49
|
+
{ first_name: 'Bob', last_name: 'Dole' },
|
50
|
+
{ first_name: 'Bill', last_name: 'Clinton' },
|
51
|
+
{ first_name: 'George', last_name: 'Bush' }
|
52
|
+
]
|
53
|
+
our_class.new(hashes)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should allow us to define custom matchers" do
|
57
|
+
our_instance.find_by_first_name_and_last_name('Bill', 'Douglas').should be_nil
|
58
|
+
our_instance.find_by_first_name_and_last_name('Bill', 'Clinton').should == { first_name: 'Bill', last_name: 'Clinton' }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/spec/delegation_spec.rb
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Missingly
|
2
|
+
describe Matchers do
|
3
|
+
describe "#handle_missingly" do
|
4
|
+
it "can be used to override previous missingly with same matcher" do
|
5
|
+
klass = Class.new do
|
6
|
+
include Missingly::Matchers
|
7
|
+
|
8
|
+
handle_missingly /foo/ do |*args|
|
9
|
+
'foo'
|
10
|
+
end
|
11
|
+
|
12
|
+
handle_missingly /foo/ do |*args|
|
13
|
+
'bar'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
klass.new.foo.should eq('bar')
|
18
|
+
|
19
|
+
another_klass = Class.new do
|
20
|
+
include Missingly::Matchers
|
21
|
+
|
22
|
+
handle_missingly /foo/ do |*args|
|
23
|
+
'foo'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
another_klass.new.foo
|
28
|
+
|
29
|
+
another_klass.module_eval do
|
30
|
+
handle_missingly /foo/ do |*args|
|
31
|
+
'bar'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
another_klass.new.foo.should eq('bar')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/spec/regex_spec.rb
CHANGED
@@ -84,13 +84,6 @@ module Missingly
|
|
84
84
|
instance.block.should == prock
|
85
85
|
end
|
86
86
|
|
87
|
-
it "should define the method on call preventing further method missing calls on same class" do
|
88
|
-
args = [1, 2, 3]
|
89
|
-
prock = Proc.new { puts 'foo' }
|
90
|
-
instance.find_by_id_and_first_name(*args, &prock)
|
91
|
-
Method.should === instance.method(:find_by_id_and_first_name)
|
92
|
-
end
|
93
|
-
|
94
87
|
it "should work with subsequent calls" do
|
95
88
|
args = [1, 2, 3]
|
96
89
|
prock = Proc.new { puts 'foo' }
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: missingly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thijs de Vries
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-08-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -42,18 +42,32 @@ dependencies:
|
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ~>
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 2.14.0
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 2.14.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.9.12
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.9.12
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: coveralls
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - '>='
|
@@ -74,19 +88,27 @@ extensions: []
|
|
74
88
|
extra_rdoc_files: []
|
75
89
|
files:
|
76
90
|
- .gitignore
|
91
|
+
- .travis.yml
|
77
92
|
- Gemfile
|
78
93
|
- LICENSE.txt
|
79
94
|
- README.md
|
80
95
|
- Rakefile
|
81
96
|
- lib/missingly.rb
|
82
|
-
- lib/missingly/
|
97
|
+
- lib/missingly/array_block_matcher.rb
|
98
|
+
- lib/missingly/array_delegate_matcher.rb
|
99
|
+
- lib/missingly/block_matcher.rb
|
100
|
+
- lib/missingly/delegate_matcher.rb
|
83
101
|
- lib/missingly/matchers.rb
|
84
|
-
- lib/missingly/
|
102
|
+
- lib/missingly/regex_block_matcher.rb
|
103
|
+
- lib/missingly/regex_delegate_matcher.rb
|
85
104
|
- lib/missingly/version.rb
|
86
105
|
- missingly.gemspec
|
87
106
|
- spec/array_spec.rb
|
88
107
|
- spec/class_inheritance_spec.rb
|
108
|
+
- spec/class_methods_spec.rb
|
109
|
+
- spec/custom_matcher_spec.rb
|
89
110
|
- spec/delegation_spec.rb
|
111
|
+
- spec/matchers_spec.rb
|
90
112
|
- spec/regex_spec.rb
|
91
113
|
- spec/spec_helper.rb
|
92
114
|
homepage: ''
|
@@ -116,6 +138,9 @@ summary: A DSL for defining method missing methods
|
|
116
138
|
test_files:
|
117
139
|
- spec/array_spec.rb
|
118
140
|
- spec/class_inheritance_spec.rb
|
141
|
+
- spec/class_methods_spec.rb
|
142
|
+
- spec/custom_matcher_spec.rb
|
119
143
|
- spec/delegation_spec.rb
|
144
|
+
- spec/matchers_spec.rb
|
120
145
|
- spec/regex_spec.rb
|
121
146
|
- spec/spec_helper.rb
|
@@ -1,40 +0,0 @@
|
|
1
|
-
module Missingly
|
2
|
-
class ArrayMatcher
|
3
|
-
attr_reader :array, :options, :method_block
|
4
|
-
|
5
|
-
def initialize(array, options, method_block)
|
6
|
-
@array, @options, @method_block = array, options, method_block
|
7
|
-
end
|
8
|
-
|
9
|
-
def matchable
|
10
|
-
array
|
11
|
-
end
|
12
|
-
|
13
|
-
def should_respond_to?(name)
|
14
|
-
array.include?(name)
|
15
|
-
end
|
16
|
-
|
17
|
-
def handle(instance, method_name, *args, &block)
|
18
|
-
if method_block
|
19
|
-
sub_name = "#{method_name}_with_method_name"
|
20
|
-
|
21
|
-
instance.class._define_method method_name do |*the_args, &the_block|
|
22
|
-
public_send(sub_name, method_name, *the_args, &the_block)
|
23
|
-
end
|
24
|
-
instance.class._define_method(sub_name, &method_block)
|
25
|
-
|
26
|
-
instance.public_send(method_name, *args, &block)
|
27
|
-
elsif options[:to]
|
28
|
-
instance.class.class_eval <<-CODE
|
29
|
-
def #{method_name}(*args, &block)
|
30
|
-
#{options[:to]}.#{method_name}(*args, &block)
|
31
|
-
end
|
32
|
-
CODE
|
33
|
-
|
34
|
-
instance.public_send(method_name, *args, &block)
|
35
|
-
else
|
36
|
-
raise ArgumentError, "either block, or to option should be passed"
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
module Missingly
|
2
|
-
class RegexMatcher
|
3
|
-
attr_reader :regex, :options, :method_block
|
4
|
-
|
5
|
-
def initialize(regex, options, method_block)
|
6
|
-
@regex, @options, @method_block = regex, options, method_block
|
7
|
-
end
|
8
|
-
|
9
|
-
def matchable
|
10
|
-
regex
|
11
|
-
end
|
12
|
-
|
13
|
-
def should_respond_to?(name)
|
14
|
-
regex.match(name)
|
15
|
-
end
|
16
|
-
|
17
|
-
def handle(instance, method_name, *args, &block)
|
18
|
-
if method_block
|
19
|
-
matches = regex.match method_name
|
20
|
-
|
21
|
-
sub_name = "#{method_name}_with_matches"
|
22
|
-
instance.class._define_method method_name do |*the_args, &the_block|
|
23
|
-
public_send(sub_name, matches, *the_args, &the_block)
|
24
|
-
end
|
25
|
-
instance.class._define_method(sub_name, &method_block)
|
26
|
-
|
27
|
-
instance.public_send(method_name, *args, &block)
|
28
|
-
elsif options[:to]
|
29
|
-
instance.class.class_eval <<-CODE
|
30
|
-
def #{method_name}(*args, &block)
|
31
|
-
#{options[:to]}.#{method_name}(*args, &block)
|
32
|
-
end
|
33
|
-
CODE
|
34
|
-
|
35
|
-
instance.public_send(method_name, *args, &block)
|
36
|
-
else
|
37
|
-
raise ArgumentError, "either block, or to option should be passed"
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|