duck_puncher 4.2.3 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +96 -88
- data/Rakefile +7 -7
- data/lib/duck_puncher.rb +24 -12
- data/lib/duck_puncher/duck.rb +4 -22
- data/lib/duck_puncher/ducks/object.rb +4 -2
- data/lib/duck_puncher/ducks/string.rb +1 -1
- data/lib/duck_puncher/registration.rb +3 -5
- data/lib/duck_puncher/unique_duck.rb +26 -0
- data/lib/duck_puncher/version.rb +1 -1
- data/test/lib/duck_puncher/numeric_test.rb +1 -1
- data/test/lib/duck_puncher/string_test.rb +2 -2
- data/test/lib/duck_puncher_test.rb +9 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54ed4f3463d0da2ed92a25d2d91953a9ac1ac4ab
|
4
|
+
data.tar.gz: 8972aa4d5fb7e54300db682202d41147d9dc770f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a460f7785607d0b87656670d0c1fbb5f2d8868803d40aed864b202ae4fb60449922f99224ecac2aaa75d3d72c686390cfeb1593d265b4392de62f0263477eae
|
7
|
+
data.tar.gz: c931bcbf428f270460f7f41509a2cd808393bc6f2ac377c8c611c3342da1e4c8a9514c329b993d633b1d59584c8a277cec1c3b849815f617a2617ae58e004548
|
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -1,44 +1,13 @@
|
|
1
1
|
# DuckPuncher [![Gem Version](https://badge.fury.io/rb/duck_puncher.svg)](http://badge.fury.io/rb/duck_puncher) [![Build Status](https://travis-ci.org/ridiculous/duck_puncher.svg)](https://travis-ci.org/ridiculous/duck_puncher) [![Code Climate](https://codeclimate.com/github/ridiculous/duck_puncher/badges/gpa.svg)](https://codeclimate.com/github/ridiculous/duck_puncher)
|
2
2
|
|
3
|
-
DuckPuncher provides an interface for administering __duck punches__ (a.k.a "monkey patches"). Punches can be
|
3
|
+
DuckPuncher provides an interface for administering __duck punches__ (a.k.a "monkey patches"). Punches can be applied permanently via an extension
|
4
|
+
or temporarily as a decorator. Decorator classes are generated when an extension is registered and used via `Object#punch`. The object is wrapped
|
5
|
+
in one decorator for each of the object's ancestors (with registered punches) and behaves much like it's extended cousin, `Object.punch!`.
|
4
6
|
|
5
|
-
|
6
|
-
* as a decorator
|
7
|
-
|
8
|
-
Default extensions:
|
7
|
+
## Install
|
9
8
|
|
10
9
|
```ruby
|
11
|
-
|
12
|
-
#m # => `[].m(:to_s)`
|
13
|
-
#m! # => `[].m!(:upcase)`
|
14
|
-
#mm # => `[].mm(:sub, /[aeiou]/, '*')`
|
15
|
-
#mm! # => `[].mm!(:sub, /[aeiou]/, '*')`
|
16
|
-
#except # => `[].except('foo', 'bar')`
|
17
|
-
#map_keys # => `[].map_keys(:id)`
|
18
|
-
Hash
|
19
|
-
#dig # => `{a: 1, b: {c: 2}}.dig(:b, :c)` (Standard in Ruby >= 2.3)
|
20
|
-
#compact # => `{a: 1, b: nil}.compact` # => {a: 1}
|
21
|
-
Numeric
|
22
|
-
#to_currency # => `25.245.to_currency` # => 25.25
|
23
|
-
#to_duration # => `10_000.to_duration` # => '2 h 46 min'
|
24
|
-
#to_time_ago # => `10_000.to_time_ago` # => '2 hours ago'
|
25
|
-
#to_rad # => `10.15.to_rad` # => 0.17715091907742445
|
26
|
-
String
|
27
|
-
#pluralize # => `'hour'.pluralize(2)` # => "hours"
|
28
|
-
#underscore # => `'DJ::JSONStorage'.underscore` # => 'dj/json_storage'
|
29
|
-
#to_boolean # => `'true'.to_boolean` # => true
|
30
|
-
#constantize # => `'MiniTest::Test'.constantize` # => MiniTest::Test
|
31
|
-
Module
|
32
|
-
#local_methods # => `Kernel.local_methods` # => returns the methods defined directly in the class + nested constants w/ methods
|
33
|
-
Object
|
34
|
-
#clone! # => `Object.new.clone!` # => a deep clone of the object (using Marshal.dump)
|
35
|
-
#punch # => `Object.new.punch` # => a copy of Object.new with String punches mixed in
|
36
|
-
#punch! # => `Object.new.punch!` # => destructive version applies extensions directly to the base object
|
37
|
-
#echo # => `Object.new.echo.inspect` # => spits out the caller and value of the object and returns the object
|
38
|
-
#track # => `Object.new.track` # => Trace methods calls to the object (requires [object_tracker](https://github.com/ridiculous/object_tracker), which it'll try to download)
|
39
|
-
Method
|
40
|
-
#to_instruct # => `Benchmark.method(:measure).to_instruct` returns the Ruby VM instruction sequence for the method
|
41
|
-
#to_source # => `Benchmark.method(:measure).to_source` returns the method definition as a string
|
10
|
+
gem 'duck_puncher', '~> 4.3'
|
42
11
|
```
|
43
12
|
|
44
13
|
## Usage
|
@@ -55,12 +24,18 @@ Punch individual ducks by name:
|
|
55
24
|
DuckPuncher.(Hash, Object)
|
56
25
|
```
|
57
26
|
|
58
|
-
|
27
|
+
Add `punch` as a proxy method to all punches:
|
59
28
|
|
60
29
|
```ruby
|
61
30
|
DuckPuncher.(Object, only: :punch)
|
62
31
|
```
|
63
32
|
|
33
|
+
Redirect the punches registered for a class to another target:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
DuckPuncher.(Object, target: String)
|
37
|
+
```
|
38
|
+
|
64
39
|
### Tactical punches
|
65
40
|
|
66
41
|
`DuckPuncher` extends the amazing [Usable](https://github.com/ridiculous/usable) gem, so you can configure only the punches you want! For instance:
|
@@ -101,27 +76,84 @@ When there are no punches registered for a class, it'll search the ancestor list
|
|
101
76
|
a method defined `echo`, but when we punch `Object`, it means all subclasses have access to the same methods, even with soft punches.
|
102
77
|
|
103
78
|
```ruby
|
104
|
-
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
def hard_punch
|
109
|
-
('a'..'z').punch!.m!(:upcase).mm!(:*, 3).echo
|
110
|
-
end
|
111
|
-
|
79
|
+
>> DuckPuncher.(Object, only: [:punch, :punch!])
|
80
|
+
>> def soft_punch() ('a'..'z').punch.echo(1).map(&:upcase) end
|
81
|
+
>> def hard_punch() ('a'..'z').punch!.echo(1).mm(:*, 3) end
|
112
82
|
>> soft_punch
|
113
|
-
"a..z
|
114
|
-
|
83
|
+
"a".."z"
|
84
|
+
* /.../delegate.rb:85:in `method_missing'
|
85
|
+
=> ["A", "B", "C", "D", "E", "F", "G", "H", "I", ...]
|
115
86
|
>> hard_punch
|
116
|
-
"
|
87
|
+
"a".."z"
|
88
|
+
* (irb):11:in `hard_punch'
|
117
89
|
=> ["AAA", "BBB", "CCC", "DDDD", ...]
|
118
90
|
```
|
119
91
|
|
120
|
-
|
92
|
+
## Default extensions
|
93
|
+
|
94
|
+
#### Enumerable (including Array, Set, Range, and Enumerator)
|
95
|
+
```ruby
|
96
|
+
[].m(:to_s)
|
97
|
+
[].m!(:upcase)
|
98
|
+
[].mm(:sub, /[aeiou]/, '*')
|
99
|
+
[].mm!(:sub, /[aeiou]/, '*')
|
100
|
+
[].except(:foo, :bar, :baz)
|
101
|
+
[].map_keys(:id)
|
102
|
+
```
|
103
|
+
|
104
|
+
#### Hash
|
105
|
+
```ruby
|
106
|
+
{ a: 1, b: { c: 2 }}.dig(:b, :c) # => 2
|
107
|
+
# ii Standard in Ruby >= 2.3
|
108
|
+
{ a: 1, b: nil }.compact # => {a: 1}
|
109
|
+
# !! destructive
|
110
|
+
```
|
111
|
+
|
112
|
+
#### Numeric
|
113
|
+
```ruby
|
114
|
+
25.245.to_currency # => "25.25"
|
115
|
+
10_000.to_duration # => "2 h 46 min"
|
116
|
+
10_000.to_time_ago # => "2 hours ago"
|
117
|
+
10.15.to_rad # => "0.17715091907742445"
|
118
|
+
```
|
119
|
+
|
120
|
+
#### String
|
121
|
+
```ruby
|
122
|
+
'hour'.pluralize(2) # => "hours"
|
123
|
+
'DJ::JSONStorage'.underscore # => "dj/json_storage"
|
124
|
+
'true'.to_boolean # => "true"
|
125
|
+
'MiniTest::Test'.constantize # => MiniTest::Test
|
126
|
+
```
|
127
|
+
|
128
|
+
#### Module
|
129
|
+
```ruby
|
130
|
+
Kernel.local_methods # => methods defined directly in the class + nested constants w/ methods
|
131
|
+
```
|
132
|
+
|
133
|
+
#### Object
|
134
|
+
```ruby
|
135
|
+
Object.new.clone! # => a deep clone of the object (using Marshal.dump)
|
136
|
+
Object.new.punch # => a copy of Object.new with String punches mixed in
|
137
|
+
Object.new.punch! # => destructive version applies extensions directly to the base object
|
138
|
+
Object.new.echo # => prints and returns itself. Accepts a number,
|
139
|
+
# indicating how many lines of the trace to display
|
140
|
+
Object.new.track # => Trace methods calls to the object
|
141
|
+
# !! requires [object_tracker](https://github.com/ridiculous/object_tracker), which it'll try to download
|
142
|
+
```
|
121
143
|
|
122
|
-
|
144
|
+
#### Method
|
145
|
+
```ruby
|
146
|
+
require 'benchmark'
|
147
|
+
|
148
|
+
Benchmark.method(:measure).to_instruct # => the Ruby VM instruction sequence for the method
|
149
|
+
Benchmark.method(:measure).to_source # => the method definition as a string
|
150
|
+
```
|
151
|
+
|
152
|
+
## Registering custom punches
|
153
|
+
|
154
|
+
DuckPuncher allows you to utilize the `punch` and `punch!` interface to __decorate__ or __extend__, respectively, any object with your own punches. Simply
|
123
155
|
call `DuckPuncher.register` with the name of your module (or an array of names) and any of
|
124
|
-
[these options](https://github.com/ridiculous/duck_puncher/blob/master/lib/duck_puncher/duck.rb#
|
156
|
+
[these options](https://github.com/ridiculous/duck_puncher/blob/master/lib/duck_puncher/duck.rb#L10).
|
125
157
|
|
126
158
|
|
127
159
|
```ruby
|
@@ -159,13 +191,7 @@ user = User.new('Ryan').punch
|
|
159
191
|
user.call_with_retry(19.99)
|
160
192
|
```
|
161
193
|
|
162
|
-
To register _and_ punch
|
163
|
-
|
164
|
-
## Install
|
165
|
-
|
166
|
-
```ruby
|
167
|
-
gem 'duck_puncher'
|
168
|
-
```
|
194
|
+
To register the extension _and_ punch the class, use `DuckPuncher.register!`
|
169
195
|
|
170
196
|
## Logging
|
171
197
|
|
@@ -215,36 +241,18 @@ DuckPuncher.(:Object, only: :track)
|
|
215
241
|
Donald.track
|
216
242
|
Duck.track
|
217
243
|
>> Duck.usable Donald, only: :tap_tap
|
218
|
-
* called "Donald.respond_to?" with to_str, true [RUBY CORE] (0.00002)
|
219
|
-
* called "Donald.respond_to?" with to_str, true [RUBY CORE] (0.00001)
|
220
|
-
* called "Donald.respond_to?" with to_ary, true [RUBY CORE] (0.00001)
|
221
|
-
* called "Donald.to_s" [RUBY CORE] (0.00001)
|
222
|
-
* called "Duck.usable_config" [ruby-2.3.0@duck_puncher/gems/usable-1.2.0/lib/usable.rb:10] (0.00002)
|
223
|
-
* called "Duck.usable_config" [ruby-2.3.0@duck_puncher/gems/usable-1.2.0/lib/usable.rb:10] (0.00001)
|
224
|
-
* called "Donald.const_defined?" with UsableSpec [RUBY CORE] (0.00001)
|
225
|
-
* called "Donald.dup" [RUBY CORE] (0.00002)
|
226
|
-
* called "Donald.name" [RUBY CORE] (0.00000)
|
227
|
-
* called "Donald.instance_methods" [RUBY CORE] (0.00001)
|
228
|
-
* called "Duck.const_defined?" with DonaldUsed [RUBY CORE] (0.00001)
|
229
|
-
|
230
|
-
* called "Donald.respond_to?" with to_str, true [RUBY CORE] (0.00000)
|
231
|
-
* called "Donald.respond_to?" with to_ary, true [RUBY CORE] (0.00000)
|
232
|
-
* called "Donald.to_s" [RUBY CORE] (0.00035)
|
233
|
-
* called "Duck.const_set" with DonaldUsed, #<Module:0x007fe23a261618> [RUBY CORE] (0.00002)
|
234
|
-
* called "Duck.usable_config" [ruby-2.3.0@duck_puncher/gems/usable-1.2.0/lib/usable.rb:10] (0.00000)
|
235
|
-
* called "Donald.respond_to?" with to_str, true [RUBY CORE] (0.00000)
|
236
|
-
* called "Donald.respond_to?" with to_ary, true [RUBY CORE] (0.00001)
|
237
|
-
* called "Donald.to_s" [RUBY CORE] (0.00019)
|
238
|
-
* called "Donald.respond_to?" with to_str, true [RUBY CORE] (0.00001)
|
239
|
-
* called "Donald.respond_to?" with to_str, true [RUBY CORE] (0.00000)
|
240
|
-
* called "Donald.respond_to?" with to_ary, true [RUBY CORE] (0.00000)
|
241
|
-
* called "Donald.to_s" [RUBY CORE] (0.00000)
|
242
|
-
* called "Duck.include" with Duck::DonaldUsed [RUBY CORE] (0.00001)
|
243
|
-
* called "Duck#send" with include, Duck::DonaldUsed [RUBY CORE] (0.00024)
|
244
|
-
* called "Duck.usable!" with #<Usable::ModExtender:0x007fe23a261ca8> [ruby-2.3.0@duck_puncher/gems/usable-1.2.0/lib/usable.rb:41] (0.00143)
|
245
|
-
* called "Donald.const_defined?" with UsableSpec [RUBY CORE] (0.00001)
|
246
|
-
* called "Duck.usable" with Donald, {:only=>:tap_tap} [ruby-2.3.0@duck_puncher/gems/usable-1.2.0/lib/usable.rb:30] (0.00189)
|
247
|
-
# ... You get the idea.
|
244
|
+
# => * called "Donald.respond_to?" with to_str, true [RUBY CORE] (0.00002)
|
245
|
+
# => * called "Donald.respond_to?" with to_str, true [RUBY CORE] (0.00001)
|
246
|
+
# => * called "Donald.respond_to?" with to_ary, true [RUBY CORE] (0.00001)
|
247
|
+
# => * called "Donald.to_s" [RUBY CORE] (0.00001)
|
248
|
+
# => * called "Duck.usable_config" [ruby-2.3.0@duck_puncher/gems/usable-1.2.0/lib/usable.rb:10] (0.00002)
|
249
|
+
# => * called "Duck.usable_config" [ruby-2.3.0@duck_puncher/gems/usable-1.2.0/lib/usable.rb:10] (0.00001)
|
250
|
+
# => * called "Donald.const_defined?" with UsableSpec [RUBY CORE] (0.00001)
|
251
|
+
# => * called "Donald.dup" [RUBY CORE] (0.00002)
|
252
|
+
# => * called "Donald.name" [RUBY CORE] (0.00000)
|
253
|
+
# => * called "Donald.instance_methods" [RUBY CORE] (0.00001)
|
254
|
+
# => * called "Duck.const_defined?" with DonaldUsed [RUBY CORE] (0.00001)
|
255
|
+
# => ...
|
248
256
|
```
|
249
257
|
|
250
258
|
## Contributing
|
data/Rakefile
CHANGED
@@ -2,12 +2,12 @@ require 'bundler/gem_tasks'
|
|
2
2
|
require 'rake'
|
3
3
|
require 'rake/testtask'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
root = Pathname.new File.expand_path('..', __FILE__)
|
6
|
+
tasks = []
|
7
|
+
Dir[root.join('test/lib/**/*_test.rb')].each do |file|
|
8
|
+
tasks << Rake::TestTask.new(file.split('/').last[/\w+/].to_sym) do |t|
|
9
|
+
t.pattern = file.sub(root.to_s + '/', '')
|
10
|
+
end
|
11
11
|
end
|
12
12
|
|
13
|
-
task default:
|
13
|
+
task default: tasks.map(&:name)
|
data/lib/duck_puncher.rb
CHANGED
@@ -15,6 +15,7 @@ require 'duck_puncher/utilities'
|
|
15
15
|
require 'duck_puncher/ancestral_hash'
|
16
16
|
require 'duck_puncher/duck'
|
17
17
|
require 'duck_puncher/ducks'
|
18
|
+
require 'duck_puncher/unique_duck'
|
18
19
|
|
19
20
|
module DuckPuncher
|
20
21
|
autoload :GemInstaller, 'duck_puncher/gem_installer'
|
@@ -22,11 +23,10 @@ module DuckPuncher
|
|
22
23
|
|
23
24
|
class << self
|
24
25
|
# @description Include additional functionality
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
include Registration, Decoration, Utilities, AncestralHash
|
26
|
+
include Registration # [:register, :deregister]
|
27
|
+
include Decoration # [:decorators, :build_decorator_class, :decorate, :cached_decorators, :undecorate]
|
28
|
+
include Utilities # [:lookup_constant, :redefine_constant]
|
29
|
+
include AncestralHash # [:ancestral_hash]
|
30
30
|
|
31
31
|
attr_accessor :logger
|
32
32
|
|
@@ -39,14 +39,15 @@ module DuckPuncher
|
|
39
39
|
classes = args.any? ? args : Ducks.list.keys
|
40
40
|
classes.each do |klass|
|
41
41
|
klass = lookup_constant(klass)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
42
|
+
Ducks[klass].sort.each do |duck|
|
43
|
+
duck.punch_options = Ducks::Object.instance_method(:clone!).bind(options).call
|
44
|
+
duck.punch_options[:target] ||= klass
|
45
|
+
if punched_ducks.include?(duck)
|
46
|
+
logger.warn %(Already punched #{duck.mod.name})
|
47
|
+
elsif duck.punch(duck.punch_options).any?
|
47
48
|
punched_ducks << duck
|
48
49
|
else
|
49
|
-
logger.
|
50
|
+
logger.warn %(No punches were thrown)
|
50
51
|
end
|
51
52
|
end
|
52
53
|
end
|
@@ -58,7 +59,18 @@ module DuckPuncher
|
|
58
59
|
alias punch! call
|
59
60
|
|
60
61
|
def punched_ducks
|
61
|
-
@punched_ducks ||=
|
62
|
+
@punched_ducks ||= Set.new
|
63
|
+
end
|
64
|
+
|
65
|
+
def register(*)
|
66
|
+
target, *_ = super
|
67
|
+
decorators[target] = build_decorator_class(*Ducks[target])
|
68
|
+
@cached_decorators = nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def deregister(*)
|
72
|
+
super
|
73
|
+
@cached_decorators = nil
|
62
74
|
end
|
63
75
|
end
|
64
76
|
end
|
data/lib/duck_puncher/duck.rb
CHANGED
@@ -3,7 +3,7 @@ module DuckPuncher
|
|
3
3
|
attr_accessor :target, :mod, :options
|
4
4
|
|
5
5
|
# @param target [String,Class] Class or module to punch
|
6
|
-
# @param mod [String,Module] The module that defines the extensions
|
6
|
+
# @param mod [String,Module] The module that defines the extensions
|
7
7
|
# @param [Hash] options to modify the duck #punch method behavior
|
8
8
|
# @option options :before [Proc] A hook that is called with the target class before +punch+
|
9
9
|
# @option options :after [Proc] A hook that is called with the target class after +punch+
|
@@ -19,35 +19,17 @@ module DuckPuncher
|
|
19
19
|
# @option options [Symbol,String] :method Specifies if the methods should be included or prepended (:include)
|
20
20
|
# @return [Class] The class that was just punched
|
21
21
|
def punch(opts = {})
|
22
|
-
opts = options.merge
|
22
|
+
opts = options.merge(opts)
|
23
23
|
targets = Array(opts[:target] || self.target)
|
24
24
|
targets.each do |target|
|
25
25
|
options[:before].call(target) if options[:before]
|
26
|
+
punches = Array(opts[:only] || Ducks::Module.instance_method(:local_methods).bind(mod).call)
|
27
|
+
DuckPuncher.logger.info %Q(#{target}#{" <-- #{mod.name}#{punches}" if punches.any?})
|
26
28
|
target.extend Usable
|
27
29
|
target.usable mod, only: opts[:only], method: opts[:method]
|
28
30
|
options[:after].call(target) if options[:after]
|
29
31
|
end
|
30
32
|
targets
|
31
33
|
end
|
32
|
-
|
33
|
-
#
|
34
|
-
# Required to play nice in a Set
|
35
|
-
#
|
36
|
-
|
37
|
-
def eql?(other)
|
38
|
-
"#{target}-#{mod}" == "#{other.target}-#{other.mod}"
|
39
|
-
end
|
40
|
-
|
41
|
-
def hash
|
42
|
-
target.to_s.hash + mod.to_s.hash
|
43
|
-
end
|
44
|
-
|
45
|
-
#
|
46
|
-
# Required for sorting
|
47
|
-
#
|
48
|
-
|
49
|
-
def <=>(other)
|
50
|
-
target <=> other.target
|
51
|
-
end
|
52
34
|
end
|
53
35
|
end
|
@@ -25,8 +25,10 @@ module DuckPuncher
|
|
25
25
|
self
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
# @param [Integer] trace The number of lines from the stack trace to print (nil)
|
29
|
+
def echo(trace = nil)
|
30
|
+
p self
|
31
|
+
puts caller_locations.take(trace).map { |l| l.to_s.prepend('* ') }.join("\n") if trace
|
30
32
|
self
|
31
33
|
end
|
32
34
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module DuckPuncher
|
2
2
|
module Ducks
|
3
3
|
module String
|
4
|
-
BOOLEAN_MAP = ::Hash[%w(true 1 yes y on).product([true])
|
4
|
+
BOOLEAN_MAP = ::Hash[%w(true 1 yes y on).product([true])].freeze
|
5
5
|
|
6
6
|
def pluralize(count)
|
7
7
|
"#{self}#{'s' if count != 1}"
|
@@ -5,12 +5,11 @@ module DuckPuncher
|
|
5
5
|
options = mods.last.is_a?(Hash) ? mods.pop : {}
|
6
6
|
target = DuckPuncher.lookup_constant target
|
7
7
|
Ducks.list[target] = Set.new [] unless Ducks.list.key?(target)
|
8
|
-
Array(mods).each do |mod|
|
9
|
-
duck = Duck.new target, mod, options
|
8
|
+
mods = Array(mods).each do |mod|
|
9
|
+
duck = UniqueDuck.new Duck.new target, mod, options
|
10
10
|
Ducks.list[target] << duck
|
11
|
-
decorators[target] = build_decorator_class(duck, *Ducks[target])
|
12
11
|
end
|
13
|
-
|
12
|
+
[target, *mods]
|
14
13
|
end
|
15
14
|
|
16
15
|
def register!(*args)
|
@@ -21,7 +20,6 @@ module DuckPuncher
|
|
21
20
|
def deregister(*classes)
|
22
21
|
classes.each &Ducks.list.method(:delete)
|
23
22
|
classes.each &decorators.method(:delete)
|
24
|
-
@cached_decorators = nil
|
25
23
|
end
|
26
24
|
end
|
27
25
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module DuckPuncher
|
2
|
+
class UniqueDuck < DelegateClass(Duck)
|
3
|
+
attr_accessor :punch_options
|
4
|
+
|
5
|
+
#
|
6
|
+
# Required to play nice in a Set
|
7
|
+
#
|
8
|
+
|
9
|
+
def eql?(other)
|
10
|
+
"#{target}-#{mod}" == "#{other.target}-#{other.mod}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def hash
|
14
|
+
target.to_s.hash + mod.to_s.hash + punch_options.to_s.hash
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Sorting
|
19
|
+
#
|
20
|
+
|
21
|
+
def <=>(other)
|
22
|
+
target <=> other.target
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/lib/duck_puncher/version.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require_relative '../../test_helper'
|
2
2
|
|
3
|
-
DuckPuncher.
|
3
|
+
DuckPuncher.(String)
|
4
4
|
|
5
5
|
class StringTest < MiniTest::Test
|
6
6
|
def test_pluralize
|
@@ -29,7 +29,7 @@ class StringTest < MiniTest::Test
|
|
29
29
|
refute 'no'.to_boolean
|
30
30
|
refute 'off'.to_boolean
|
31
31
|
refute ''.to_boolean
|
32
|
-
refute '
|
32
|
+
refute 'f'.to_boolean
|
33
33
|
end
|
34
34
|
|
35
35
|
def test_constantize
|
@@ -35,11 +35,18 @@ class DuckPuncherTest < MiniTest::Test
|
|
35
35
|
DuckPuncher.()
|
36
36
|
expected_methods = DuckPuncher::Ducks.list.values.m(:to_a).flatten.m(:mod).m(:local_methods).flatten
|
37
37
|
assert expected_methods.size > 1
|
38
|
+
# Find all ducks that have copied all their methods to the target class (e.g. String)
|
38
39
|
good_ducks = DuckPuncher::Ducks.list.select { |_, ducks|
|
39
|
-
# Assert that all methods were copied over
|
40
40
|
ducks.all? { |duck| (duck.mod.local_methods - duck.target.instance_methods(:false)).size.zero? }
|
41
41
|
}
|
42
|
-
assert good_ducks.size
|
42
|
+
assert good_ducks.size == 6, "Good ducks should be equal to 6 but are #{good_ducks.size}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_call_with_target
|
46
|
+
DuckPuncher.(String, only: [:to_boolean])
|
47
|
+
refute_respond_to @subject, :to_boolean
|
48
|
+
DuckPuncher.(String, target: @subject.class)
|
49
|
+
assert_respond_to @subject, :to_boolean
|
43
50
|
end
|
44
51
|
|
45
52
|
def test_register_with_multiple_mods
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: duck_puncher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Buckley
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: usable
|
@@ -98,6 +98,7 @@ files:
|
|
98
98
|
- ".ruby-gemset"
|
99
99
|
- ".ruby-version"
|
100
100
|
- ".travis.yml"
|
101
|
+
- CHANGELOG.md
|
101
102
|
- Gemfile
|
102
103
|
- LICENSE.txt
|
103
104
|
- README.md
|
@@ -121,6 +122,7 @@ files:
|
|
121
122
|
- lib/duck_puncher/gem_installer.rb
|
122
123
|
- lib/duck_puncher/json_storage.rb
|
123
124
|
- lib/duck_puncher/registration.rb
|
125
|
+
- lib/duck_puncher/unique_duck.rb
|
124
126
|
- lib/duck_puncher/utilities.rb
|
125
127
|
- lib/duck_puncher/version.rb
|
126
128
|
- test/fixtures/test_classes.rb
|