footing 0.2.3 → 1.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 +4 -4
- data/Gemfile.lock +58 -15
- data/README.md +23 -59
- data/Rakefile +1 -1
- data/footing.gemspec +20 -0
- data/lib/footing.rb +5 -54
- data/lib/footing/concerns/hash_methods.rb +39 -0
- data/lib/footing/hash.rb +7 -0
- data/lib/footing/object.rb +55 -0
- data/lib/footing/version.rb +1 -1
- metadata +13 -51
- data/lib/footing/extensions/array.rb +0 -18
- data/lib/footing/extensions/hash.rb +0 -123
- data/lib/footing/extensions/kernel.rb +0 -18
- data/lib/footing/extensions/nil_class.rb +0 -17
- data/lib/footing/extensions/numeric.rb +0 -37
- data/lib/footing/extensions/object.rb +0 -28
- data/lib/footing/extensions/postgresql_adapter.rb +0 -20
- data/lib/footing/extensions/schema_statements.rb +0 -75
- data/lib/footing/extensions/string.rb +0 -74
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62331199211ec8658359707f77253412878bf094
|
4
|
+
data.tar.gz: 5f342a27486320579d36a1dab9b851bdaee79de7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c22c8d9868db21ab5528d5a8855b63775243121feb1430b0604c5dd1c73a0115973555d12908a0a96700196682c33f40222ac499b29c84a79530ba7da4fffd77
|
7
|
+
data.tar.gz: 09c724182d4234decdca085191b171043059759e45bada2d6b82e56137f01c4e21b2944e558c24550264106e3f72c58d6abfb1e4acf5328a0dc9d16df60a9771
|
data/Gemfile.lock
CHANGED
@@ -1,37 +1,80 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
footing (0.
|
4
|
+
footing (1.0.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
9
|
binding_of_caller (0.7.2)
|
10
10
|
debug_inspector (>= 0.0.1)
|
11
|
-
|
11
|
+
byebug (8.2.1)
|
12
|
+
coderay (1.1.0)
|
13
|
+
coveralls (0.8.10)
|
14
|
+
json (~> 1.8)
|
15
|
+
rest-client (>= 1.6.8, < 2)
|
16
|
+
simplecov (~> 0.11.0)
|
17
|
+
term-ansicolor (~> 1.3)
|
18
|
+
thor (~> 0.19.1)
|
19
|
+
tins (~> 1.6.0)
|
12
20
|
debug_inspector (0.0.2)
|
21
|
+
docile (1.1.5)
|
22
|
+
domain_name (0.5.25)
|
23
|
+
unf (>= 0.0.5, < 1.0.0)
|
24
|
+
http-cookie (1.0.2)
|
25
|
+
domain_name (~> 0.5)
|
26
|
+
interception (0.5)
|
27
|
+
json (1.8.3)
|
13
28
|
method_source (0.8.2)
|
14
|
-
|
15
|
-
|
16
|
-
os
|
29
|
+
mime-types (2.99)
|
30
|
+
netrc (0.11.0)
|
17
31
|
os (0.9.6)
|
18
|
-
pry (0.
|
19
|
-
coderay (~> 1.0)
|
20
|
-
method_source (~> 0.8)
|
32
|
+
pry (0.10.3)
|
33
|
+
coderay (~> 1.1.0)
|
34
|
+
method_source (~> 0.8.1)
|
21
35
|
slop (~> 3.4)
|
22
|
-
pry-
|
36
|
+
pry-byebug (3.3.0)
|
37
|
+
byebug (~> 8.0)
|
38
|
+
pry (~> 0.10)
|
39
|
+
pry-rescue (1.4.2)
|
40
|
+
interception (>= 0.5)
|
41
|
+
pry
|
42
|
+
pry-stack_explorer (0.4.9.2)
|
23
43
|
binding_of_caller (>= 0.7)
|
24
44
|
pry (>= 0.9.11)
|
25
|
-
|
26
|
-
|
45
|
+
pry-test (0.6.4)
|
46
|
+
os
|
47
|
+
pry
|
48
|
+
pry-byebug
|
49
|
+
pry-rescue
|
50
|
+
pry-stack_explorer
|
51
|
+
rake (10.4.2)
|
52
|
+
rest-client (1.8.0)
|
53
|
+
http-cookie (>= 1.0.2, < 2.0)
|
54
|
+
mime-types (>= 1.16, < 3.0)
|
55
|
+
netrc (~> 0.7)
|
56
|
+
simplecov (0.11.1)
|
57
|
+
docile (~> 1.1.0)
|
58
|
+
json (~> 1.8)
|
59
|
+
simplecov-html (~> 0.10.0)
|
60
|
+
simplecov-html (0.10.0)
|
61
|
+
slop (3.6.0)
|
62
|
+
term-ansicolor (1.3.2)
|
63
|
+
tins (~> 1.0)
|
64
|
+
thor (0.19.1)
|
65
|
+
tins (1.6.0)
|
66
|
+
unf (0.1.4)
|
67
|
+
unf_ext
|
68
|
+
unf_ext (0.0.7.1)
|
27
69
|
|
28
70
|
PLATFORMS
|
29
71
|
ruby
|
30
72
|
|
31
73
|
DEPENDENCIES
|
74
|
+
coveralls
|
32
75
|
footing!
|
33
|
-
|
34
|
-
micro_test
|
35
|
-
pry
|
36
|
-
pry-stack_explorer
|
76
|
+
pry-test
|
37
77
|
rake
|
78
|
+
|
79
|
+
BUNDLED WITH
|
80
|
+
1.10.6
|
data/README.md
CHANGED
@@ -1,71 +1,35 @@
|
|
1
|
+
[](http://blog.codinghorror.com/the-best-code-is-no-code-at-all/)
|
2
|
+
[](https://codeclimate.com/github/hopsoft/footing)
|
3
|
+
[](https://gemnasium.com/hopsoft/footing)
|
4
|
+
[](https://travis-ci.org/hopsoft/footing)
|
5
|
+
[](https://coveralls.io/r/hopsoft/footing?branch=master)
|
6
|
+
[](http://rubygems.org/gems/footing)
|
7
|
+
|
1
8
|
# Footing
|
2
9
|
|
3
|
-
[
|
4
|
-
[
|
5
|
-
[
|
10
|
+
An [ActiveSupport](https://github.com/rails/rails/tree/master/activesupport)
|
11
|
+
style utility library that employs [delegation](https://en.wikipedia.org/wiki/Delegation_(programming))
|
12
|
+
instead of [monkey patching](https://en.wikipedia.org/wiki/Monkey_patch).
|
6
13
|
|
7
|
-
|
14
|
+
__NOTE:__ _The project is structured so that it can support explicit monkey patching if you prefer to use that strategy._
|
8
15
|
|
9
|
-
|
10
|
-
Think of it as a lightweight version of ActiveSupport that doesn't implicitly change native behavior.
|
16
|
+
## Immutabilty
|
11
17
|
|
12
|
-
|
18
|
+
Footing employs some principles of [immutability](https://en.wikipedia.org/wiki/Immutable_object) that are common in
|
19
|
+
[functional programming](https://en.wikipedia.org/wiki/Functional_programming).
|
20
|
+
The integrity of original objects/data is preserved because Footing creates a deep copy by default.
|
13
21
|
|
14
|
-
|
22
|
+
__NOTE:__ _This behavior can be overridden to improve performance... just be sure you know what you're doing_
|
15
23
|
|
16
|
-
|
17
|
-
Footing.patch! String, Footing::String
|
18
|
-
Footing.patch! Numeric, Footing::Numeric
|
19
|
-
```
|
24
|
+
## Hash
|
20
25
|
|
21
|
-
|
26
|
+
### Filter
|
22
27
|
|
23
|
-
|
24
|
-
String.ancestors
|
25
|
-
[
|
26
|
-
String,
|
27
|
-
Footing::String, # <--
|
28
|
-
Comparable,
|
29
|
-
Object,
|
30
|
-
Kernel,
|
31
|
-
BasicObject
|
32
|
-
]
|
33
|
-
|
34
|
-
Numeric.ancestors
|
35
|
-
[
|
36
|
-
Numeric,
|
37
|
-
Footing::Numeric, # <--
|
38
|
-
Comparable,
|
39
|
-
Object,
|
40
|
-
Kernel,
|
41
|
-
BasicObject
|
42
|
-
]
|
43
|
-
```
|
44
|
-
|
45
|
-
## Instance patching
|
46
|
-
|
47
|
-
If you don't want to corrupt the entire runtime, you can patch an instance.
|
28
|
+
Recursively filter out unwanted values based on key.
|
48
29
|
|
49
30
|
```ruby
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
31
|
+
data = { name: "Joe", password: "secret" }
|
32
|
+
copy = Footing::Hash.new(data)
|
33
|
+
copy.filter!(:password)
|
34
|
+
copy.inner_object # => {:name=>"Joe", :password=>"[FILTERED]"}
|
54
35
|
```
|
55
|
-
|
56
|
-
## Patch free
|
57
|
-
|
58
|
-
Dont like monkey patches? Run patch free by setting up utility methods instead.
|
59
|
-
|
60
|
-
```ruby
|
61
|
-
Footing.util! Footing::String
|
62
|
-
Footing::String.escape "foo", "o" # => "f\\o\\o"
|
63
|
-
```
|
64
|
-
|
65
|
-
## The Library
|
66
|
-
|
67
|
-
The suite of functionality is pretty small right now.
|
68
|
-
Poke around the [extensions directory](https://github.com/hopsoft/footing/tree/master/lib/footing/extensions) to see what's available.
|
69
|
-
|
70
|
-
Pull requests welcome.
|
71
|
-
|
data/Rakefile
CHANGED
data/footing.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "lib", "footing", "version")
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "footing"
|
5
|
+
spec.license = "MIT"
|
6
|
+
spec.version = Footing::VERSION
|
7
|
+
spec.homepage = "https://github.com/hopsoft/footing"
|
8
|
+
spec.summary = "A utility belt lib with sane monkey patching."
|
9
|
+
spec.description = "A utility belt lib with sane monkey patching."
|
10
|
+
|
11
|
+
spec.authors = ["Nathan Hopkins"]
|
12
|
+
spec.email = ["natehop@gmail.com"]
|
13
|
+
|
14
|
+
spec.files = Dir["lib/**/*.rb", "[A-Z]*"]
|
15
|
+
spec.test_files = Dir["lib/**/*.rb"]
|
16
|
+
|
17
|
+
spec.add_development_dependency "rake"
|
18
|
+
spec.add_development_dependency "pry-test"
|
19
|
+
spec.add_development_dependency "coveralls"
|
20
|
+
end
|
data/lib/footing.rb
CHANGED
@@ -1,55 +1,6 @@
|
|
1
|
-
|
2
|
-
require
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
require "footing/version"
|
3
|
+
require "footing/object"
|
4
|
+
require "footing/hash"
|
5
|
+
require "pry"
|
3
6
|
|
4
|
-
module Footing
|
5
|
-
class << self
|
6
|
-
|
7
|
-
# Patches a Module or instance with the given extension.
|
8
|
-
# @param [Module, Object] obj The Module or instance to patch.
|
9
|
-
# @param [Module] extension The Module that contains the patches.
|
10
|
-
def patch!(obj, extension)
|
11
|
-
context = patch_context(obj)
|
12
|
-
raise "#{obj.class.name} doesn't support patching!" unless context
|
13
|
-
context.send :include, extension
|
14
|
-
end
|
15
|
-
|
16
|
-
# Creates class methods for all instance methods in the module.
|
17
|
-
# This allows users to invoke utility methods rather than monkey patching if they so desire.
|
18
|
-
# @param [Module] mod The Module to setup util methods for.
|
19
|
-
def util!(mod)
|
20
|
-
proxy = Class.new(SimpleDelegator)
|
21
|
-
Footing.patch! proxy, mod
|
22
|
-
|
23
|
-
mod.instance_methods(false).each do |method_name|
|
24
|
-
mod.define_singleton_method(method_name) do |target, *args|
|
25
|
-
method = proxy.instance_method(method_name)
|
26
|
-
target = proxy.new(target)
|
27
|
-
if method.parameters.empty?
|
28
|
-
method.bind(target).call
|
29
|
-
else
|
30
|
-
method.bind(target).call(*args)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def patch_context(obj)
|
39
|
-
context = obj if obj.is_a? Module
|
40
|
-
begin
|
41
|
-
context ||= class << obj
|
42
|
-
self
|
43
|
-
end
|
44
|
-
rescue Exception
|
45
|
-
end
|
46
|
-
context
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
Dir[File.expand_path("../**/*.rb", __FILE__)].each do |file|
|
53
|
-
next if file =~ /(lib\/footing\.rb|version\.rb)\z/
|
54
|
-
ENV["FOOTING_DEV"] ? load(file) : require(file)
|
55
|
-
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Footing
|
2
|
+
module HashMethods
|
3
|
+
|
4
|
+
# Recursively filters the values for the specified keys in place.
|
5
|
+
# IMPORTANT: This mutates the Hash.
|
6
|
+
#
|
7
|
+
# @param keys [Array<Symbol,String,Regexp>] The keys to filter
|
8
|
+
# @param replacement [Object] The replacement value to use
|
9
|
+
# @return [Footing::Hash] Returns self
|
10
|
+
def filter!(keys, replacement: "[FILTERED]")
|
11
|
+
should_replace = lambda do |key|
|
12
|
+
replace = false
|
13
|
+
keys.each do |k|
|
14
|
+
break if replace
|
15
|
+
replace = k.is_a?(Regexp) ? key.to_s =~ k : key.to_s == k.to_s
|
16
|
+
end
|
17
|
+
replace
|
18
|
+
end
|
19
|
+
|
20
|
+
Footing::Hash.new(self).inner_object.each do |key, value|
|
21
|
+
if value.is_a?(::Hash)
|
22
|
+
Footing::Hash.new(value, copy: false).filter!(keys, replacement: replacement)
|
23
|
+
elsif value.is_a?(Enumerable)
|
24
|
+
value.each do |val|
|
25
|
+
if val.is_a?(::Hash)
|
26
|
+
Footing::Hash.new(val, copy: false).filter!(keys, replacement: replacement)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
else
|
30
|
+
value = replacement if should_replace.call(key)
|
31
|
+
self[key] = value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
data/lib/footing/hash.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Footing
|
2
|
+
class Object
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
attr_reader :inner_object
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def target_name
|
9
|
+
self.name.gsub(/\AFooting::/, "")
|
10
|
+
end
|
11
|
+
|
12
|
+
def match?(o)
|
13
|
+
o.class.ancestors.map(&:name).include?(target_name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def new(o, copy: true)
|
17
|
+
return o if o.is_a?(self)
|
18
|
+
super o, copy: copy
|
19
|
+
end
|
20
|
+
|
21
|
+
def copy(o)
|
22
|
+
Marshal.load Marshal.dump(o)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(o, copy: true)
|
27
|
+
raise ArgumentError.new("Types must match") unless self.class.match?(o)
|
28
|
+
o = self.class.copy(o) if copy
|
29
|
+
@inner_object = o
|
30
|
+
end
|
31
|
+
|
32
|
+
def eigen
|
33
|
+
@eigen ||= class << self
|
34
|
+
self
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_missing(name, *args)
|
39
|
+
if inner_object.respond_to?(name)
|
40
|
+
eigen.instance_eval do
|
41
|
+
define_method(name) { |*a| inner_object.send name, *a }
|
42
|
+
end
|
43
|
+
return inner_object.send name, *args
|
44
|
+
end
|
45
|
+
|
46
|
+
super
|
47
|
+
end
|
48
|
+
|
49
|
+
def respond_to?(name)
|
50
|
+
return true if inner_object.respond_to?(name)
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
data/lib/footing/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: footing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Hopkins
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-12-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: pry-test
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -39,35 +39,7 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: pry
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: pry-stack_explorer
|
42
|
+
name: coveralls
|
71
43
|
requirement: !ruby/object:Gem::Requirement
|
72
44
|
requirements:
|
73
45
|
- - ">="
|
@@ -92,16 +64,11 @@ files:
|
|
92
64
|
- LICENSE.txt
|
93
65
|
- README.md
|
94
66
|
- Rakefile
|
67
|
+
- footing.gemspec
|
95
68
|
- lib/footing.rb
|
96
|
-
- lib/footing/
|
97
|
-
- lib/footing/
|
98
|
-
- lib/footing/
|
99
|
-
- lib/footing/extensions/nil_class.rb
|
100
|
-
- lib/footing/extensions/numeric.rb
|
101
|
-
- lib/footing/extensions/object.rb
|
102
|
-
- lib/footing/extensions/postgresql_adapter.rb
|
103
|
-
- lib/footing/extensions/schema_statements.rb
|
104
|
-
- lib/footing/extensions/string.rb
|
69
|
+
- lib/footing/concerns/hash_methods.rb
|
70
|
+
- lib/footing/hash.rb
|
71
|
+
- lib/footing/object.rb
|
105
72
|
- lib/footing/version.rb
|
106
73
|
homepage: https://github.com/hopsoft/footing
|
107
74
|
licenses:
|
@@ -123,19 +90,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
90
|
version: '0'
|
124
91
|
requirements: []
|
125
92
|
rubyforge_project:
|
126
|
-
rubygems_version: 2.
|
93
|
+
rubygems_version: 2.4.5
|
127
94
|
signing_key:
|
128
95
|
specification_version: 4
|
129
96
|
summary: A utility belt lib with sane monkey patching.
|
130
97
|
test_files:
|
131
|
-
- lib/footing/
|
132
|
-
- lib/footing/
|
133
|
-
- lib/footing/
|
134
|
-
- lib/footing/extensions/nil_class.rb
|
135
|
-
- lib/footing/extensions/numeric.rb
|
136
|
-
- lib/footing/extensions/object.rb
|
137
|
-
- lib/footing/extensions/postgresql_adapter.rb
|
138
|
-
- lib/footing/extensions/schema_statements.rb
|
139
|
-
- lib/footing/extensions/string.rb
|
98
|
+
- lib/footing/concerns/hash_methods.rb
|
99
|
+
- lib/footing/hash.rb
|
100
|
+
- lib/footing/object.rb
|
140
101
|
- lib/footing/version.rb
|
141
102
|
- lib/footing.rb
|
103
|
+
has_rdoc:
|
@@ -1,18 +0,0 @@
|
|
1
|
-
module Footing
|
2
|
-
module Array
|
3
|
-
|
4
|
-
# Recursively casts all string values in this Array.
|
5
|
-
# See Footing::String#cast
|
6
|
-
def cast_values!
|
7
|
-
each_with_index do |value, index|
|
8
|
-
if value.respond_to?(:cast_values!)
|
9
|
-
value.cast_values!
|
10
|
-
elsif value.respond_to?(:cast)
|
11
|
-
self[index] = value.cast
|
12
|
-
end
|
13
|
-
end
|
14
|
-
self
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
end
|
@@ -1,123 +0,0 @@
|
|
1
|
-
module Footing
|
2
|
-
module Hash
|
3
|
-
|
4
|
-
# Rekeys the Hash by invoking a method on the existing keys
|
5
|
-
# and uses the return value as the new key.
|
6
|
-
#
|
7
|
-
# NOTE: Creates and returns a new Hash.
|
8
|
-
#
|
9
|
-
# Example:
|
10
|
-
# h = { [1] => "short", [1,2] => "medium", [1,2,3] => "long" }
|
11
|
-
# h.rekey(:length) # => { 1 => "short", 2 => "medium", 3 => "long" }
|
12
|
-
#
|
13
|
-
# @param [Symbol] name The method name to invoke on the existing keys.
|
14
|
-
# @return [Hash] A new Hash that has been re-keyed.
|
15
|
-
def rekey(method_name)
|
16
|
-
reduce({}) do |new_hash, (key, value)|
|
17
|
-
new_hash[key.public_send(method_name)] = value
|
18
|
-
new_hash
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
# Recursively forces all String values to the specified encoding.
|
23
|
-
# @param [] encoding The encoding to use.
|
24
|
-
# @yield [value] Yields the value after the encoding has been applied.
|
25
|
-
def force_encoding!(encoding, &block)
|
26
|
-
each do |key, value|
|
27
|
-
if value.respond_to?(:force_encoding!)
|
28
|
-
value.force_encoding!(encoding, &block)
|
29
|
-
elsif value.is_a?(Enumerable)
|
30
|
-
value.each do |val|
|
31
|
-
next unless val.respond_to?(:force_encoding!)
|
32
|
-
val.force_encoding!(encoding, &block)
|
33
|
-
end
|
34
|
-
elsif value.is_a?(String)
|
35
|
-
# force encoding then strip all non ascii chars
|
36
|
-
if block_given?
|
37
|
-
self[key] = yield(value.force_encoding(encoding))
|
38
|
-
else
|
39
|
-
self[key] = value.force_encoding(encoding)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Recursively adjusts the values of the Hash in place.
|
46
|
-
#
|
47
|
-
# @example
|
48
|
-
# dict = {:a => 1, :b => 2, :c => 3}
|
49
|
-
# dict.adjust_values! { |v| v.to_s }
|
50
|
-
# dict # => {:a => "1", :b => "2", :c => "3"}
|
51
|
-
#
|
52
|
-
# @yield [value] Yields the current value to the block.
|
53
|
-
# The result of the block is then assigned to the corresponding key.
|
54
|
-
def adjust_values!(&block)
|
55
|
-
each do |key, value|
|
56
|
-
if value.respond_to?(:adjust_values!)
|
57
|
-
value.adjust_values!(&block)
|
58
|
-
elsif value.is_a?(Enumerable)
|
59
|
-
value.each do |val|
|
60
|
-
next unless val.respond_to?(:adjust_values!)
|
61
|
-
val.adjust_values!(&block)
|
62
|
-
end
|
63
|
-
else
|
64
|
-
self[key] = yield(value)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
self
|
68
|
-
end
|
69
|
-
|
70
|
-
# Recursively casts all string values in this Hash.
|
71
|
-
# See Footing::String#cast
|
72
|
-
def cast_values!
|
73
|
-
each do |key, value|
|
74
|
-
if value.respond_to?(:cast_values!)
|
75
|
-
value.cast_values!
|
76
|
-
elsif value.is_a?(Enumerable)
|
77
|
-
value.each do |val|
|
78
|
-
next unless val.respond_to?(:cast_values!)
|
79
|
-
val.cast_values!
|
80
|
-
end
|
81
|
-
elsif value.respond_to?(:cast)
|
82
|
-
self[key] = value.cast
|
83
|
-
end
|
84
|
-
end
|
85
|
-
self
|
86
|
-
end
|
87
|
-
|
88
|
-
def filter!(keys, replacement="[FILTERED]")
|
89
|
-
should_replace = lambda do |key|
|
90
|
-
replace = false
|
91
|
-
keys.each do |k|
|
92
|
-
break if replace
|
93
|
-
replace = k.is_a?(Regexp) ? key.to_s =~ k : key.to_s == k.to_s
|
94
|
-
end
|
95
|
-
replace
|
96
|
-
end
|
97
|
-
|
98
|
-
each do |key, value|
|
99
|
-
if value.respond_to?(:filter!)
|
100
|
-
value.filter!(keys, replacement)
|
101
|
-
elsif value.is_a?(Enumerable)
|
102
|
-
value.each do |val|
|
103
|
-
next unless val.respond_to?(:filter!)
|
104
|
-
val.filter!(keys, replacement)
|
105
|
-
end
|
106
|
-
else
|
107
|
-
value = replacement if should_replace.call(key)
|
108
|
-
self[key] = value
|
109
|
-
end
|
110
|
-
end
|
111
|
-
self
|
112
|
-
end
|
113
|
-
|
114
|
-
def silence!(keys)
|
115
|
-
filter! keys, nil
|
116
|
-
end
|
117
|
-
|
118
|
-
def copy
|
119
|
-
Marshal.load(Marshal.dump(self))
|
120
|
-
end
|
121
|
-
|
122
|
-
end
|
123
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
module Footing
|
2
|
-
module Kernel
|
3
|
-
|
4
|
-
# Safely evals text inside of a sandbox.
|
5
|
-
# @see http://phrogz.net/programmingruby/taint.html Ruby safe level description.
|
6
|
-
# @param [String] text The text to eval.
|
7
|
-
# @param [Integer] level The safe level to apply.
|
8
|
-
# @return [Object]
|
9
|
-
def safe_eval(text, level=4)
|
10
|
-
sandbox = lambda do
|
11
|
-
$SAFE = level
|
12
|
-
eval(text.to_s)
|
13
|
-
end
|
14
|
-
sandbox.call
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
module Footing
|
2
|
-
module NilClass
|
3
|
-
|
4
|
-
# Calling [] on nil returns nil instead of raising an exception.
|
5
|
-
# Helpful when looping over nested Hashes.
|
6
|
-
#
|
7
|
-
# @example
|
8
|
-
# dict = {}
|
9
|
-
# dict[:foo][:bar][:baz] # => nil
|
10
|
-
#
|
11
|
-
# @param [Object] key The key to lookup.
|
12
|
-
def [](key)
|
13
|
-
nil
|
14
|
-
end
|
15
|
-
|
16
|
-
end
|
17
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
module Footing
|
2
|
-
module Numeric
|
3
|
-
|
4
|
-
# Returns a positive representation of the number.
|
5
|
-
def positive
|
6
|
-
abs
|
7
|
-
end
|
8
|
-
|
9
|
-
# Returns a negative representation of the number.
|
10
|
-
def negative
|
11
|
-
abs.flip_sign
|
12
|
-
end
|
13
|
-
|
14
|
-
# Flips the sign on the number making it either either positive or negative.
|
15
|
-
def flip_sign
|
16
|
-
self * -1
|
17
|
-
end
|
18
|
-
|
19
|
-
# Returns the percentage that this number is of the passed number.
|
20
|
-
# @example
|
21
|
-
# 8.percent_of(10) # => 80.0
|
22
|
-
# @param [Numeric] number The number to calculate the percentage with
|
23
|
-
def percent_of(number)
|
24
|
-
percent = (self.to_f / number.to_f) * 100 if number > 0
|
25
|
-
percent ||= 0.0
|
26
|
-
end
|
27
|
-
|
28
|
-
# Rounds the number to a certain number of decimal places.
|
29
|
-
# @example
|
30
|
-
# 1.784329.round_to(1) # => 1.8
|
31
|
-
# @param [Numeric] decimal_places The number of decimal places to round to
|
32
|
-
def round_to(decimal_places)
|
33
|
-
(self * 10**decimal_places).round.to_f / 10**decimal_places
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
37
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
module Footing
|
2
|
-
module Object
|
3
|
-
|
4
|
-
# Returns the eigen class for the object.
|
5
|
-
def eigen
|
6
|
-
class << self
|
7
|
-
self
|
8
|
-
end
|
9
|
-
rescue Exception
|
10
|
-
nil
|
11
|
-
end
|
12
|
-
|
13
|
-
# Indicates if the object has an eigen class.
|
14
|
-
def has_eigen?
|
15
|
-
!eigen.nil?
|
16
|
-
end
|
17
|
-
|
18
|
-
# Trys to invoke a method on the object.
|
19
|
-
# Returns nil if the method isn't supported.
|
20
|
-
def try(name, *args, &block)
|
21
|
-
if respond_to?(name)
|
22
|
-
return public_send(name, *args, &block)
|
23
|
-
end
|
24
|
-
nil
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# Extend Rails with this module to add a uuid method in your migrations.
|
2
|
-
#
|
3
|
-
# Example:
|
4
|
-
# # rails_root/config/application.rb
|
5
|
-
# config.after_initialize do
|
6
|
-
# Footing.patch! ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::TableDefinition, Footing::PGTableDefinition
|
7
|
-
# end
|
8
|
-
#
|
9
|
-
module Footing
|
10
|
-
module PGTableDefinition
|
11
|
-
|
12
|
-
# Provides a uuid method when inside a migration's create_table block.
|
13
|
-
def uuid(*args)
|
14
|
-
options = args.extract_options!
|
15
|
-
#column(args[0], 'uuid default uuid_generate_v1()', options)
|
16
|
-
column(args[0], 'uuid', options)
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
20
|
-
end
|
@@ -1,75 +0,0 @@
|
|
1
|
-
# Extend Rails to support adding timestamp indexes for created_at & updated_at using day granularity.
|
2
|
-
#
|
3
|
-
# @example Make these statements available to ActiveRecord::Migration change, up, down methods
|
4
|
-
# # rails_root/config/application.rb
|
5
|
-
# config.after_initialize do
|
6
|
-
# Footing.patch! ActiveRecord::ConnectionAdapters::AbstractAdapter, Footing::PGSchemaStatements
|
7
|
-
# end
|
8
|
-
#
|
9
|
-
module Footing
|
10
|
-
module PGSchemaStatements
|
11
|
-
|
12
|
-
# Adds indexes to the created_at and updated_at timestamp columns with 'day' granularity.
|
13
|
-
def add_timestamp_indexes(table_name)
|
14
|
-
%w(created_at updated_at).each do |column_name|
|
15
|
-
add_datetime_index(table_name, column_name, :precision => :day)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# Removes indexes to the created_at and updated_at timestamp columns with 'day' granularity.
|
20
|
-
def remove_timestamp_indexes(table_name)
|
21
|
-
%w(created_at updated_at).each do |column_name|
|
22
|
-
remove_datetime_index(table_name, column_name, :precision => :day)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Adds an index on a datetime column using the specified precision.
|
27
|
-
# @param [Symbol,String] table_name The name of the table to migrate.
|
28
|
-
# @param [Symbol,String] column_name The name of the column to migrate.
|
29
|
-
# @param [Hash] options
|
30
|
-
# @option options [Symbol,String] :precision
|
31
|
-
# - microseconds
|
32
|
-
# - milliseconds
|
33
|
-
# - second
|
34
|
-
# - minute *default
|
35
|
-
# - hour
|
36
|
-
# - day
|
37
|
-
# - week
|
38
|
-
# - month
|
39
|
-
# - quarter
|
40
|
-
# - year
|
41
|
-
# - decade
|
42
|
-
# - century
|
43
|
-
# - millennium
|
44
|
-
def add_datetime_index(table_name, column_name, options={})
|
45
|
-
options[:precision] ||= :minute
|
46
|
-
index_name = "index_#{table_name}_on_#{column_name}_by_#{options[:precision]}"
|
47
|
-
execute "create index #{index_name} on #{quote_table_name(table_name)} (date_trunc('#{options[:precision]}', #{quote_column_name(column_name)}))"
|
48
|
-
end
|
49
|
-
|
50
|
-
# Removes an index on a datetime column using the specified precision.
|
51
|
-
# @param [Symbol,String] table_name The name of the table to migrate.
|
52
|
-
# @param [Symbol,String] column_name The name of the column to migrate.
|
53
|
-
# @param [Hash] options
|
54
|
-
# @option options [Symbol,String] :precision
|
55
|
-
# - microseconds
|
56
|
-
# - milliseconds
|
57
|
-
# - second
|
58
|
-
# - minute *default
|
59
|
-
# - hour
|
60
|
-
# - day
|
61
|
-
# - week
|
62
|
-
# - month
|
63
|
-
# - quarter
|
64
|
-
# - year
|
65
|
-
# - decade
|
66
|
-
# - century
|
67
|
-
# - millennium
|
68
|
-
def remove_datetime_index(table_name, column_name, options={})
|
69
|
-
options[:precision] ||= :minute
|
70
|
-
index_name = "index_#{table_name}_on_#{column_name}_by_#{options[:precision]}"
|
71
|
-
execute "drop index if exists #{index_name}"
|
72
|
-
end
|
73
|
-
|
74
|
-
end
|
75
|
-
end
|
@@ -1,74 +0,0 @@
|
|
1
|
-
module Footing
|
2
|
-
module String
|
3
|
-
|
4
|
-
# Generates a random string.
|
5
|
-
#
|
6
|
-
# @example
|
7
|
-
# Footing.util! Footing::String
|
8
|
-
# Footing::String.random_key(100, :reject => ["1", "I", "l", "0", "O"])
|
9
|
-
#
|
10
|
-
# @param [Integer] length The length of the key to generate. Defaults to 12.
|
11
|
-
# @param [Hash] opts
|
12
|
-
# @option opts [Array] :reject A list of characters to omit from the key.
|
13
|
-
# @option opts [Boolean] :upcase Indicates that only use uppercase characters should be used.
|
14
|
-
# @return [String]
|
15
|
-
def random(length=12, opts={})
|
16
|
-
@chars ||= [(0..9).to_a, ('a'..'z').to_a, ('A'..'Z').to_a].flatten.map { |c| c.to_s }
|
17
|
-
chars = @chars.reject do |c|
|
18
|
-
c =~ /[a-z]/ if opts[:upcase]
|
19
|
-
end
|
20
|
-
opts[:reject] ||= []
|
21
|
-
opts[:reject] = opts[:reject].map { |c| c.to_s }
|
22
|
-
(1..length).map{ |i| (chars - opts[:reject]).sample }.join
|
23
|
-
end
|
24
|
-
|
25
|
-
# Escapes a series of chars.
|
26
|
-
def escape(*chars)
|
27
|
-
gsub(/(?<!\\)(#{chars.join("|")})/) do |char|
|
28
|
-
"\\" + char
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Converts a word with underscores into a sentance with a capitalized first word.
|
33
|
-
def humanize
|
34
|
-
self.downcase.gsub(/_/, " ").capitalize
|
35
|
-
end
|
36
|
-
|
37
|
-
# Similar to humanize but it capitalizes each word
|
38
|
-
def titleize
|
39
|
-
self.gsub(/\s/, "_").split('_').map(&:capitalize).join(' ')
|
40
|
-
end
|
41
|
-
alias :titlecase :titleize
|
42
|
-
|
43
|
-
|
44
|
-
def classify
|
45
|
-
self.gsub(/\s/, "_").titleize.gsub(/\W/, "")
|
46
|
-
end
|
47
|
-
|
48
|
-
# Indicates if this string represents a number.
|
49
|
-
def numeric?
|
50
|
-
!!(self =~ /\A[0-9]+\.*[0-9]*\z/)
|
51
|
-
end
|
52
|
-
|
53
|
-
# Indicates if this string represents a boolean value.
|
54
|
-
def boolean?
|
55
|
-
!!(self =~ /\A(true|false)\z/i)
|
56
|
-
end
|
57
|
-
|
58
|
-
# Casts this string to another datatype.
|
59
|
-
# Supported datatypes:
|
60
|
-
# * Integer
|
61
|
-
# * Float
|
62
|
-
# * Boolean
|
63
|
-
def cast
|
64
|
-
return to_f if numeric? && index(".")
|
65
|
-
return to_i if numeric?
|
66
|
-
if boolean?
|
67
|
-
return true if self =~ /\Atrue\z/i
|
68
|
-
return false if self =~ /\Afalse\z/i
|
69
|
-
end
|
70
|
-
self
|
71
|
-
end
|
72
|
-
|
73
|
-
end
|
74
|
-
end
|