duck_puncher 2.15.0 → 2.16.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: 0396fdad209bbef11b8fdcbd981c93d58a1d7ac9
4
- data.tar.gz: 0dea0bc87f6f20719c7f571982b2808825ceee90
3
+ metadata.gz: a8a2fed5af6189e26acdeb046ec66ff7dec8368d
4
+ data.tar.gz: 6f487f85bd05ea581fcc4463e11b2b1c611a7f1e
5
5
  SHA512:
6
- metadata.gz: 0f0ea32d017582b6ce51691f510f7eb3614c1be71a4af861a346c1cb45f09656ed65de6bf174c5541ea26dbdf0053602a4e1ff16c25b2dc7d59669e5000e6cfd
7
- data.tar.gz: d34ef5fd1266ae89699bf053256e316a5c1be58b196feaaa8d61793b0896a7bc0e30792cf618e48bcd8503005067ec11b2691b81c40af04ea5ab11ad0cecfcfe
6
+ metadata.gz: 16fac17fdb70185f7deed4f5657cfa32c29203b7427a6cda940765a454eb8300dedcd4ebf056b785c54669dc901eef05ced5e5aa3c2833e4217ff99d1fdd3ba7
7
+ data.tar.gz: e40705f7bf0bb01eebdf5767e590254d5d83c8190df619cadd36fd31ff310d0d4a2a2ef2875626249ee32e3cd103e4ee9983b062776c5701089ce4924c802289
data/README.md CHANGED
@@ -100,7 +100,9 @@ module Retryable
100
100
  retry if (retries -= 1) > 0
101
101
  end
102
102
  end
103
+ ```
103
104
 
105
+ ```ruby
104
106
  # Register the extensions
105
107
  DuckPuncher.register [:Billable, :Retryable]
106
108
 
@@ -116,6 +118,13 @@ user = User.new('Ryan').punch(:Billable).punch(:Retryable)
116
118
  user.call_with_retry(19.99)
117
119
  ```
118
120
 
121
+ Ducks can be registered under any name you want, as long as the `:mod` option specifies a module:
122
+
123
+ ```ruby
124
+ DuckPuncher.register :bills, mod: 'Admin::Billable'
125
+ User.new.punch(:bills)
126
+ ```
127
+
119
128
  When punching at a class level, the `:class` option is required:
120
129
 
121
130
  ```ruby
@@ -4,45 +4,66 @@ module DuckPuncher
4
4
 
5
5
  # @param [Symbol] name of the duck
6
6
  # @param [Hash] options to modify the duck #punch method behavior
7
- # @option options [String] :class (name) to punch
7
+ # @option options [String,Module] :mod The module that defines the extensions (@name is used by default)
8
+ # @option options [String,Class] :class (name) to punch
8
9
  # @option options [Proc] :if Stops +punch+ if it returns false
9
- # @option options [Proc] :before A hook that is called with the target @klass before +punch+
10
- # @option options [Proc] :after A hook that is called with the target @klass after +punch+
10
+ # @option options [Proc] :before A hook that is called with the target class before +punch+
11
+ # @option options [Proc] :after A hook that is called with the target class after +punch+
11
12
  def initialize(name, options = {})
12
13
  @name = name
13
14
  @options = options
14
15
  end
15
16
 
16
17
  # @param [Hash] opts to modify punch
17
- # @option options [Class] :target Overrides the @klass to be punched
18
+ # @option options [Class] :target Specifies the class to be punched
18
19
  # @option options [Array,Symbol] :only Specifies the methods to extend onto the current object
20
+ # @option options [Symbol,String] :method Specifies if the methods should be included or prepended (:include)
21
+ # @return [Class] The class that was just punched
19
22
  def punch(opts = {})
20
23
  if options[:if] && !options[:if].call
21
24
  DuckPuncher.log.info %Q(Skipping the punch for #{name}!)
22
25
  return nil
23
26
  end
24
- target = opts.delete(:target) || klass
25
- options[:before].call(target) if options[:before]
26
- target.extend Usable
27
- target.usable DuckPuncher::Ducks.const_get(name), opts
27
+ if options[:mod]
28
+ mod = lookup_constant(options[:mod])
29
+ else
30
+ mod = DuckPuncher::Ducks.const_get(name)
31
+ end
32
+ target = opts.delete(:target) || lookup_class
33
+ Array(target).each do |klass|
34
+ options[:before].call(klass) if options[:before]
35
+ klass.extend Usable
36
+ klass.usable mod, only: opts[:only], method: opts[:method]
37
+ options[:after].call(klass) if options[:after]
38
+ end
28
39
  target
29
40
  end
30
41
 
31
- def klass
32
- @klass ||= (options[:class] || name).to_s.split('::').inject(Kernel) { |k, part| k.const_get part }
42
+ # @return [Class] The class that is given to initialize as the option :class or the name of the current duck (module extension)
43
+ def lookup_class
44
+ lookup_constant(options[:class] || name)
45
+ end
46
+
47
+ def lookup_constant(const)
48
+ return const if Module === const
49
+ if const.to_s.respond_to?(:constantize)
50
+ const.to_s.constantize
51
+ else
52
+ const.to_s.split('::').inject(Object) { |k, part| k.const_get(part) }
53
+ end
33
54
  end
34
55
 
35
56
  # @param [Class] obj The object being punched
36
57
  def delegated(obj = nil)
37
- obj_class = obj ? obj.class : klass
58
+ obj_class = obj ? obj.class : lookup_class
38
59
  klass = DelegateClass(obj_class)
39
- punch target: klass
60
+ punch target: klass, method: :prepend
40
61
  klass.usable DuckPuncher::Ducks::Object, only: :punch, method: :prepend
41
62
  klass
42
63
  end
43
64
 
44
65
  def classify
45
- Class.new(klass).tap { |k| punch target: k }
66
+ Class.new(lookup_class).tap { |k| punch target: k }
46
67
  end
47
68
  end
48
69
  end
@@ -12,7 +12,7 @@ module DuckPuncher
12
12
  end
13
13
 
14
14
  def punch(duck_name = self.class.name)
15
- DuckPuncher.delegate_class(duck_name.to_sym, self).new(self)
15
+ DuckPuncher.delegate_class(duck_name, self).new(self)
16
16
  end
17
17
 
18
18
  def track
@@ -14,6 +14,10 @@ module DuckPuncher
14
14
  def to_boolean
15
15
  !!BOOLEAN_MAP[downcase]
16
16
  end unless method_defined?(:to_boolean)
17
+
18
+ def constantize
19
+ split('::').inject(Object) { |o, name| o.const_get name }
20
+ end unless method_defined?(:constantize)
17
21
  end
18
22
  end
19
23
  end
@@ -15,7 +15,7 @@ module DuckPuncher
15
15
 
16
16
  def [](name)
17
17
  list.find { |duck| duck.name == name.to_sym } ||
18
- DuckPuncher.log.warn(%Q(Couldn't find "#{name}" in my list of Ducks! I know about: #{list.map(&:name).map(&:to_s)}))
18
+ fail(ArgumentError, %Q(Couldn't find "#{name}" in my list of Ducks! I know about: #{list.map(&:name).map(&:to_s)}))
19
19
  end
20
20
 
21
21
  def load_path_for(duck)
@@ -1,3 +1,3 @@
1
1
  module DuckPuncher
2
- VERSION = '2.15.0'.freeze
2
+ VERSION = '2.16.0'.freeze
3
3
  end
data/lib/duck_puncher.rb CHANGED
@@ -25,7 +25,13 @@ module DuckPuncher
25
25
  # @param [Symbol] duck_name
26
26
  # @param [Class] obj The object being punched
27
27
  def delegate_class(duck_name, obj = nil)
28
- delegations[duck_name] ||= const_set "#{duck_name}DuckDelegated", Ducks[duck_name].dup.delegated(obj)
28
+ delegations["#{obj.class}#{duck_name}"] ||= begin
29
+ duck_const = duck_name.to_s
30
+ if duck_const[/^[A-Z]/].nil?
31
+ duck_const = duck_const.split('_').map(&:capitalize).join
32
+ end
33
+ const_set "#{duck_const}DuckDelegated", Ducks[duck_name.to_sym].dup.delegated(obj)
34
+ end
29
35
  end
30
36
 
31
37
  def duck_class(name)
@@ -1,12 +1,16 @@
1
1
  require_relative '../../test_helper'
2
- DuckPuncher.punch! :Object
2
+
3
+ DuckPuncher.punch :Hash
3
4
 
4
5
  class HashTest < MiniTest::Test
6
+ def setup
7
+ @subject = DuckPuncher::HashDuck.new.merge({ a: 1, b: { c: 2 } })
8
+ end
9
+
5
10
  def test_dig
6
- my_hash = { a: 1, b: { c: 2 } }.punch
7
- assert_equal my_hash.dig(:a), 1
8
- assert_equal my_hash.dig(:b, :a), nil
9
- assert_equal my_hash.dig(:b, :c), 2
10
- assert_equal my_hash.dig(:b), { c: 2 }
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 }
11
15
  end
12
16
  end
@@ -2,10 +2,46 @@ require_relative '../../test_helper'
2
2
  DuckPuncher.punch! :Object
3
3
 
4
4
  class ObjectTest < MiniTest::Test
5
+
6
+ def setup
7
+ Object.const_set :User, Class.new
8
+ @subject = Object.new
9
+ @user = User.new
10
+ end
11
+
12
+ def teardown
13
+ DuckPuncher::Ducks.list.delete_if { |duck| [:admin, :super_admin, :User].include?(duck.name) }
14
+ Object.send :remove_const, :User
15
+ end
16
+
5
17
  def test_clone!
6
- obj = Object.new
7
- cloned = obj.clone!
8
- assert_equal cloned.class, obj.class
9
- refute_equal cloned, obj
18
+ cloned = @subject.clone!
19
+ assert_equal cloned.class, @subject.class
20
+ refute_equal cloned, @subject
21
+ end
22
+
23
+ def test_punch_on_a_core_duck
24
+ refute [].respond_to?(:m)
25
+ assert [].respond_to?(:punch)
26
+ assert [].punch.respond_to?(:m)
27
+ end
28
+
29
+ def test_punch_with_a_core_duck
30
+ assert [].punch(:Array).respond_to?(:m)
31
+ end
32
+
33
+ def test_punch_on_a_custom_duck
34
+ DuckPuncher.register :User, mod: 'CustomPunch2'
35
+ assert @user.punch.respond_to?(:quack)
36
+ end
37
+
38
+ def test_punch_with_a_custom_duck
39
+ refute @user.respond_to?(:quack)
40
+ DuckPuncher.register :admin, mod: 'CustomPunch2'
41
+ assert @user.punch(:admin).respond_to?(:quack)
42
+
43
+ refute @user.respond_to?(:wobble)
44
+ DuckPuncher.register :super_admin, mod: 'CustomPunch3'
45
+ assert @user.punch(:super_admin).respond_to?(:wobble)
10
46
  end
11
47
  end
@@ -4,16 +4,16 @@ DuckPuncher.punch! :String
4
4
 
5
5
  class StringTest < MiniTest::Test
6
6
  def test_pluralize
7
- assert_equal 'hour'.pluralize(1), 'hour'
8
- assert_equal 'hour'.pluralize(0), 'hours'
9
- assert_equal 'hour'.pluralize(2), 'hours'
7
+ assert_equal 'hour', 'hour'.pluralize(1)
8
+ assert_equal 'hours', 'hour'.pluralize(0)
9
+ assert_equal 'hours', 'hour'.pluralize(2)
10
10
  end
11
11
 
12
12
  def test_underscore
13
- assert_equal 'MiniTest'.underscore, 'mini_test'
14
- assert_equal 'MiniTestDoItToIt'.underscore, 'mini_test_do_it_to_it'
15
- assert_equal 'MiniTest::Helper'.underscore, 'mini_test/helper'
16
- assert_equal 'MiniTest::Helper::Expectations'.underscore, 'mini_test/helper/expectations'
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
17
  assert_equal 'mini_test.rb', 'mini_test.rb'.underscore
18
18
  assert_equal 'duck_puncher/json_storage', 'DuckPuncher::JSONStorage'.underscore
19
19
  end
@@ -31,4 +31,9 @@ class StringTest < MiniTest::Test
31
31
  refute ''.to_boolean
32
32
  refute 'asd'.to_boolean
33
33
  end
34
+
35
+ def test_constantize
36
+ assert_equal MiniTest, 'MiniTest'.constantize
37
+ assert_equal MiniTest::Test, 'MiniTest::Test'.constantize
38
+ end
34
39
  end
data/test/test_helper.rb CHANGED
@@ -5,6 +5,8 @@ require 'duck_puncher'
5
5
 
6
6
  Minitest::Reporters.use!
7
7
 
8
+ DuckPuncher.log.level = Logger::INFO
9
+
8
10
  module CustomPunch
9
11
  def tap_tap
10
12
  p self
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: 2.15.0
4
+ version: 2.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Buckley
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-27 00:00:00.000000000 Z
11
+ date: 2016-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: usable