duck_puncher 4.5.1 → 5.0.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 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