duck_puncher 2.4.0 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +23 -3
- data/Rakefile +7 -3
- data/bin/console +1 -1
- data/lib/duck_puncher.rb +44 -15
- data/lib/duck_puncher/duck.rb +16 -5
- data/lib/duck_puncher/ducks.rb +3 -2
- data/lib/duck_puncher/ducks/active_record.rb +48 -46
- data/lib/duck_puncher/ducks/array.rb +1 -0
- data/lib/duck_puncher/version.rb +1 -1
- data/test/duck_puncher/array_test.rb +1 -0
- data/test/duck_puncher/hash_test.rb +1 -0
- data/test/duck_puncher/method_test.rb +1 -0
- data/test/duck_puncher/numeric_test.rb +1 -0
- data/test/duck_puncher/object_test.rb +1 -0
- data/test/duck_puncher/string_test.rb +2 -0
- data/test/soft_punch/duck_puncher_test.rb +21 -0
- data/test/test_helper.rb +0 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2afbd4dd98468db97339d8094564c7830a8f6aa8
|
4
|
+
data.tar.gz: 2cabadf15f55e7b38b5028d0c702fb71ac809a4b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3016a12e3e5ba11571b05ad62a58163a25db0fc4866d0c82c39cfeeaeada1c7ff44999bfabcb81021b3f4c810973e2b8b0a79e9c522426597b17e5f9f6d4d9e
|
7
|
+
data.tar.gz: c35a7d1dd77ac403f42884935f56963354e4aa5e75dcc36442b7a125ae000f3417a991da536aa8bcdb6b30ddb95f6619ced25004a6c44ce4efddfce85b6f0745
|
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# DuckPuncher
|
2
2
|
|
3
|
-
|
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.
|
52
|
-
DuckPuncher.
|
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
|
-
|
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
|
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]
|
data/bin/console
CHANGED
data/lib/duck_puncher.rb
CHANGED
@@ -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
|
-
|
13
|
-
|
14
|
-
|
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
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
data/lib/duck_puncher/duck.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
23
|
-
@
|
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
|
data/lib/duck_puncher/ducks.rb
CHANGED
@@ -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
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
data/lib/duck_puncher/version.rb
CHANGED
@@ -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
|
data/test/test_helper.rb
CHANGED
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
|
+
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-
|
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
|