much-stub 0.1.4 → 0.1.8
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/.l.yml +8 -0
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -0
- data/.t.yml +6 -0
- data/Gemfile +5 -1
- data/README.md +40 -35
- data/lib/much-stub/call.rb +2 -0
- data/lib/much-stub/call_spy.rb +28 -27
- data/lib/much-stub/version.rb +3 -1
- data/lib/much-stub.rb +60 -46
- data/much-stub.gemspec +20 -12
- data/test/helper.rb +3 -1
- data/test/support/factory.rb +2 -0
- data/test/system/much-stub_tests.rb +115 -77
- data/test/unit/call_spy_tests.rb +17 -2
- data/test/unit/call_tests.rb +4 -2
- data/test/unit/much-stub_tests.rb +173 -111
- metadata +27 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c37bcf3b2a355483515be9eabd74c93bd93ccf5f503180409f86d18a183f6f8a
|
4
|
+
data.tar.gz: 20cab69d294b44212ccd23eb068ae17e2458576153e817c30dac08206c294c47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 929d09b4df2e8b85496b4a47fe67a23dde700ee751b7fa3d1b074a09db185f01ad3ff54e16a7935cd1db75fc4af58fdb9ff3482fe81d40fcfcca421ae515e265
|
7
|
+
data.tar.gz: dfbfd75bc0edc084c45eee9c62926c7c2baaec32cb28f40d670a6296e1b21c7348ee4f1d6926d02b03a63223e1369b2d0907735c2b8849805862404afffd256f
|
data/.l.yml
ADDED
data/.rubocop.yml
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.0.2
|
data/.t.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -16,15 +16,16 @@ Note: this was originally implemented in and extracted from [Assert](https://git
|
|
16
16
|
```ruby
|
17
17
|
# Given this object/API
|
18
18
|
|
19
|
-
my_class =
|
20
|
-
|
21
|
-
|
19
|
+
my_class =
|
20
|
+
Class.new do
|
21
|
+
def my_method
|
22
|
+
"my-method"
|
23
|
+
end
|
24
|
+
|
25
|
+
def my_value(value)
|
26
|
+
value
|
27
|
+
end
|
22
28
|
end
|
23
|
-
|
24
|
-
def my_value(value)
|
25
|
-
value
|
26
|
-
end
|
27
|
-
end
|
28
29
|
my_object = my_class.new
|
29
30
|
|
30
31
|
my_object.my_method
|
@@ -95,15 +96,16 @@ my_object.my_value(456)
|
|
95
96
|
```ruby
|
96
97
|
# Given this object/API
|
97
98
|
|
98
|
-
my_class =
|
99
|
-
|
100
|
-
value
|
101
|
-
|
99
|
+
my_class =
|
100
|
+
Class.new do
|
101
|
+
def basic_method(value)
|
102
|
+
value
|
103
|
+
end
|
102
104
|
|
103
|
-
|
104
|
-
|
105
|
+
def iterator_method(items, &block)
|
106
|
+
items.each(&block)
|
107
|
+
end
|
105
108
|
end
|
106
|
-
end
|
107
109
|
my_object = my_class.new
|
108
110
|
|
109
111
|
# Store method call arguments/blocks for spying.
|
@@ -189,11 +191,12 @@ basic_method_calls.first.args
|
|
189
191
|
```ruby
|
190
192
|
# Given this object/API ...
|
191
193
|
|
192
|
-
my_class =
|
193
|
-
|
194
|
-
|
194
|
+
my_class =
|
195
|
+
Class.new do
|
196
|
+
def build_thing(thing_value);
|
197
|
+
Thing.new(value)
|
198
|
+
end
|
195
199
|
end
|
196
|
-
end
|
197
200
|
my_object = my_class.new
|
198
201
|
|
199
202
|
# ... and this Test Double.
|
@@ -223,11 +226,12 @@ Use the `.tap` method to spy on method calls while preserving the original metho
|
|
223
226
|
```ruby
|
224
227
|
# Given this object/API
|
225
228
|
|
226
|
-
my_class =
|
227
|
-
|
228
|
-
value
|
229
|
+
my_class =
|
230
|
+
Class.new do
|
231
|
+
def basic_method(value)
|
232
|
+
value.to_s
|
233
|
+
end
|
229
234
|
end
|
230
|
-
end
|
231
235
|
my_object = my_class.new
|
232
236
|
|
233
237
|
# Normal stubs override the original behavior and return value...
|
@@ -275,11 +279,12 @@ class Thing
|
|
275
279
|
end
|
276
280
|
end
|
277
281
|
|
278
|
-
my_class =
|
279
|
-
|
280
|
-
|
282
|
+
my_class =
|
283
|
+
Class.new do
|
284
|
+
def thing(value)
|
285
|
+
Thing.new(value)
|
286
|
+
end
|
281
287
|
end
|
282
|
-
end
|
283
288
|
my_object = my_class.new
|
284
289
|
|
285
290
|
# Use `MuchStub.tap` to stub any thing instances created by `my_object.thing`
|
@@ -306,16 +311,16 @@ Use the `.spy` method to spy on method calls. This is especially helpful for spy
|
|
306
311
|
```ruby
|
307
312
|
# Given this object/API
|
308
313
|
|
309
|
-
myclass =
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
end
|
314
|
+
myclass =
|
315
|
+
Class.new do
|
316
|
+
def one; self; end
|
317
|
+
def two(val); self; end
|
318
|
+
def three; self; end
|
319
|
+
def ready?; false; end
|
320
|
+
end
|
315
321
|
myobj = myclass.new
|
316
322
|
|
317
|
-
spy =
|
318
|
-
MuchStub.spy(myobj :one, :two, :three, ready?: true)
|
323
|
+
spy = MuchStub.spy(myobj :one, :two, :three, ready?: true)
|
319
324
|
|
320
325
|
assert_equal spy, myobj.one
|
321
326
|
assert_equal spy, myobj.two("a")
|
data/lib/much-stub/call.rb
CHANGED
data/lib/much-stub/call_spy.rb
CHANGED
@@ -1,17 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "much-stub/call"
|
2
4
|
|
3
5
|
module MuchStub
|
4
6
|
class CallSpy < ::BasicObject
|
5
7
|
METHOD_NAME_REPLACEMENTS = {
|
6
8
|
"!" => "_bang",
|
7
|
-
"?" => "_predicate"
|
9
|
+
"?" => "_predicate",
|
8
10
|
}.freeze
|
9
11
|
|
10
12
|
def initialize(**return_values)
|
11
|
-
@call_spy_return_values = return_values.transform_keys
|
12
|
-
@call_spy_method_calls = ::Hash.new
|
13
|
+
@call_spy_return_values = return_values.transform_keys(&:to_s)
|
14
|
+
@call_spy_method_calls = ::Hash.new{ |hash, key| hash[key] = [] }
|
13
15
|
@call_spy_method_return_values =
|
14
|
-
::Hash.new
|
16
|
+
::Hash.new{ |hash, key| hash[key] = call_spy_return_value_proc(key) }
|
15
17
|
end
|
16
18
|
|
17
19
|
def call_spy_tap
|
@@ -20,23 +22,23 @@ module MuchStub
|
|
20
22
|
end
|
21
23
|
|
22
24
|
def ==(other)
|
23
|
-
|
25
|
+
equal?(other)
|
24
26
|
end
|
25
27
|
|
26
28
|
def ===(other)
|
27
|
-
|
29
|
+
equal?(other)
|
28
30
|
end
|
29
31
|
|
30
32
|
def eql?(other)
|
31
|
-
|
33
|
+
equal?(other)
|
32
34
|
end
|
33
35
|
|
34
36
|
def equal?(other)
|
35
|
-
|
37
|
+
__id__ == other.__id__
|
36
38
|
end
|
37
39
|
|
38
40
|
def hash
|
39
|
-
|
41
|
+
__id__
|
40
42
|
end
|
41
43
|
|
42
44
|
def respond_to?(*)
|
@@ -44,7 +46,7 @@ module MuchStub
|
|
44
46
|
end
|
45
47
|
|
46
48
|
def inspect
|
47
|
-
"#<MuchStub::CallSpy:#{"0x0%x" % (
|
49
|
+
"#<MuchStub::CallSpy:#{"0x0%x" % (__id__ << 1)}>"
|
48
50
|
end
|
49
51
|
|
50
52
|
private
|
@@ -54,16 +56,14 @@ module MuchStub
|
|
54
56
|
end
|
55
57
|
|
56
58
|
def call_spy_return_value_proc(method_name)
|
57
|
-
value = @call_spy_return_values
|
58
|
-
|
59
|
-
|
60
|
-
::Proc.new { value.nil? ? self : value }
|
59
|
+
value = @call_spy_return_values.fetch(method_name, ::Proc.new{ self })
|
60
|
+
value.respond_to?(:call) ? value : ::Proc.new{ value }
|
61
61
|
end
|
62
62
|
|
63
63
|
def call_spy_normalize_method_name(name)
|
64
|
-
METHOD_NAME_REPLACEMENTS.reduce(name.to_s)
|
64
|
+
METHOD_NAME_REPLACEMENTS.reduce(name.to_s) do |acc, (source, replacement)|
|
65
65
|
acc.gsub(source, replacement)
|
66
|
-
|
66
|
+
end
|
67
67
|
end
|
68
68
|
|
69
69
|
def call_spy_define_spied_method(name)
|
@@ -79,7 +79,9 @@ module MuchStub
|
|
79
79
|
spied_method_name = query_method_match[1]
|
80
80
|
query_method_suffix = query_method_match[2]
|
81
81
|
method_name = call_spy_normalize_method_name(spied_method_name)
|
82
|
-
call_spy_define_metaclass_method(
|
82
|
+
call_spy_define_metaclass_method(
|
83
|
+
"#{method_name}#{query_method_suffix}",
|
84
|
+
) do
|
83
85
|
yield(method_name) if ::Kernel.block_given?
|
84
86
|
end
|
85
87
|
end
|
@@ -89,36 +91,35 @@ module MuchStub
|
|
89
91
|
metaclass.define_method(name, &block)
|
90
92
|
end
|
91
93
|
|
94
|
+
def respond_to_missing?(_name, *_args)
|
95
|
+
false
|
96
|
+
end
|
97
|
+
|
92
98
|
def method_missing(name, *args, &block)
|
93
99
|
if (match = name.match(/(\w+)(_calls)\z/))
|
94
100
|
call_spy_define_query_method(match) do |method_name|
|
95
101
|
@call_spy_method_calls[method_name]
|
96
102
|
end
|
97
|
-
self.__send__(name, *args, &block)
|
98
103
|
elsif (match = name.match(/(\w+)(_last_called_with)\z/))
|
99
104
|
call_spy_define_query_method(match) do |method_name|
|
100
|
-
|
105
|
+
__send__("#{method_name}_calls").last
|
101
106
|
end
|
102
|
-
self.__send__(name, *args, &block)
|
103
107
|
elsif (match = name.match(/(\w+)(_called_with)\z/))
|
104
108
|
call_spy_define_query_method(match) do |method_name|
|
105
|
-
|
109
|
+
__send__("#{method_name}_last_called_with")
|
106
110
|
end
|
107
|
-
self.__send__(name, *args, &block)
|
108
111
|
elsif (match = name.match(/(\w+)(_call_count)\z/))
|
109
112
|
call_spy_define_query_method(match) do |method_name|
|
110
|
-
|
113
|
+
__send__("#{method_name}_calls").size
|
111
114
|
end
|
112
|
-
self.__send__(name, *args, &block)
|
113
115
|
elsif (match = name.match(/(\w+)(_called\?)\z/))
|
114
116
|
call_spy_define_query_method(match) do |method_name|
|
115
|
-
|
117
|
+
__send__("#{method_name}_call_count") > 0
|
116
118
|
end
|
117
|
-
self.__send__(name, *args, &block)
|
118
119
|
else
|
119
120
|
call_spy_define_spied_method(name)
|
120
|
-
self.__send__(name, *args, &block)
|
121
121
|
end
|
122
|
+
__send__(name, *args, &block)
|
122
123
|
end
|
123
124
|
end
|
124
125
|
end
|
data/lib/much-stub/version.rb
CHANGED
data/lib/much-stub.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "much-stub/version"
|
2
4
|
|
3
5
|
require "much-stub/call"
|
@@ -13,66 +15,70 @@ module MuchStub
|
|
13
15
|
end
|
14
16
|
|
15
17
|
def self.arity_matches?(method, args)
|
16
|
-
|
17
|
-
return true if method.arity
|
18
|
-
|
18
|
+
# mandatory args
|
19
|
+
return true if method.arity == args.size
|
20
|
+
# variable args
|
21
|
+
return true if method.arity < 0 && args.size >= (method.arity + 1).abs
|
22
|
+
|
23
|
+
false
|
19
24
|
end
|
20
25
|
|
21
26
|
def self.stub(obj, meth, &block)
|
22
|
-
key =
|
23
|
-
|
24
|
-
|
27
|
+
key = stub_key(obj, meth)
|
28
|
+
stubs[key] ||= MuchStub::Stub.new(obj, meth, caller_locations)
|
29
|
+
stubs[key].tap{ |s| s.do = block }
|
25
30
|
end
|
26
31
|
|
27
32
|
def self.call(*args, &block)
|
28
|
-
|
33
|
+
stub(*args, &block)
|
29
34
|
end
|
30
35
|
|
31
36
|
def self.stub_on_call(*args, &on_call_block)
|
32
|
-
|
37
|
+
stub(*args).on_call(&on_call_block)
|
33
38
|
end
|
34
39
|
|
35
40
|
def self.on_call(*args, &on_call_block)
|
36
|
-
|
41
|
+
stub_on_call(*args, &on_call_block)
|
37
42
|
end
|
38
43
|
|
39
44
|
def self.unstub(obj, meth)
|
40
|
-
key =
|
41
|
-
(
|
45
|
+
key = stub_key(obj, meth)
|
46
|
+
(stubs.delete(key) || MuchStub::NullStub.new).teardown
|
42
47
|
end
|
43
48
|
|
44
49
|
def self.unstub!
|
45
|
-
|
50
|
+
stubs.keys.each{ |key| stubs.delete(key).teardown }
|
46
51
|
end
|
47
52
|
|
48
53
|
def self.stub_send(obj, meth, *args, &block)
|
49
54
|
orig_caller = caller_locations
|
50
|
-
stub =
|
51
|
-
|
52
|
-
|
55
|
+
stub =
|
56
|
+
stubs.fetch(MuchStub::Stub.key(obj, meth)) do
|
57
|
+
raise NotStubbedError, "`#{meth}` not stubbed.", orig_caller.map(&:to_s)
|
58
|
+
end
|
53
59
|
stub.call_method(args, &block)
|
54
60
|
end
|
55
61
|
|
56
62
|
def self.tap(obj, meth, &tap_block)
|
57
|
-
|
58
|
-
|
59
|
-
tap_block
|
60
|
-
|
61
|
-
|
63
|
+
stub(obj, meth) do |*args, &block|
|
64
|
+
stub_send(obj, meth, *args, &block).tap do |value|
|
65
|
+
tap_block&.call(value, *args, &block)
|
66
|
+
end
|
67
|
+
end
|
62
68
|
end
|
63
69
|
|
64
70
|
def self.tap_on_call(obj, meth, &on_call_block)
|
65
|
-
|
66
|
-
on_call_block
|
67
|
-
|
71
|
+
tap(obj, meth) do |value, *args, &block|
|
72
|
+
on_call_block&.call(value, MuchStub::Call.new(*args, &block))
|
73
|
+
end
|
68
74
|
end
|
69
75
|
|
70
76
|
def self.spy(obj, *meths, **return_values)
|
71
77
|
MuchStub::CallSpy.new(**return_values).call_spy_tap do |spy|
|
72
78
|
meths.each do |meth|
|
73
|
-
|
79
|
+
stub(obj, meth) do |*args, &block|
|
74
80
|
spy.__send__(meth, *args, &block)
|
75
|
-
|
81
|
+
end
|
76
82
|
end
|
77
83
|
end
|
78
84
|
end
|
@@ -114,7 +120,9 @@ module MuchStub
|
|
114
120
|
@method,
|
115
121
|
args,
|
116
122
|
method_name: @method_name,
|
117
|
-
backtrace: orig_caller
|
123
|
+
backtrace: orig_caller,
|
124
|
+
),
|
125
|
+
)
|
118
126
|
end
|
119
127
|
lookup(args, orig_caller).call(*args, &block)
|
120
128
|
rescue NotStubbedError
|
@@ -130,7 +138,9 @@ module MuchStub
|
|
130
138
|
@method,
|
131
139
|
args,
|
132
140
|
method_name: @method_name,
|
133
|
-
backtrace: orig_caller
|
141
|
+
backtrace: orig_caller,
|
142
|
+
),
|
143
|
+
)
|
134
144
|
end
|
135
145
|
@lookup[args] = block
|
136
146
|
self
|
@@ -138,12 +148,12 @@ module MuchStub
|
|
138
148
|
|
139
149
|
def on_call(&on_call_block)
|
140
150
|
stub_block =
|
141
|
-
->(*args, &block)
|
142
|
-
on_call_block
|
151
|
+
->(*args, &block){
|
152
|
+
on_call_block&.call(MuchStub::Call.new(*args, &block))
|
143
153
|
}
|
144
154
|
if @lookup.empty?
|
145
155
|
@do = stub_block
|
146
|
-
elsif @lookup.
|
156
|
+
elsif @lookup.value?(nil)
|
147
157
|
@lookup.transform_values!{ |value| value.nil? ? stub_block : value }
|
148
158
|
end
|
149
159
|
self
|
@@ -157,7 +167,7 @@ module MuchStub
|
|
157
167
|
end
|
158
168
|
|
159
169
|
def inspect
|
160
|
-
"#<#{self.class}:#{"0x0%x"
|
170
|
+
"#<#{self.class}:#{format("0x0%x", (object_id << 1))}" \
|
161
171
|
" @method_name=#{@method_name.inspect}" \
|
162
172
|
">"
|
163
173
|
end
|
@@ -169,7 +179,7 @@ module MuchStub
|
|
169
179
|
msg = "#{object.inspect} does not respond to `#{@method_name}`"
|
170
180
|
raise StubError, msg, orig_caller.map(&:to_s)
|
171
181
|
end
|
172
|
-
is_constant = object.
|
182
|
+
is_constant = object.is_a?(Module)
|
173
183
|
local_object_methods = object.methods(false).map(&:to_s)
|
174
184
|
all_object_methods = object.methods.map(&:to_s)
|
175
185
|
if (is_constant && !local_object_methods.include?(@method_name)) ||
|
@@ -180,7 +190,8 @@ module MuchStub
|
|
180
190
|
method
|
181
191
|
end
|
182
192
|
|
183
|
-
|
193
|
+
# already stubbed
|
194
|
+
unless local_object_methods.include?(@name)
|
184
195
|
@metaclass.send(:alias_method, @name, @method_name)
|
185
196
|
end
|
186
197
|
@method = object.method(@name)
|
@@ -194,19 +205,21 @@ module MuchStub
|
|
194
205
|
end
|
195
206
|
|
196
207
|
def lookup(args, orig_caller)
|
197
|
-
@lookup.fetch(args)
|
198
|
-
self.do ||
|
208
|
+
@lookup.fetch(args) do
|
209
|
+
self.do ||
|
210
|
+
begin
|
199
211
|
msg = "#{inspect_call(args)} not stubbed."
|
200
212
|
inspect_lookup_stubs.tap do |stubs|
|
201
|
-
msg += "\nStubs:\n#{stubs}"
|
213
|
+
msg += "\nStubs:\n#{stubs}" unless stubs.empty?
|
202
214
|
end
|
203
215
|
raise NotStubbedError, msg, orig_caller.map(&:to_s)
|
204
216
|
end
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
217
|
+
end ||
|
218
|
+
raise(
|
219
|
+
StubError,
|
220
|
+
"#{inspect_call(args)} stubbed with no block.",
|
221
|
+
orig_caller.map(&:to_s),
|
222
|
+
)
|
210
223
|
end
|
211
224
|
|
212
225
|
def inspect_lookup_stubs
|
@@ -242,9 +255,12 @@ module MuchStub
|
|
242
255
|
end
|
243
256
|
end
|
244
257
|
|
245
|
-
NullStub =
|
246
|
-
|
247
|
-
|
258
|
+
NullStub =
|
259
|
+
Class.new do
|
260
|
+
def teardown
|
261
|
+
# no-op
|
262
|
+
end
|
263
|
+
end
|
248
264
|
|
249
265
|
module ParameterList
|
250
266
|
LETTERS = ("a".."z").to_a.freeze
|
@@ -257,8 +273,6 @@ module MuchStub
|
|
257
273
|
params.join(", ")
|
258
274
|
end
|
259
275
|
|
260
|
-
private
|
261
|
-
|
262
276
|
def self.get_arity(object, method_name)
|
263
277
|
object.method(method_name).arity
|
264
278
|
rescue NameError
|
data/much-stub.gemspec
CHANGED
@@ -1,24 +1,32 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
lib = File.expand_path("../lib", __FILE__)
|
3
5
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
6
|
require "much-stub/version"
|
5
7
|
|
6
8
|
Gem::Specification.new do |gem|
|
7
|
-
gem.name
|
8
|
-
gem.version
|
9
|
-
gem.authors
|
10
|
-
gem.email
|
11
|
-
|
12
|
-
gem.
|
13
|
-
|
14
|
-
gem.
|
15
|
-
|
16
|
-
|
9
|
+
gem.name = "much-stub"
|
10
|
+
gem.version = MuchStub::VERSION
|
11
|
+
gem.authors = ["Kelly Redding", "Collin Redding"]
|
12
|
+
gem.email = ["kelly@kellyredding.com", "collin.redding@me.com"]
|
13
|
+
|
14
|
+
gem.summary =
|
15
|
+
"Stubbing API for replacing method calls on objects in test runs."
|
16
|
+
gem.description =
|
17
|
+
"Stubbing API for replacing method calls on objects in test runs."
|
18
|
+
|
19
|
+
gem.homepage = "https://github.com/redding/much-stub"
|
20
|
+
gem.license = "MIT"
|
21
|
+
|
22
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
23
|
+
|
17
24
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
25
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
26
|
gem.require_paths = ["lib"]
|
20
27
|
|
21
|
-
gem.required_ruby_version = "
|
28
|
+
gem.required_ruby_version = ">= 2.5"
|
22
29
|
|
23
|
-
gem.add_development_dependency("assert",
|
30
|
+
gem.add_development_dependency("assert", ["~> 2.19.2"])
|
31
|
+
gem.add_development_dependency("much-style-guide", ["~> 0.6.0"])
|
24
32
|
end
|
data/test/helper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# this file is automatically required when you run `assert`
|
2
4
|
# put any test helpers here
|
3
5
|
|
@@ -12,7 +14,7 @@ require "test/support/factory"
|
|
12
14
|
# 1.8.7 backfills
|
13
15
|
|
14
16
|
# Array#sample
|
15
|
-
if !(a =
|
17
|
+
if !(a = []).respond_to?(:sample) && a.respond_to?(:choice)
|
16
18
|
class Array
|
17
19
|
alias_method :sample, :choice
|
18
20
|
end
|