duck_puncher 4.4.2 → 5.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![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
|
-
|
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
|