duck_puncher 4.2.3 → 4.3.0
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/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 [](http://badge.fury.io/rb/duck_puncher) [](https://travis-ci.org/ridiculous/duck_puncher) [](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
|