duck_puncher 2.15.0 → 2.16.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: 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