duck_puncher 2.4.0 → 2.5.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: d30be8964065fb57d3de53e4e23b2deaf8ceb277
4
- data.tar.gz: f2bc471c1a31a1f438680f21440b86d489092cd1
3
+ metadata.gz: 2afbd4dd98468db97339d8094564c7830a8f6aa8
4
+ data.tar.gz: 2cabadf15f55e7b38b5028d0c702fb71ac809a4b
5
5
  SHA512:
6
- metadata.gz: e117be77a8e6a2a80f0ba415315039fa727ba1495e52bfa9b83cd7b5d631b92ebce331eaac51f6b7fb2d5c76a00e2f88f19892f4f84039686fab277976a40588
7
- data.tar.gz: 64a2e78858356beacb9f30ea6cd6955046c8f90fc596fd0d15c64cbe2e121423e85b9ffe384e08826655f45eddc74bae824a6c401877e65ebf62c44908adc851
6
+ metadata.gz: f3016a12e3e5ba11571b05ad62a58163a25db0fc4866d0c82c39cfeeaeada1c7ff44999bfabcb81021b3f4c810973e2b8b0a79e9c522426597b17e5f9f6d4d9e
7
+ data.tar.gz: c35a7d1dd77ac403f42884935f56963354e4aa5e75dcc36442b7a125ae000f3417a991da536aa8bcdb6b30ddb95f6619ced25004a6c44ce4efddfce85b6f0745
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # DuckPuncher
2
2
 
3
- These are the ducks I can punch:
3
+ Ruby objects walk and talk like ducks, therefore they _are_ ducks. But ducks don't always behave, and some times they need
4
+ tough love. You know, lil love punches! :punch: :heart:
5
+
6
+ These are the ducks I love the most:
4
7
 
5
8
  Array#m => `[].m(:to_s)` => `[].map(&:to_s)`
6
9
  Array#mm => `[].mm(:sub, /[aeiou]/, '*')` => `[].map { |x| x.sub(/[aeiou]/, '*') }`
@@ -48,8 +51,25 @@ Try it out if your feeling frisky! However, I noticed it doesn't work well with
48
51
  Ducks need to be _loaded_ before they can be punched! Maybe do this in an initializer?
49
52
 
50
53
  ```ruby
51
- DuckPuncher.punch! :Hash, :Object #=> only punches the specified ducks
52
- DuckPuncher.punch_all! #=> punches all the ducks
54
+ DuckPuncher.punch_all! #=> punches all the ducks forever
55
+ DuckPuncher.punch! :Hash, :Object #=> only punches the Hash and Object ducks
56
+ DuckPuncher.punch :String #=> returns an anonymous punched duck that inherits from String
57
+ DuckString = DuckPuncher.punch :String #=> give the anonymous duck a name, so that you can use it!
58
+ DuckString.new.respond_to? :underscore #=> true
59
+ ```
60
+
61
+ DuckPuncher defines a global `punch` method. This method creates and caches a delegation class pre-punched with only the
62
+ ducks you want!
63
+
64
+ Here's an example of how to use it:
65
+
66
+ ```ruby
67
+ >> a = punch :Array, [punch(:String, 'foo'), punch(:String, 'bar')]
68
+ => ["foo", "bar"]
69
+ >> a.m :upcase
70
+ => ["FOO", "BAR"]
71
+ >> a.mm :pluralize, 2
72
+ => ["foos", "bars"]
53
73
  ```
54
74
 
55
75
  ## Contributing
data/Rakefile CHANGED
@@ -2,8 +2,12 @@ require 'bundler/gem_tasks'
2
2
  require 'rake'
3
3
  require 'rake/testtask'
4
4
 
5
- task default: :test
5
+ Rake::TestTask.new(:soft_punch_test) do |t|
6
+ t.pattern = 'test/soft_punch/*_test.rb'
7
+ end
6
8
 
7
- Rake::TestTask.new do |t|
8
- t.pattern = 'test/**/*_test.rb'
9
+ Rake::TestTask.new(:hard_punch_test) do |t|
10
+ t.pattern = 'test/duck_puncher/*_test.rb'
9
11
  end
12
+
13
+ task default: [:soft_punch_test, :hard_punch_test]
@@ -6,6 +6,6 @@ require 'duck_puncher'
6
6
  require 'irb'
7
7
  require_relative '../test/fixtures/wut'
8
8
 
9
- DuckPuncher.punch_all!
9
+ DuckPuncher.punch_all! if ENV['PUNCH'] != 'no'
10
10
 
11
11
  IRB.start
@@ -1,5 +1,6 @@
1
1
  require 'pathname'
2
2
  require 'fileutils'
3
+ require 'delegate'
3
4
  require 'logger'
4
5
  require 'duck_puncher/version'
5
6
 
@@ -9,35 +10,63 @@ module DuckPuncher
9
10
  autoload :Duck, 'duck_puncher/duck'
10
11
  autoload :Ducks, 'duck_puncher/ducks'
11
12
 
12
- def self.punch!(*names)
13
- names.each do |name|
14
- if duck = Ducks[name]
13
+ class << self
14
+ attr_accessor :log
15
+
16
+ def delegate_class(name)
17
+ @delegations ||= {}
18
+ @delegations[name] ||= Ducks[name].dup.delegated
19
+ end
20
+
21
+ # @description Extends functionality to a copy of the specified class
22
+ def punch(*names)
23
+ singular = names.size == 1
24
+ punched_ducks = names.map do |name|
25
+ duck = Ducks[name]
26
+ duck_class = Class.new(duck.klass)
27
+ if duck.punch duck_class
28
+ duck_class
29
+ else
30
+ log.error %Q(Failed to punch #{name}!)
31
+ end
32
+ end
33
+ punched_ducks.compact!
34
+ punched_ducks = punched_ducks.first if singular
35
+ punched_ducks
36
+ end
37
+
38
+ def punch!(*names)
39
+ names.each do |name|
40
+ duck = Ducks[name]
15
41
  if duck.punched?
16
42
  log.info %Q(Already punched #{name})
17
43
  else
18
- log.warn %Q(Punching the #{name} ducky)
44
+ log.warn %Q(Punching #{name} ducky)
19
45
  unless duck.punch
20
46
  log.error %Q(Failed to punch #{name}!)
21
47
  end
22
48
  end
23
- else
24
- log.info %Q(Couldn't find "#{name}" in my list of Ducks! I know about: #{Ducks.list.map(&:name).map(&:to_s)})
25
49
  end
50
+ nil
26
51
  end
27
- nil
28
- end
29
52
 
30
- def self.punch_all!
31
- log.warn 'Punching all ducks! Watch out!'
32
- Ducks.list.each &:punch
33
- end
34
-
35
- class << self
36
- attr_accessor :log
53
+ def punch_all!
54
+ log.warn 'Punching all ducks! Watch out!'
55
+ Ducks.list.each &:punch
56
+ end
37
57
  end
38
58
 
59
+ # @description Default logger
60
+ # @example Silence logging
61
+ #
62
+ # `DuckPuncher.log.level = Logger::ERROR`
63
+ #
39
64
  self.log = Logger.new(STDOUT).tap do |config|
40
65
  config.level = Logger::INFO
41
66
  config.formatter = proc { |*args| "#{args.first}: #{args.last.to_s}\n" }
42
67
  end
43
68
  end
69
+
70
+ def punch(name, val)
71
+ DuckPuncher.delegate_class(name).new val
72
+ end
@@ -8,23 +8,34 @@ module DuckPuncher
8
8
  @punched = false
9
9
  end
10
10
 
11
+ # @note Assumes the String duck is loaded first
11
12
  def load_path
12
- "duck_puncher/ducks/#{name.to_s.downcase}"
13
+ path_name = if name == :String
14
+ name.to_s.downcase
15
+ else
16
+ Object.send(:punch, :String, name.to_s).underscore
17
+ end
18
+ "duck_puncher/ducks/#{path_name}"
13
19
  end
14
20
 
15
- def punch
21
+ def punch(target = nil)
16
22
  return false if options[:if] && !options[:if].call
17
23
  options[:before].call if options[:before]
18
- const.send :include, DuckPuncher::Ducks.const_get(name)
24
+ (target || klass).send :include, DuckPuncher::Ducks.const_get(name)
25
+ options[:after].call if options[:after]
19
26
  @punched = true
20
27
  end
21
28
 
22
- def const
23
- @const ||= (options[:class] || name).to_s.split('::').inject(Kernel) { |k, part| k.const_get part }
29
+ def klass
30
+ @klass ||= (options[:class] || name).to_s.split('::').inject(Kernel) { |k, part| k.const_get part }
24
31
  end
25
32
 
26
33
  def punched?
27
34
  @punched
28
35
  end
36
+
37
+ def delegated
38
+ DelegateClass(klass).tap { |k| punch k }
39
+ end
29
40
  end
30
41
  end
@@ -3,10 +3,10 @@ module DuckPuncher
3
3
  class << self
4
4
  def list
5
5
  @list ||= [
6
+ Duck.new(:String),
6
7
  Duck.new(:Array),
7
8
  Duck.new(:Numeric),
8
9
  Duck.new(:Hash),
9
- Duck.new(:String),
10
10
  Duck.new(:Object),
11
11
  Duck.new(:Method, before: -> { DuckPuncher::GemInstaller.initialize! }),
12
12
  Duck.new(:ActiveRecord, class: 'ActiveRecord::Base', if: -> { defined? ::ActiveRecord })
@@ -14,7 +14,8 @@ module DuckPuncher
14
14
  end
15
15
 
16
16
  def [](name)
17
- list.find { |duck| duck.name == name.to_sym }
17
+ list.find { |duck| duck.name == name.to_sym } ||
18
+ DuckPuncher.log.info(%Q(Couldn't find "#{name}" in my list of Ducks! I know about: #{list.map(&:name).map(&:to_s)}))
18
19
  end
19
20
  end
20
21
 
@@ -1,48 +1,50 @@
1
1
  module DuckPuncher
2
- module ActiveRecord
3
- def self.included(base)
4
- base.extend(ClassMethods)
5
- end
6
-
7
- def associations?
8
- associations.present?
9
- end
10
-
11
- def associations
12
- reflections.select { |key, _| send(key).present? rescue nil }.keys
13
- end
14
-
15
- module ClassMethods
16
- def except_for(*ids)
17
- scoped.where("#{quoted_table_name}.id NOT IN (?)", ids)
18
- end
19
-
20
- def since(time)
21
- scoped.where("#{quoted_table_name}.created_at > ?", time)
22
- end
23
-
24
- alias created_since since
25
-
26
- def before(time)
27
- scoped.where("#{quoted_table_name}.created_at < ?", time)
28
- end
29
-
30
- def updated_since(time)
31
- scoped.where("#{quoted_table_name}.updated_at > ?", time)
32
- end
33
-
34
- def between(start_at, end_at)
35
- scoped.where("#{quoted_table_name}.created_at BETWEEN ? AND ", start_at, end_at)
36
- end
37
-
38
- def latest
39
- scoped.order("#{quoted_table_name}.id ASC").last
40
- end
41
-
42
- # shim for backwards compatibility with Rails 3
43
- def scoped
44
- where(nil)
45
- end if Rails::VERSION::MAJOR != 3
46
- end
47
- end
2
+ module Ducks
3
+ module ActiveRecord
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ def associations?
9
+ associations.present?
10
+ end
11
+
12
+ def associations
13
+ reflections.select { |key, _| send(key).present? rescue nil }.keys
14
+ end
15
+
16
+ module ClassMethods
17
+ def except_for(*ids)
18
+ scoped.where("#{quoted_table_name}.id NOT IN (?)", ids)
19
+ end
20
+
21
+ def since(time)
22
+ scoped.where("#{quoted_table_name}.created_at > ?", time)
23
+ end
24
+
25
+ alias created_since since
26
+
27
+ def before(time)
28
+ scoped.where("#{quoted_table_name}.created_at < ?", time)
29
+ end
30
+
31
+ def updated_since(time)
32
+ scoped.where("#{quoted_table_name}.updated_at > ?", time)
33
+ end
34
+
35
+ def between(start_at, end_at)
36
+ scoped.where("#{quoted_table_name}.created_at BETWEEN ? AND ", start_at, end_at)
37
+ end
38
+
39
+ def latest
40
+ scoped.order("#{quoted_table_name}.id ASC").last
41
+ end
42
+
43
+ # shim for backwards compatibility with Rails 3
44
+ def scoped
45
+ where(nil)
46
+ end if Rails::VERSION::MAJOR != 3
47
+ end
48
+ end
49
+ end
48
50
  end
@@ -16,3 +16,4 @@ module DuckPuncher
16
16
  end
17
17
  end
18
18
  end
19
+
@@ -1,3 +1,3 @@
1
1
  module DuckPuncher
2
- VERSION = '2.4.0'.freeze
2
+ VERSION = '2.5.0'.freeze
3
3
  end
@@ -1,4 +1,5 @@
1
1
  require_relative '../test_helper'
2
+ DuckPuncher.punch! :Array
2
3
 
3
4
  class ArrayTest < MiniTest::Test
4
5
  attr_reader :subject
@@ -1,4 +1,5 @@
1
1
  require_relative '../test_helper'
2
+ DuckPuncher.punch! :Hash
2
3
 
3
4
  class HashTest < MiniTest::Test
4
5
  def test_seek
@@ -1,5 +1,6 @@
1
1
  require_relative '../test_helper'
2
2
  require_relative '../fixtures/wut'
3
+ DuckPuncher.punch! :Method
3
4
 
4
5
  class MethodTest < MiniTest::Test
5
6
 
@@ -1,4 +1,5 @@
1
1
  require_relative '../test_helper'
2
+ DuckPuncher.punch! :Numeric
2
3
 
3
4
  class NumericTest < MiniTest::Test
4
5
 
@@ -1,4 +1,5 @@
1
1
  require_relative '../test_helper'
2
+ DuckPuncher.punch! :Object
2
3
 
3
4
  class ObjectTest < MiniTest::Test
4
5
  def test_clone!
@@ -1,5 +1,7 @@
1
1
  require_relative '../test_helper'
2
2
 
3
+ DuckPuncher.punch! :String
4
+
3
5
  class StringTest < MiniTest::Test
4
6
  def test_pluralize
5
7
  assert_equal 'hour'.pluralize(1), 'hour'
@@ -0,0 +1,21 @@
1
+ require_relative '../test_helper'
2
+
3
+ class DuckPuncherTest < MiniTest::Test
4
+ DuckString = DuckPuncher.punch :String
5
+ DuckNumber, DuckArray = DuckPuncher.punch :Numeric, :Array
6
+
7
+ def test_punch
8
+ refute_respond_to '', :underscore
9
+ assert_respond_to DuckString.new, :underscore
10
+ refute_respond_to '', :underscore
11
+ end
12
+
13
+ def test_punch_multiple
14
+ refute_respond_to 25, :to_currency
15
+ refute_respond_to [], :m
16
+ assert_respond_to DuckNumber.new, :to_currency
17
+ assert_respond_to DuckArray.new, :m
18
+ refute_respond_to 25, :to_currency
19
+ refute_respond_to [], :m
20
+ end
21
+ end
@@ -4,4 +4,3 @@ require 'minitest/reporters'
4
4
  require 'duck_puncher'
5
5
 
6
6
  Minitest::Reporters.use!
7
- DuckPuncher.punch_all!
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.4.0
4
+ version: 2.5.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-01-30 00:00:00.000000000 Z
11
+ date: 2016-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -116,6 +116,7 @@ files:
116
116
  - test/duck_puncher/object_test.rb
117
117
  - test/duck_puncher/string_test.rb
118
118
  - test/fixtures/wut.rb
119
+ - test/soft_punch/duck_puncher_test.rb
119
120
  - test/test_helper.rb
120
121
  homepage: https://github.com/ridiculous/duck_puncher
121
122
  licenses:
@@ -149,4 +150,5 @@ test_files:
149
150
  - test/duck_puncher/object_test.rb
150
151
  - test/duck_puncher/string_test.rb
151
152
  - test/fixtures/wut.rb
153
+ - test/soft_punch/duck_puncher_test.rb
152
154
  - test/test_helper.rb