missingly 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Code Climate](https://codeclimate.com/github/moger777/missingly.png)](https://codeclimate.com/github/moger777/missingly)
|
6
|
+
[![Build Status](https://travis-ci.org/moger777/missingly.png?branch=master)](https://travis-ci.org/moger777/missingly)
|
7
|
+
[![Coverage Status](https://coveralls.io/repos/moger777/missingly/badge.png?branch=master)](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
|