duck_puncher 4.5.1 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8a1e2a545fad7e6c8fb369e3e205012a8929c8f7
4
- data.tar.gz: 178c68f1a3c667f6aabcc216b690c63850b98f22
3
+ metadata.gz: 5eef188156f1414f02333fe56d300125533d0316
4
+ data.tar.gz: d15076d2567674bcc912862412c119466d029c5b
5
5
  SHA512:
6
- metadata.gz: 223cfd0a857d5e38e1c261581a38e965e4135eff3b4d9158149529d9aea09729a742471580db5846870f7c1aeb951b9f6823631ef3a87f44ef23e2c733174c06
7
- data.tar.gz: 92b02f4fba4fed7cdbd20820d5ecda2d9590a5ef5a4e277ab774cbe6f997ec4ecdc5441c953180e36e132890466ab7c0a18ecc610296f5b5b313e868bec49f3a
6
+ metadata.gz: 0e82ce6e8fa66fed2a53d06e19e873fbb6d85211bbadf6c4137c46aa07396152ed1a046a966afd3d362ce1c1474f126b55c01d0ffdfc7547b57dff85c56ea830
7
+ data.tar.gz: 76415a29eac87178fb5e3be11bf3c0644cfd048ce1993f913a4c933f6455aabc8e00dcd8df557b6ea24bb47307204a8f35ecbdc17eeaca6393e580b258664c5f
data/duck_puncher.gemspec CHANGED
@@ -13,9 +13,9 @@ Gem::Specification.new do |spec|
13
13
  spec.homepage = "https://github.com/ridiculous/duck_puncher"
14
14
  spec.license = "MIT"
15
15
 
16
- spec.files = `git ls-files`.split($/)
16
+ spec.files = `git ls-files`.split($/).keep_if { |f| f =~ /duck_puncher/ and f !~ %r{test/} }
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.test_files = spec.files.grep(%r{^(test)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.required_ruby_version = ">= 2.0.0"
data/lib/duck_puncher.rb CHANGED
@@ -62,6 +62,14 @@ module DuckPuncher
62
62
  @punched_ducks ||= Set.new
63
63
  end
64
64
 
65
+ # Register an extension with a target class
66
+ # When given a block, the block is used to create an anonymous module
67
+ # @param target [Class,Module,Object] constant or instance to extend
68
+ # @param mods [Array<Module>] modules to extend or mix into the target. The last argument can be a hash of options to customize the extension
69
+ # @option :only [Symbol, Array<Symbol>] list of methods to extend onto the target (the module must have these defined)
70
+ # @option :method [Symbol,String] the method used to apply the module, e.g. :extend (:include)
71
+ # @option :before [Proc] A hook that is called with the target class before #punch
72
+ # @option :after [Proc] A hook that is called with the target class after #punch
65
73
  def register(*)
66
74
  target, *_ = super
67
75
  decorators[target] = build_decorator_class(*Ducks[target])
@@ -5,6 +5,8 @@ module DuckPuncher
5
5
  # @param target [String,Class] Class or module to punch
6
6
  # @param mod [String,Module] The module that defines the extensions
7
7
  # @param [Hash] options to modify the duck #punch method behavior
8
+ # @option options :only [Symbol, Array<Symbol>] list of methods to extend onto the target (the module must have these defined)
9
+ # @option options :method [Symbol,String] the method used to apply the module, e.g. :extend (:include)
8
10
  # @option options :before [Proc] A hook that is called with the target class before +punch+
9
11
  # @option options :after [Proc] A hook that is called with the target class after +punch+
10
12
  def initialize(target, mod, options = {})
@@ -14,9 +16,9 @@ module DuckPuncher
14
16
  end
15
17
 
16
18
  # @param [Hash] opts to modify punch
17
- # @option options [Class] :target Specifies the class to be punched
18
- # @option options [Array,Symbol] :only Specifies the methods to extend onto the current object
19
- # @option options [Symbol,String] :method Specifies if the methods should be included or prepended (:include)
19
+ # @option options :target [Class] Specifies the class to be punched (overrides @target)
20
+ # @option options :only [Array,Symbol] Specifies the methods to extend onto the current object
21
+ # @option options :method [Symbol,String] Specifies if the methods should be included or prepended (:include)
20
22
  # @return [Class] The class that was just punched
21
23
  def call(opts = {})
22
24
  opts = options.merge(opts)
@@ -5,10 +5,17 @@ module DuckPuncher
5
5
  Marshal.load Marshal.dump self
6
6
  end unless defined? clone!
7
7
 
8
- def require!(file_or_gem, version = '')
8
+ def require!(file_or_gem, version = '', patience: 1)
9
9
  if DuckPuncher::GemInstaller.new.perform(file_or_gem, version)
10
- require file_or_gem.tr('-', '/')
10
+ if require file_or_gem.tr('-', '/')
11
+ true
12
+ elsif patience > 0
13
+ sleep 0.005
14
+ require!(file_or_gem, version, patience: patience - 1)
15
+ end
11
16
  end
17
+ rescue ::LoadError
18
+ require!(file_or_gem, version, patience: patience - 1) unless patience.zero?
12
19
  end
13
20
 
14
21
  # @description Returns a new decorated version of ourself with the punches mixed in (adds ancestors decorators)
@@ -32,15 +39,17 @@ module DuckPuncher
32
39
  self
33
40
  end
34
41
 
35
- def track
42
+ def track!(patience: 1)
36
43
  begin
37
- require 'object_tracker' || raise(LoadError)
38
- rescue LoadError
44
+ require 'object_tracker' || raise(::LoadError)
45
+ rescue ::LoadError
39
46
  DuckPuncher.(Object, only: :require!) unless respond_to? :require!
40
47
  require! 'object_tracker'
41
48
  end
42
- extend ::ObjectTracker
43
- track_all!
49
+ ::ObjectTracker.(self)
50
+ rescue ::Exception
51
+ sleep 0.005
52
+ track!(patience: patience - 1) unless patience.zero?
44
53
  end
45
54
  end
46
55
  end
@@ -1,25 +1,37 @@
1
1
  module DuckPuncher
2
2
  # @note When updating this file please update comment regarding this module in duck_puncher.rb
3
3
  module Registration
4
- def register(target, *mods)
4
+ # Register an extension with a target class
5
+ # When given a block, the block is used to create an anonymous module
6
+ # @param target [Class,Module,Object] constant or instance to extend
7
+ # @param mods [Array<Module>] modules to extend or mix into the target. The last argument can be a hash of options to customize the extension
8
+ # @option :only [Symbol, Array<Symbol>] list of methods to extend onto the target (the module must have these defined)
9
+ # @option :method [Symbol,String] the method used to apply the module, e.g. :extend (:include)
10
+ # @option :before [Proc] A hook that is called with the target class before #punch
11
+ # @option :after [Proc] A hook that is called with the target class after #punch
12
+ def register(target, *mods, &block)
5
13
  options = mods.last.is_a?(Hash) ? mods.pop : {}
14
+ mods << Module.new(&block) if block
6
15
  target = DuckPuncher.lookup_constant target
7
16
  Ducks.list[target] = Set.new [] unless Ducks.list.key?(target)
8
17
  mods = Array(mods).each do |mod|
9
- duck = UniqueDuck.new Duck.new target, mod, options
18
+ duck = UniqueDuck.new Duck.new(target, mod, options)
10
19
  Ducks.list[target] << duck
11
20
  end
12
21
  [target, *mods]
13
22
  end
14
23
 
15
- def register!(*args)
16
- register *args
24
+ # Register an extension and then immediately activate it
25
+ # See #register for accepted arguments
26
+ def register!(*args, &block)
27
+ register *args, &block
17
28
  call args.first
18
29
  end
19
30
 
20
- def deregister(*classes)
21
- classes.each &Ducks.list.method(:delete)
22
- classes.each &decorators.method(:delete)
31
+ # Remove extensions for a given class or list of classes
32
+ def deregister(*targets)
33
+ targets.each &Ducks.list.method(:delete)
34
+ targets.each &decorators.method(:delete)
23
35
  end
24
36
  end
25
37
  end
@@ -1,3 +1,3 @@
1
1
  module DuckPuncher
2
- VERSION = '4.5.1'.freeze
2
+ VERSION = '5.0.0'.freeze
3
3
  end
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.5.1
4
+ version: 5.0.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: 2017-01-20 00:00:00.000000000 Z
11
+ date: 2017-09-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: usable
@@ -83,21 +83,10 @@ dependencies:
83
83
  description: Administer precision punches
84
84
  email:
85
85
  - arebuckley@gmail.com
86
- executables:
87
- - console
86
+ executables: []
88
87
  extensions: []
89
88
  extra_rdoc_files: []
90
89
  files:
91
- - ".gitignore"
92
- - ".ruby-gemset"
93
- - ".ruby-version"
94
- - ".travis.yml"
95
- - CHANGELOG.md
96
- - Gemfile
97
- - LICENSE.txt
98
- - README.md
99
- - Rakefile
100
- - bin/console
101
90
  - duck_puncher.gemspec
102
91
  - lib/duck_puncher.rb
103
92
  - lib/duck_puncher/ancestral_hash.rb
@@ -119,18 +108,6 @@ files:
119
108
  - lib/duck_puncher/unique_duck.rb
120
109
  - lib/duck_puncher/utilities.rb
121
110
  - lib/duck_puncher/version.rb
122
- - test/fixtures/test_classes.rb
123
- - test/fixtures/wut.rb
124
- - test/lib/duck_puncher/duck_test.rb
125
- - test/lib/duck_puncher/enumerable_test.rb
126
- - test/lib/duck_puncher/hash_test.rb
127
- - test/lib/duck_puncher/method_test.rb
128
- - test/lib/duck_puncher/module_test.rb
129
- - test/lib/duck_puncher/numeric_test.rb
130
- - test/lib/duck_puncher/object_test.rb
131
- - test/lib/duck_puncher/string_test.rb
132
- - test/lib/duck_puncher_test.rb
133
- - test/test_helper.rb
134
111
  homepage: https://github.com/ridiculous/duck_puncher
135
112
  licenses:
136
113
  - MIT
@@ -155,16 +132,4 @@ rubygems_version: 2.5.1
155
132
  signing_key:
156
133
  specification_version: 4
157
134
  summary: Administer precision extensions (a.k.a "punches") to your favorite Ruby classes
158
- test_files:
159
- - test/fixtures/test_classes.rb
160
- - test/fixtures/wut.rb
161
- - test/lib/duck_puncher/duck_test.rb
162
- - test/lib/duck_puncher/enumerable_test.rb
163
- - test/lib/duck_puncher/hash_test.rb
164
- - test/lib/duck_puncher/method_test.rb
165
- - test/lib/duck_puncher/module_test.rb
166
- - test/lib/duck_puncher/numeric_test.rb
167
- - test/lib/duck_puncher/object_test.rb
168
- - test/lib/duck_puncher/string_test.rb
169
- - test/lib/duck_puncher_test.rb
170
- - test/test_helper.rb
135
+ test_files: []
data/.gitignore DELETED
@@ -1,19 +0,0 @@
1
- *.gem
2
- *.rbc
3
- .bundle
4
- .config
5
- .yardoc
6
- Gemfile.lock
7
- InstalledFiles
8
- _yardoc
9
- coverage
10
- doc/
11
- lib/bundler/man
12
- pkg
13
- rdoc
14
- spec/reports
15
- test/tmp
16
- test/version_tmp
17
- tmp
18
- .duck_puncher/
19
- .byebug_history
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
@@ -1,10 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.0.0
4
- - 2.1.0
5
- - 2.1.5
6
- - 2.2.1
7
- - 2.2.2
8
- - 2.2.3
9
- - 2.3.0
10
- before_install: gem install bundler -v 1.10.5
data/CHANGELOG.md DELETED
@@ -1,31 +0,0 @@
1
- 4.5.1 (01/19/2016)
2
- ==================
3
-
4
- * Fix `ActiveRecord#associations` for Rails 4
5
-
6
- 4.5.0 (01/19/2016)
7
- ==================
8
-
9
- * Fix `ActiveRecord#associations` to use LIMIT 1 for has_many associations
10
- * Remove redundant `ActiveRecord.latest`
11
-
12
- 4.4.2 (01/10/2016)
13
- ==================
14
-
15
- * Fix glaring bug in Duck class where the `:only` option is ignored (since 4.4.0)
16
-
17
- 4.4.1 (12/19/2016)
18
- ==================
19
-
20
- * Lazy load the rubygems/dependency_installer only when `require!` is called
21
-
22
- 4.4.0 (12/4/2016)
23
- =================
24
-
25
- * Target objects are no longer extended with the [Usable](https://github.com/ridiculous/usable) module
26
-
27
- 4.3.0 (10/10/2016)
28
- ==================
29
-
30
- * Fix issue with not being able to punch the same duck with different options
31
- * Add the `:target` option to `.call` to override the receiving class
data/Gemfile DELETED
@@ -1,9 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in duck_puncher.gemspec
4
- gemspec
5
-
6
- if RUBY_VERSION >= '2.0'
7
- gem 'byebug'
8
- # gem 'object_tracker'
9
- end
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,7 +0,0 @@
1
- class Wut
2
- def to_a
3
- ['w'] + ['u'] + ['t']
4
- end
5
-
6
- def to_f() Float::INFINITY end
7
- end
@@ -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
@@ -1,64 +0,0 @@
1
- require_relative '../../test_helper'
2
-
3
- DuckPuncher.punch! Object
4
-
5
- class EnumerableTest < MiniTest::Test
6
- attr_reader :subject
7
-
8
- def setup
9
- @subject = ('a'..'m').to_a
10
- end
11
-
12
- def test_m
13
- subject.punch!
14
- assert_equal subject.map(&:upcase), subject.m(:upcase)
15
- refute_equal subject.object_id, subject.m(:upcase).object_id
16
- assert_equal subject.map!(&:upcase), subject.m!(:upcase)
17
- assert_equal subject.object_id, subject.m!(:upcase).object_id
18
- end
19
-
20
- def test_m_with_range
21
- @subject = ('a'..'f')
22
- assert_equal %w[A B C D E F], @subject.punch.m(:upcase)
23
- assert_equal %w[A B C D E F], @subject.punch!.m(:upcase)
24
- end
25
-
26
- def test_m_with_enum
27
- @subject = ('A'..'F').to_enum
28
- assert_equal %w[B C D E F G], @subject.punch.m(:next)
29
- assert_equal %w[B C D E F G], @subject.punch!.m(:next)
30
- end
31
-
32
- def test_mm_with_set
33
- @subject = Set.new %w[a b c d e f]
34
- assert_equal %w[A B C D E F], @subject.punch.m(:upcase)
35
- assert_equal %w[A B C D E F], @subject.punch!.m(:upcase)
36
- assert_equal %w[B C D E F G], @subject.punch!.m!(:upcase).m(:next)
37
- end
38
-
39
- def test_mm_with_two_args
40
- subject.punch!
41
- assert_equal subject.map { |x| x.prepend('btn-') }, subject.mm(:prepend, 'btn-')
42
- refute_equal subject.object_id, subject.mm(:prepend, 'btn-')
43
- assert_equal subject.map! { |x| x.prepend('btn-') }, subject.mm!(:prepend, 'btn-')
44
- assert_equal subject.object_id, subject.mm!(:prepend, 'btn-').object_id
45
- end
46
-
47
- def test_mm_with_three_args
48
- @subject = @subject.punch
49
- assert_equal subject.map { |x| x.sub(/[aeiou]/, '*') }, subject.mm(:sub, /[aeiou]/, '*')
50
- end
51
-
52
- def test_except
53
- @subject = @subject.punch
54
- assert_equal subject.except('a'), %w[b c d e f g h i j k l m]
55
- assert_equal subject.except('a', 'b', 'c'), %w[d e f g h i j k l m]
56
- assert_equal subject.except, subject
57
- end
58
-
59
- def test_map_keys
60
- @subject = [{ id: 1, name: 'a' }, { id: 2, name: 'b' }, { name: 'c' }]
61
- assert_respond_to @subject.punch, :map_keys
62
- assert_equal %w[a b c], @subject.punch.map_keys(:name)
63
- end
64
- end
@@ -1,23 +0,0 @@
1
- require_relative '../../test_helper'
2
-
3
- DuckPuncher.punch! Hash
4
-
5
- class HashTest < MiniTest::Test
6
- def setup
7
- @subject = { a: 1, b: { c: 2 } }
8
- end
9
-
10
- def test_dig
11
- assert_equal @subject.dig(:a), 1
12
- assert_equal @subject.dig(:b, :a), nil
13
- assert_equal @subject.dig(:b, :c), 2
14
- assert_equal @subject.dig(:b), { c: 2 }
15
- end
16
-
17
- def test_compact
18
- assert_equal @subject.compact, { a: 1, b: { c: 2 } }
19
- @subject[:b] = nil
20
- assert_equal @subject, { a: 1, b: nil }
21
- assert_equal @subject.compact, { a: 1 }
22
- end
23
- end
@@ -1,32 +0,0 @@
1
- require_relative '../../test_helper'
2
- require_relative '../../fixtures/wut'
3
- DuckPuncher.punch! Method
4
-
5
- class MethodTest < MiniTest::Test
6
-
7
- # Called before every test method runs. Can be used
8
- # to set up fixture information.
9
- def setup
10
- @subject = Wut.new
11
- end
12
-
13
- def test_to_instruct
14
- assert_match /:to_a/, @subject.method(:to_a).to_instruct
15
- assert_match /newarray/, @subject.method(:to_a).to_instruct
16
- assert_match /opt_plus/, @subject.method(:to_a).to_instruct
17
- assert_equal nil, [].method(:to_s).to_instruct
18
- end
19
-
20
- def test_to_instruct_single_line
21
- assert_match /:to_f/, @subject.method(:to_f).to_instruct
22
- assert_match /getconstant\s*:INFINITY/, @subject.method(:to_f).to_instruct
23
- end
24
-
25
- def test_to_source
26
- assert_equal "def to_a\n ['w'] + ['u'] + ['t']\nend\n", @subject.method(:to_a).to_source
27
- end
28
-
29
- def test_to_source_with_no_source
30
- assert_equal '', @subject.method(:object_id).to_source
31
- end
32
- end
@@ -1,9 +0,0 @@
1
- require_relative '../../test_helper'
2
-
3
- DuckPuncher.punch! Module
4
-
5
- class ModuleTest < MiniTest::Test
6
- def test_local_methods
7
- assert_equal ModWithNestedMod.local_methods, [:instance_method_1, :class_method_1]
8
- end
9
- end
@@ -1,48 +0,0 @@
1
- require_relative '../../test_helper'
2
- DuckPuncher.(Numeric, String)
3
-
4
- class NumericTest < MiniTest::Test
5
-
6
- def test_to_currency
7
- assert_equal '0.00', 0.to_currency
8
- assert_equal '25.00', 25.to_currency
9
- assert_equal '25.20', 25.2.to_currency
10
- assert_equal '25.25', 25.245.to_currency
11
- assert_equal '$25.25', 25.245.to_currency('$')
12
- end
13
-
14
- def test_to_duration
15
- assert_equal '', 10.to_duration
16
- assert_equal '1 min', 100.to_duration
17
- assert_equal '16 min', 1_000.to_duration
18
- assert_equal '2 h 46 min', 10_000.to_duration
19
- assert_equal '27 h 46 min', 100_000.to_duration
20
- end
21
-
22
- def test_to_duration_with_seconds
23
- assert_equal '10 s', 10.to_duration(true)
24
- assert_equal '1 min 40 s', 100.to_duration(true)
25
- assert_equal '16 min 40 s', 1_000.to_duration(true)
26
- assert_equal '2 h 46 min', 10_000.to_duration(true)
27
- end
28
-
29
- def test_to_rad
30
- assert_equal 0.0, 0.to_rad
31
- assert_equal 0.17715091907742445, 10.15.to_rad
32
- assert_equal 0.36035409894869713, 20.646769.to_rad
33
- assert_equal -2.730392366234936, -156.439959.to_rad
34
- end
35
-
36
- def test_to_time_ago
37
- assert_equal 'less than a minute ago', 10.to_time_ago
38
- assert_equal '1 minute ago', 100.to_time_ago
39
- assert_equal '2 minutes ago', 130.to_time_ago
40
- assert_equal '16 minutes ago', 1_000.to_time_ago
41
- assert_equal '1 hour ago', 3_600.to_time_ago
42
- assert_equal '1 hour ago', 4_600.to_time_ago
43
- assert_equal '2 hours ago', 7_300.to_time_ago
44
- assert_equal '1 day ago', 86_400.to_time_ago
45
- assert_equal '1 day ago', 100_000.to_time_ago
46
- assert_equal '2 days ago', 180_000.to_time_ago
47
- end
48
- end
@@ -1,78 +0,0 @@
1
- require_relative '../../test_helper'
2
- DuckPuncher.punch! Object
3
-
4
- class ObjectTest < MiniTest::Test
5
- def setup
6
- @animal = Animal.new
7
- @dog = Dog.new
8
- @kaia = Kaia.new
9
- end
10
-
11
- def teardown
12
- DuckPuncher.deregister Animal, Dog, Kaia
13
- end
14
-
15
- def test_clone!
16
- cloned = @dog.clone!
17
- assert_equal cloned.class, @dog.class
18
- refute_equal cloned, @dog
19
- end
20
-
21
- def test_punch_with_a_core_duck
22
- assert [].punch.respond_to?(:m)
23
- end
24
-
25
- def test_punch_on_a_custom_duck
26
- DuckPuncher.register Animal, CustomPunch2
27
- assert @animal.punch.respond_to?(:quack)
28
- end
29
-
30
- def test_punch_with_multiple_custom_duck
31
- DuckPuncher.register Animal, CustomPunch2
32
- DuckPuncher.register Animal, CustomPunch3
33
- assert @animal.punch.respond_to?(:wobble)
34
- end
35
-
36
- def test_punch_call_stack
37
- Animal.send(:define_method, :foo) { quack }
38
- Animal.send(:define_method, :quack) { 'foo' }
39
- assert_equal 'foo', @animal.foo
40
- DuckPuncher.register Animal, CustomPunch2
41
- @animal.punch!
42
- assert_equal 'quack', @animal.foo
43
- end
44
-
45
- def test_punch_on_ancestor_only
46
- DuckPuncher.register Dog, CustomPunch2
47
- assert_respond_to @dog.punch, :quack
48
- end
49
-
50
- def test_punch_includes_all_ancestors
51
- DuckPuncher.register Animal, CustomPunch2
52
- DuckPuncher.register Dog, CustomPunch
53
- DuckPuncher.register Kaia, CustomPunch3
54
- @kaia = Kaia.new
55
- @kaia.punch!
56
- assert_respond_to @kaia, :wobble
57
- assert_respond_to @kaia, :talk
58
- assert_respond_to @kaia, :quack
59
- end
60
-
61
- def test_soft_punch_includes_all_ancestors
62
- DuckPuncher.register Animal, CustomPunch2
63
- DuckPuncher.register Dog, CustomPunch
64
- DuckPuncher.register Kaia, CustomPunch3
65
- @kaia = Kaia.new.punch
66
- assert_respond_to @kaia, :wobble
67
- assert_respond_to @kaia, :talk
68
- assert_respond_to @kaia, :quack
69
- end
70
-
71
- def test_soft_punch_with_parent_override
72
- DuckPuncher.register Animal, CustomPunch
73
- DuckPuncher.register Kaia, ModWithOverride
74
- @kaia = Kaia.new.punch
75
- assert_respond_to @kaia, :talk
76
- assert_equal @kaia.talk, 'talk is cheap'
77
- end
78
- end
@@ -1,39 +0,0 @@
1
- require_relative '../../test_helper'
2
-
3
- DuckPuncher.(String)
4
-
5
- class StringTest < MiniTest::Test
6
- def test_pluralize
7
- assert_equal 'hour', 'hour'.pluralize(1)
8
- assert_equal 'hours', 'hour'.pluralize(0)
9
- assert_equal 'hours', 'hour'.pluralize(2)
10
- end
11
-
12
- def test_underscore
13
- assert_equal 'mini_test', 'MiniTest'.underscore
14
- assert_equal 'mini_test_do_it_to_it', 'MiniTestDoItToIt'.underscore
15
- assert_equal 'mini_test/helper', 'MiniTest::Helper'.underscore
16
- assert_equal 'mini_test/helper/expectations', 'MiniTest::Helper::Expectations'.underscore
17
- assert_equal 'mini_test.rb', 'mini_test.rb'.underscore
18
- assert_equal 'duck_puncher/json_storage', 'DuckPuncher::JSONStorage'.underscore
19
- end
20
-
21
- def test_to_boolean
22
- assert 'true'.to_boolean
23
- assert '1'.to_boolean
24
- assert 'y'.to_boolean
25
- assert 'on'.to_boolean
26
- assert 'yes'.to_boolean
27
- refute 'false'.to_boolean
28
- refute '0'.to_boolean
29
- refute 'no'.to_boolean
30
- refute 'off'.to_boolean
31
- refute ''.to_boolean
32
- refute 'f'.to_boolean
33
- end
34
-
35
- def test_constantize
36
- assert_equal MiniTest, 'MiniTest'.constantize
37
- assert_equal MiniTest::Test, 'MiniTest::Test'.constantize
38
- end
39
- end
@@ -1,71 +0,0 @@
1
- require_relative '../test_helper'
2
-
3
- DuckPuncher.punch! Object, only: :punch
4
-
5
- class DuckPuncherTest < MiniTest::Test
6
- def setup
7
- @subject = Animal.new
8
- @kaia = Kaia.new
9
- end
10
-
11
- def teardown
12
- DuckPuncher.deregister Animal, Kaia, Dog
13
- end
14
-
15
- def test_punch!
16
- refute_respond_to @kaia, :talk
17
- refute_respond_to @kaia.punch, :talk
18
- DuckPuncher.register Kaia, CustomPunch
19
- DuckPuncher.punch! Kaia, only: :talk
20
- assert_respond_to @kaia, :talk
21
- assert_respond_to @kaia.punch, :talk
22
- end
23
-
24
- def test_punch_all!
25
- DuckPuncher.()
26
- expected_methods = DuckPuncher::Ducks.list.values.m(:to_a).flatten.m(:mod).m(:local_methods).flatten
27
- assert expected_methods.size > 1
28
- good_ducks = DuckPuncher::Ducks.list.select { |_, ducks|
29
- ducks.all? { |duck| (duck.mod.local_methods - duck.target.instance_methods(:false)).size.zero? }
30
- }
31
- assert good_ducks.size > 5
32
- end
33
-
34
- def test_call
35
- DuckPuncher.()
36
- expected_methods = DuckPuncher::Ducks.list.values.m(:to_a).flatten.m(:mod).m(:local_methods).flatten
37
- assert expected_methods.size > 1
38
- # Find all ducks that have copied all their methods to the target class (e.g. String)
39
- good_ducks = DuckPuncher::Ducks.list.select { |_, ducks|
40
- ducks.all? { |duck| (duck.mod.local_methods - duck.target.instance_methods(:false)).size.zero? }
41
- }
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
50
- end
51
-
52
- def test_register_with_multiple_mods
53
- refute_respond_to @subject, :talk
54
- refute_respond_to @subject, :wobble
55
- refute_respond_to @subject.punch, :talk
56
- refute_respond_to @subject.punch, :wobble
57
- DuckPuncher.register Animal, CustomPunch, CustomPunch3
58
- assert_respond_to @subject.punch, :talk
59
- assert_respond_to @subject.punch, :wobble
60
- end
61
-
62
- def test_deregister
63
- refute_respond_to @subject, :talk
64
- refute_respond_to @subject.punch, :talk
65
- DuckPuncher.register Animal, CustomPunch
66
- assert_respond_to @subject.punch, :talk
67
- refute_respond_to @subject, :talk
68
- DuckPuncher.deregister Animal
69
- refute_respond_to @subject.punch, :talk
70
- end
71
- end
data/test/test_helper.rb DELETED
@@ -1,9 +0,0 @@
1
- require 'minitest'
2
- require 'minitest/autorun'
3
- require 'minitest/reporters'
4
- require 'duck_puncher'
5
- require_relative 'fixtures/test_classes'
6
-
7
- Minitest::Reporters.use!
8
-
9
- DuckPuncher.logger.level = Logger::DEBUG