duck_puncher 4.4.2 → 5.0.1
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 +5 -5
- data/duck_puncher.gemspec +3 -3
- data/lib/duck_puncher.rb +9 -1
- data/lib/duck_puncher/defaults.rb +1 -1
- data/lib/duck_puncher/duck.rb +5 -3
- data/lib/duck_puncher/ducks/active_record.rb +55 -48
- data/lib/duck_puncher/ducks/method.rb +1 -1
- data/lib/duck_puncher/ducks/object.rb +16 -7
- data/lib/duck_puncher/gem_installer.rb +1 -1
- data/lib/duck_puncher/json_storage.rb +2 -2
- data/lib/duck_puncher/registration.rb +20 -8
- data/lib/duck_puncher/version.rb +1 -1
- metadata +10 -46
- data/.gitignore +0 -19
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/.travis.yml +0 -10
- data/CHANGELOG.md +0 -20
- data/Gemfile +0 -9
- data/LICENSE.txt +0 -22
- data/README.md +0 -272
- data/Rakefile +0 -13
- data/bin/console +0 -30
- data/test/fixtures/test_classes.rb +0 -42
- data/test/fixtures/wut.rb +0 -7
- data/test/lib/duck_puncher/duck_test.rb +0 -45
- data/test/lib/duck_puncher/enumerable_test.rb +0 -64
- data/test/lib/duck_puncher/hash_test.rb +0 -23
- data/test/lib/duck_puncher/method_test.rb +0 -32
- data/test/lib/duck_puncher/module_test.rb +0 -9
- data/test/lib/duck_puncher/numeric_test.rb +0 -48
- data/test/lib/duck_puncher/object_test.rb +0 -78
- data/test/lib/duck_puncher/string_test.rb +0 -39
- data/test/lib/duck_puncher_test.rb +0 -71
- data/test/test_helper.rb +0 -9
data/.ruby-gemset
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
duck_puncher
|
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
ruby-2.3.0
|
data/.travis.yml
DELETED
data/CHANGELOG.md
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
4.4.2 (01/10/2016)
|
2
|
-
==================
|
3
|
-
|
4
|
-
* Fix glaring bug in Duck class where the `:only` option is ignored (since 4.4.0)
|
5
|
-
|
6
|
-
4.4.1 (12/19/2016)
|
7
|
-
==================
|
8
|
-
|
9
|
-
* Lazy load the rubygems/dependency_installer only when `require!` is called
|
10
|
-
|
11
|
-
4.4.0 (12/4/2016)
|
12
|
-
=================
|
13
|
-
|
14
|
-
* Target objects are no longer extended with the [Usable](https://github.com/ridiculous/usable) module
|
15
|
-
|
16
|
-
4.3.0 (10/10/2016)
|
17
|
-
==================
|
18
|
-
|
19
|
-
* Fix issue with not being able to punch the same duck with different options
|
20
|
-
* Add the `:target` option to `.call` to override the receiving class
|
data/Gemfile
DELETED
data/LICENSE.txt
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
Copyright (c) 2016 Ryan Buckley
|
2
|
-
|
3
|
-
MIT License
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
-
a copy of this software and associated documentation files (the
|
7
|
-
"Software"), to deal in the Software without restriction, including
|
8
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
-
permit persons to whom the Software is furnished to do so, subject to
|
11
|
-
the following conditions:
|
12
|
-
|
13
|
-
The above copyright notice and this permission notice shall be
|
14
|
-
included in all copies or substantial portions of the Software.
|
15
|
-
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
DELETED
@@ -1,272 +0,0 @@
|
|
1
|
-
# DuckPuncher [](http://badge.fury.io/rb/duck_puncher) [](https://travis-ci.org/ridiculous/duck_puncher) [](https://codeclimate.com/github/ridiculous/duck_puncher)
|
2
|
-
|
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!`.
|
6
|
-
|
7
|
-
## Install
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
gem 'duck_puncher', '~> 4.3'
|
11
|
-
```
|
12
|
-
|
13
|
-
## Usage
|
14
|
-
|
15
|
-
Punch all registered ducks:
|
16
|
-
|
17
|
-
```ruby
|
18
|
-
DuckPuncher.()
|
19
|
-
```
|
20
|
-
|
21
|
-
Punch individual ducks by name:
|
22
|
-
|
23
|
-
```ruby
|
24
|
-
DuckPuncher.(Hash, Object)
|
25
|
-
```
|
26
|
-
|
27
|
-
Add `punch` as a proxy method to all punches:
|
28
|
-
|
29
|
-
```ruby
|
30
|
-
DuckPuncher.(Object, only: :punch)
|
31
|
-
```
|
32
|
-
|
33
|
-
Redirect the punches registered for a class to another target:
|
34
|
-
|
35
|
-
```ruby
|
36
|
-
DuckPuncher.(Object, target: String)
|
37
|
-
```
|
38
|
-
|
39
|
-
### Tactical punches
|
40
|
-
|
41
|
-
`DuckPuncher` extends the amazing [Usable](https://github.com/ridiculous/usable) gem, so you can configure only the punches you want! For instance:
|
42
|
-
|
43
|
-
```ruby
|
44
|
-
DuckPuncher.(Numeric, only: [:to_currency, :to_duration])
|
45
|
-
```
|
46
|
-
|
47
|
-
If you punch `Object` then you can use `#punch!` on any object to extend individual instances:
|
48
|
-
|
49
|
-
```ruby
|
50
|
-
>> DuckPuncher.(Object, only: :punch!)
|
51
|
-
>> %w[yes no 1].punch!.m!(:punch).m(:to_boolean)
|
52
|
-
=> [true, false, true]
|
53
|
-
```
|
54
|
-
|
55
|
-
Alternatively, there is also the `Object#punch` method which returns a decorated copy of an object with punches mixed in:
|
56
|
-
```ruby
|
57
|
-
>> DuckPuncher.(Object, only: :punch)
|
58
|
-
>> %w[1 2 3].punch.m(:to_i)
|
59
|
-
=> [1, 2, 3]
|
60
|
-
```
|
61
|
-
|
62
|
-
The `#punch!` method will lookup the extension by the object's class name. The above example works because `Array` and `String` are default extensions. If you want to punch a specific extension, then you can specify it as an argument:
|
63
|
-
```ruby
|
64
|
-
>> LovableDuck = Module.new { def inspect() "I love #{self.first}" end }
|
65
|
-
>> DuckPuncher.register Array, LovableDuck
|
66
|
-
>> ducks = %w[ducks]
|
67
|
-
>> soft_punch = ducks.punch
|
68
|
-
=> "I love ducks"
|
69
|
-
>> soft_punch.class
|
70
|
-
=> DuckPuncher::ArrayDelegator
|
71
|
-
>> ducks.punch!.class
|
72
|
-
=> Array
|
73
|
-
```
|
74
|
-
|
75
|
-
When there are no punches registered for a class, it'll search the ancestor list for a class with registered punches. For example, `Array` doesn't have
|
76
|
-
a method defined `echo`, but when we punch `Object`, it means all subclasses have access to the same methods, even with soft punches.
|
77
|
-
|
78
|
-
```ruby
|
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
|
82
|
-
>> soft_punch
|
83
|
-
"a".."z"
|
84
|
-
* /.../delegate.rb:85:in `method_missing'
|
85
|
-
=> ["A", "B", "C", "D", "E", "F", "G", "H", "I", ...]
|
86
|
-
>> hard_punch
|
87
|
-
"a".."z"
|
88
|
-
* (irb):11:in `hard_punch'
|
89
|
-
=> ["AAA", "BBB", "CCC", "DDDD", ...]
|
90
|
-
```
|
91
|
-
|
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
|
-
```
|
143
|
-
|
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
|
155
|
-
call `DuckPuncher.register` with the name of your module (or an array of names) and any of
|
156
|
-
[these options](https://github.com/ridiculous/duck_puncher/blob/master/lib/duck_puncher/duck.rb#L10).
|
157
|
-
|
158
|
-
```ruby
|
159
|
-
DuckPuncher.register <class>, [<module>, ...]
|
160
|
-
```
|
161
|
-
|
162
|
-
A full example:
|
163
|
-
```ruby
|
164
|
-
# Define some extensions
|
165
|
-
module Billable
|
166
|
-
def call(amt)
|
167
|
-
puts "Attempting to bill #{name} for $#{amt}"
|
168
|
-
fail Errno::ENOENT
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
module Retryable
|
173
|
-
def call_with_retry(*args, retries: 3)
|
174
|
-
call *args
|
175
|
-
rescue Errno::ENOENT
|
176
|
-
puts 'retrying'
|
177
|
-
retry if (retries -= 1) > 0
|
178
|
-
end
|
179
|
-
end
|
180
|
-
```
|
181
|
-
|
182
|
-
```ruby
|
183
|
-
# Our duck
|
184
|
-
class User < Struct.new(:name)
|
185
|
-
end
|
186
|
-
|
187
|
-
# Register the extensions
|
188
|
-
DuckPuncher.register User, Billable, Retryable
|
189
|
-
|
190
|
-
# Add the #punch method to User instances
|
191
|
-
DuckPuncher.(Object, only: :punch)
|
192
|
-
|
193
|
-
# Usage
|
194
|
-
user = User.new('Ryan').punch
|
195
|
-
user.call_with_retry(19.99)
|
196
|
-
```
|
197
|
-
|
198
|
-
To register the extension _and_ punch the class, use `DuckPuncher.register!`
|
199
|
-
|
200
|
-
## Logging
|
201
|
-
|
202
|
-
Get notified of all punches/extensions by changing the logger level:
|
203
|
-
|
204
|
-
```ruby
|
205
|
-
DuckPuncher.logger.level = Logger::INFO
|
206
|
-
```
|
207
|
-
|
208
|
-
The default log level is `DEBUG`
|
209
|
-
|
210
|
-
## Experimental
|
211
|
-
|
212
|
-
__Object#require!__ will try to require a gem, or, if it's not found, then _download_ it! It will also keep track of any
|
213
|
-
downloaded gems and load them for subsequent IRB/rails console sessions. Gems are _not_
|
214
|
-
saved to the Gemfile.
|
215
|
-
|
216
|
-
In the wild:
|
217
|
-
|
218
|
-
```bash
|
219
|
-
>> `require 'pry'`
|
220
|
-
LoadError: cannot load such file -- pry
|
221
|
-
from (irb):1:in `require'
|
222
|
-
from (irb):1
|
223
|
-
from bin/console:10:in `<main>'
|
224
|
-
>> DuckPuncher.(Object, only: :require!)
|
225
|
-
=> nil
|
226
|
-
>> require! 'pry'
|
227
|
-
Fetching: method_source-0.8.2.gem (100%)
|
228
|
-
Fetching: slop-3.6.0.gem (100%)
|
229
|
-
Fetching: coderay-1.1.0.gem (100%)
|
230
|
-
Fetching: pry-0.10.3.gem (100%)
|
231
|
-
=> true
|
232
|
-
>> Pry.start
|
233
|
-
[1] pry(main)>
|
234
|
-
```
|
235
|
-
|
236
|
-
Perfect! Mostly ... although, it doesn't work well with bigger gems or those with native extensions ¯\\\_(ツ)_/¯
|
237
|
-
|
238
|
-
__Object#track__ builds upon `require!` to download the [ObjectTracker](https://github.com/ridiculous/object_tracker) gem,
|
239
|
-
if it's not available in the current load path, and starts tracking the current object!
|
240
|
-
|
241
|
-
```ruby
|
242
|
-
Duck = Class.new
|
243
|
-
Donald = Module.new { def tap_tap() self end }
|
244
|
-
DuckPuncher.(:Object, only: :track)
|
245
|
-
Donald.track
|
246
|
-
Duck.track
|
247
|
-
>> Duck.usable Donald, only: :tap_tap
|
248
|
-
# => * called "Donald.respond_to?" with to_str, true [RUBY CORE] (0.00002)
|
249
|
-
# => * called "Donald.respond_to?" with to_str, true [RUBY CORE] (0.00001)
|
250
|
-
# => * called "Donald.respond_to?" with to_ary, true [RUBY CORE] (0.00001)
|
251
|
-
# => * called "Donald.to_s" [RUBY CORE] (0.00001)
|
252
|
-
# => * called "Duck.usable_config" [ruby-2.3.0@duck_puncher/gems/usable-1.2.0/lib/usable.rb:10] (0.00002)
|
253
|
-
# => * called "Duck.usable_config" [ruby-2.3.0@duck_puncher/gems/usable-1.2.0/lib/usable.rb:10] (0.00001)
|
254
|
-
# => * called "Donald.const_defined?" with UsableSpec [RUBY CORE] (0.00001)
|
255
|
-
# => * called "Donald.dup" [RUBY CORE] (0.00002)
|
256
|
-
# => * called "Donald.name" [RUBY CORE] (0.00000)
|
257
|
-
# => * called "Donald.instance_methods" [RUBY CORE] (0.00001)
|
258
|
-
# => * called "Duck.const_defined?" with DonaldUsed [RUBY CORE] (0.00001)
|
259
|
-
# => ...
|
260
|
-
```
|
261
|
-
|
262
|
-
## Contributing
|
263
|
-
|
264
|
-
* Fork it
|
265
|
-
* Run tests with `rake`
|
266
|
-
* Start an IRB console that already has all your ducks in a row: `bin/console`
|
267
|
-
* Start an IRB console without punching ducks: `PUNCH=no bin/console`
|
268
|
-
* Make changes and submit a PR to [https://github.com/ridiculous/duck_puncher](https://github.com/ridiculous/duck_puncher)
|
269
|
-
|
270
|
-
## License
|
271
|
-
|
272
|
-
MIT
|
data/Rakefile
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
require 'bundler/gem_tasks'
|
2
|
-
require 'rake'
|
3
|
-
require 'rake/testtask'
|
4
|
-
|
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
|
-
end
|
12
|
-
|
13
|
-
task default: tasks.map(&:name)
|
data/bin/console
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'bundler/setup'
|
4
|
-
require 'pp'
|
5
|
-
require 'irb'
|
6
|
-
require 'byebug'
|
7
|
-
|
8
|
-
# Stub out Rails
|
9
|
-
module Rails
|
10
|
-
module VERSION
|
11
|
-
MAJOR = 4
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
# And AR
|
16
|
-
module ActiveRecord
|
17
|
-
class Base
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
require 'duck_puncher'
|
22
|
-
require_relative '../test/fixtures/wut'
|
23
|
-
|
24
|
-
DuckPuncher.logger.level = Logger::DEBUG
|
25
|
-
|
26
|
-
if ENV['PUNCH'] != 'no'
|
27
|
-
DuckPuncher.()
|
28
|
-
end
|
29
|
-
|
30
|
-
IRB.start
|
@@ -1,42 +0,0 @@
|
|
1
|
-
module CustomPunch
|
2
|
-
def talk
|
3
|
-
p self
|
4
|
-
self
|
5
|
-
end
|
6
|
-
end
|
7
|
-
|
8
|
-
module CustomPunch2
|
9
|
-
def quack
|
10
|
-
'quack'
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
module CustomPunch3
|
15
|
-
def wobble
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
module ModWithOverride
|
20
|
-
def talk
|
21
|
-
'talk is cheap'
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
module ModWithNestedMod
|
26
|
-
def instance_method_1
|
27
|
-
end
|
28
|
-
|
29
|
-
module ClassMethods
|
30
|
-
def class_method_1
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
class Animal
|
36
|
-
end
|
37
|
-
|
38
|
-
class Dog < Animal
|
39
|
-
end
|
40
|
-
|
41
|
-
class Kaia < Dog
|
42
|
-
end
|
data/test/fixtures/wut.rb
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
require_relative '../../test_helper'
|
2
|
-
|
3
|
-
class DuckTest < MiniTest::Test
|
4
|
-
def setup
|
5
|
-
@class = Class.new
|
6
|
-
@object = @class.new
|
7
|
-
@mod = Module.new { %w[foo bar baz].each { |x| define_method(x, -> {}) } }
|
8
|
-
@subject = DuckPuncher::Duck.new(@class, @mod)
|
9
|
-
end
|
10
|
-
|
11
|
-
def test_punch
|
12
|
-
@subject.target = Kaia
|
13
|
-
refute_respond_to Kaia.new, :baz
|
14
|
-
@subject.call
|
15
|
-
assert_respond_to Kaia.new, :foo
|
16
|
-
assert_respond_to Kaia.new, :bar
|
17
|
-
assert_respond_to Kaia.new, :baz
|
18
|
-
end
|
19
|
-
|
20
|
-
def test_punch_with_instance
|
21
|
-
e = assert_raises ArgumentError do
|
22
|
-
@subject.call target: @object
|
23
|
-
end
|
24
|
-
assert_match /Invalid target #<#{@class}:.*>\. Please pass a module as :target/,
|
25
|
-
e.message
|
26
|
-
end
|
27
|
-
|
28
|
-
def test_punch_with_only
|
29
|
-
refute_respond_to @object, :foo
|
30
|
-
refute_respond_to @object, :bar
|
31
|
-
@subject.call(only: :foo)
|
32
|
-
refute_respond_to @object, :bar
|
33
|
-
assert_respond_to @object, :foo
|
34
|
-
@subject.call(only: :bar)
|
35
|
-
assert_respond_to @object, :bar
|
36
|
-
end
|
37
|
-
|
38
|
-
def test_punch_with_only_target
|
39
|
-
refute_respond_to @object, :bar
|
40
|
-
@subject.call target: @class, only: [:foo, :bar]
|
41
|
-
assert_respond_to @object, :bar
|
42
|
-
assert_respond_to @object, :foo
|
43
|
-
refute_respond_to @object, :baz
|
44
|
-
end
|
45
|
-
end
|