motion-support 0.1.0 → 0.2.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.
- data/.gitignore +3 -0
- data/Gemfile +1 -1
- data/README.md +231 -2
- data/Rakefile +3 -3
- data/app/app_delegate.rb +0 -2
- data/examples/Inflector/.gitignore +16 -0
- data/examples/Inflector/Gemfile +5 -0
- data/examples/Inflector/Rakefile +13 -0
- data/examples/Inflector/app/app_delegate.rb +8 -0
- data/examples/Inflector/app/inflections.rb +5 -0
- data/examples/Inflector/app/inflector_view_controller.rb +120 -0
- data/examples/Inflector/app/words_view_controller.rb +101 -0
- data/examples/Inflector/resources/Default-568h@2x.png +0 -0
- data/examples/Inflector/spec/main_spec.rb +9 -0
- data/lib/motion-support/core_ext/array.rb +13 -0
- data/lib/motion-support/core_ext/class.rb +8 -0
- data/lib/motion-support/core_ext/hash.rb +16 -0
- data/lib/motion-support/core_ext/integer.rb +9 -0
- data/lib/motion-support/core_ext/module.rb +14 -0
- data/lib/motion-support/core_ext/numeric.rb +8 -0
- data/lib/motion-support/core_ext/object.rb +14 -0
- data/lib/motion-support/core_ext/range.rb +8 -0
- data/lib/motion-support/core_ext/string.rb +14 -0
- data/lib/motion-support/core_ext/time.rb +19 -0
- data/lib/motion-support/core_ext.rb +3 -0
- data/lib/motion-support/inflector.rb +12 -156
- data/lib/motion-support.rb +5 -5
- data/motion/_stdlib/cgi.rb +22 -0
- data/motion/_stdlib/date.rb +77 -0
- data/motion/_stdlib/time.rb +19 -0
- data/motion/core_ext/array/access.rb +28 -0
- data/motion/core_ext/array/conversions.rb +86 -0
- data/motion/core_ext/array/extract_options.rb +11 -0
- data/motion/core_ext/array/grouping.rb +99 -0
- data/motion/core_ext/array/prepend_and_append.rb +7 -0
- data/motion/core_ext/array/wrap.rb +45 -0
- data/{lib/motion-support → motion/core_ext}/array.rb +0 -4
- data/motion/core_ext/class/attribute.rb +119 -0
- data/motion/core_ext/class/attribute_accessors.rb +168 -0
- data/motion/core_ext/date/acts_like.rb +8 -0
- data/motion/core_ext/date/calculations.rb +117 -0
- data/motion/core_ext/date/conversions.rb +56 -0
- data/motion/core_ext/date_and_time/calculations.rb +232 -0
- data/motion/core_ext/enumerable.rb +90 -0
- data/motion/core_ext/hash/deep_merge.rb +27 -0
- data/motion/core_ext/hash/except.rb +15 -0
- data/motion/core_ext/hash/indifferent_access.rb +19 -0
- data/motion/core_ext/hash/keys.rb +138 -0
- data/motion/core_ext/hash/reverse_merge.rb +22 -0
- data/motion/core_ext/hash/slice.rb +40 -0
- data/motion/core_ext/integer/inflections.rb +27 -0
- data/motion/core_ext/integer/multiple.rb +10 -0
- data/motion/core_ext/integer/time.rb +41 -0
- data/{lib/motion-support → motion/core_ext}/metaclass.rb +0 -0
- data/motion/core_ext/module/aliasing.rb +69 -0
- data/motion/core_ext/module/anonymous.rb +19 -0
- data/motion/core_ext/module/attr_internal.rb +38 -0
- data/motion/core_ext/module/attribute_accessors.rb +64 -0
- data/motion/core_ext/module/delegation.rb +175 -0
- data/motion/core_ext/module/introspection.rb +60 -0
- data/motion/core_ext/module/reachable.rb +5 -0
- data/motion/core_ext/module/remove_method.rb +12 -0
- data/motion/core_ext/ns_dictionary.rb +11 -0
- data/motion/core_ext/numeric/bytes.rb +44 -0
- data/motion/core_ext/numeric/conversions.rb +7 -0
- data/motion/core_ext/numeric/time.rb +75 -0
- data/motion/core_ext/object/acts_like.rb +10 -0
- data/motion/core_ext/object/blank.rb +105 -0
- data/motion/core_ext/object/deep_dup.rb +44 -0
- data/motion/core_ext/object/duplicable.rb +83 -0
- data/motion/core_ext/object/instance_variables.rb +28 -0
- data/motion/core_ext/object/to_param.rb +58 -0
- data/motion/core_ext/object/to_query.rb +26 -0
- data/motion/core_ext/object/try.rb +78 -0
- data/motion/core_ext/range/include_range.rb +23 -0
- data/motion/core_ext/range/overlaps.rb +8 -0
- data/motion/core_ext/regexp.rb +5 -0
- data/motion/core_ext/string/access.rb +104 -0
- data/motion/core_ext/string/behavior.rb +6 -0
- data/motion/core_ext/string/exclude.rb +11 -0
- data/motion/core_ext/string/filters.rb +55 -0
- data/motion/core_ext/string/indent.rb +43 -0
- data/motion/core_ext/string/inflections.rb +195 -0
- data/motion/core_ext/string/starts_ends_with.rb +4 -0
- data/motion/core_ext/string/strip.rb +24 -0
- data/motion/core_ext/time/acts_like.rb +8 -0
- data/motion/core_ext/time/calculations.rb +215 -0
- data/motion/core_ext/time/conversions.rb +52 -0
- data/motion/duration.rb +104 -0
- data/motion/hash_with_indifferent_access.rb +251 -0
- data/motion/inflections.rb +67 -0
- data/motion/inflector/inflections.rb +203 -0
- data/motion/inflector/methods.rb +321 -0
- data/{lib/motion-support → motion}/logger.rb +0 -0
- data/{lib/motion-support → motion}/version.rb +1 -1
- data/motion-support.gemspec +2 -2
- data/spec/motion-support/_helpers/constantize_test_cases.rb +75 -0
- data/spec/motion-support/_helpers/inflector_test_cases.rb +313 -0
- data/spec/motion-support/core_ext/array/access_spec.rb +29 -0
- data/spec/motion-support/core_ext/array/conversion_spec.rb +60 -0
- data/spec/motion-support/core_ext/array/extract_options_spec.rb +15 -0
- data/spec/motion-support/core_ext/array/grouping_spec.rb +85 -0
- data/spec/motion-support/core_ext/array/prepend_and_append_spec.rb +25 -0
- data/spec/motion-support/core_ext/array/wrap_spec.rb +19 -0
- data/spec/motion-support/{array_spec.rb → core_ext/array_spec.rb} +0 -5
- data/spec/motion-support/core_ext/class/attribute_accessor_spec.rb +127 -0
- data/spec/motion-support/core_ext/class/attribute_spec.rb +92 -0
- data/spec/motion-support/core_ext/date/acts_like_spec.rb +11 -0
- data/spec/motion-support/core_ext/date/calculation_spec.rb +186 -0
- data/spec/motion-support/core_ext/date/conversion_spec.rb +18 -0
- data/spec/motion-support/core_ext/date_and_time/calculation_spec.rb +336 -0
- data/spec/motion-support/core_ext/enumerable_spec.rb +130 -0
- data/spec/motion-support/core_ext/hash/deep_merge_spec.rb +32 -0
- data/spec/motion-support/core_ext/hash/except_spec.rb +43 -0
- data/spec/motion-support/core_ext/hash/key_spec.rb +230 -0
- data/spec/motion-support/core_ext/hash/reverse_merge_spec.rb +26 -0
- data/spec/motion-support/core_ext/hash/slice_spec.rb +61 -0
- data/spec/motion-support/core_ext/integer/inflection_spec.rb +23 -0
- data/spec/motion-support/core_ext/integer/multiple_spec.rb +19 -0
- data/spec/motion-support/{metaclass_spec.rb → core_ext/metaclass_spec.rb} +0 -0
- data/spec/motion-support/core_ext/module/aliasing_spec.rb +143 -0
- data/spec/motion-support/core_ext/module/anonymous_spec.rb +29 -0
- data/spec/motion-support/core_ext/module/attr_internal_spec.rb +104 -0
- data/spec/motion-support/core_ext/module/attribute_accessor_spec.rb +86 -0
- data/spec/motion-support/core_ext/module/delegation_spec.rb +136 -0
- data/spec/motion-support/core_ext/module/introspection_spec.rb +70 -0
- data/spec/motion-support/core_ext/module/reachable_spec.rb +61 -0
- data/spec/motion-support/core_ext/module/remove_method_spec.rb +25 -0
- data/spec/motion-support/core_ext/numeric/bytes_spec.rb +43 -0
- data/spec/motion-support/core_ext/object/acts_like_spec.rb +21 -0
- data/spec/motion-support/core_ext/object/blank_spec.rb +54 -0
- data/spec/motion-support/core_ext/object/deep_dup_spec.rb +54 -0
- data/spec/motion-support/core_ext/object/duplicable_spec.rb +31 -0
- data/spec/motion-support/core_ext/object/instance_variable_spec.rb +19 -0
- data/spec/motion-support/core_ext/object/to_param_spec.rb +75 -0
- data/spec/motion-support/core_ext/object/to_query_spec.rb +37 -0
- data/spec/motion-support/core_ext/object/try_spec.rb +92 -0
- data/spec/motion-support/core_ext/range/include_range_spec.rb +31 -0
- data/spec/motion-support/core_ext/range/overlap_spec.rb +43 -0
- data/spec/motion-support/core_ext/regexp_spec.rb +7 -0
- data/spec/motion-support/core_ext/string/access_spec.rb +53 -0
- data/spec/motion-support/core_ext/string/behavior_spec.rb +7 -0
- data/spec/motion-support/core_ext/string/exclude_spec.rb +8 -0
- data/spec/motion-support/core_ext/string/filter_spec.rb +48 -0
- data/spec/motion-support/core_ext/string/indent_spec.rb +56 -0
- data/spec/motion-support/core_ext/string/inflection_spec.rb +142 -0
- data/spec/motion-support/core_ext/string/starts_end_with_spec.rb +14 -0
- data/spec/motion-support/core_ext/string/strip_spec.rb +34 -0
- data/spec/motion-support/core_ext/string_spec.rb +88 -0
- data/spec/motion-support/core_ext/time/acts_like_spec.rb +11 -0
- data/spec/motion-support/core_ext/time/calculation_spec.rb +201 -0
- data/spec/motion-support/core_ext/time/conversion_spec.rb +54 -0
- data/spec/motion-support/duration_spec.rb +107 -0
- data/spec/motion-support/hash_with_indifferent_access_spec.rb +605 -0
- data/spec/motion-support/inflector_spec.rb +474 -35
- data/spec/motion-support/ns_dictionary_spec.rb +29 -0
- metadata +212 -35
- data/lib/motion-support/cattr_accessor.rb +0 -19
- data/lib/motion-support/class_inheritable_accessor.rb +0 -23
- data/lib/motion-support/class_inheritable_array.rb +0 -29
- data/lib/motion-support/hash.rb +0 -31
- data/lib/motion-support/nilclass.rb +0 -5
- data/lib/motion-support/object.rb +0 -17
- data/lib/motion-support/string.rb +0 -71
- data/spec/motion-support/cattr_accessor_spec.rb +0 -49
- data/spec/motion-support/class_inheritable_accessor_spec.rb +0 -49
- data/spec/motion-support/class_inheritable_array_spec.rb +0 -61
- data/spec/motion-support/hash_spec.rb +0 -31
- data/spec/motion-support/nilclass_spec.rb +0 -5
- data/spec/motion-support/object_spec.rb +0 -43
- data/spec/motion-support/string_spec.rb +0 -145
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module Enumerable
|
|
2
|
+
# Iterates through a container, yielding each element and its index to the given block.
|
|
3
|
+
def collect_with_index(&block)
|
|
4
|
+
index = 0
|
|
5
|
+
collect do |value|
|
|
6
|
+
block.call(value, index).tap do
|
|
7
|
+
index += 1
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Calculates a sum from the elements.
|
|
13
|
+
#
|
|
14
|
+
# payments.sum { |p| p.price * p.tax_rate }
|
|
15
|
+
# payments.sum(&:price)
|
|
16
|
+
#
|
|
17
|
+
# The latter is a shortcut for:
|
|
18
|
+
#
|
|
19
|
+
# payments.inject(0) { |sum, p| sum + p.price }
|
|
20
|
+
#
|
|
21
|
+
# It can also calculate the sum without the use of a block.
|
|
22
|
+
#
|
|
23
|
+
# [5, 15, 10].sum # => 30
|
|
24
|
+
# ['foo', 'bar'].sum # => "foobar"
|
|
25
|
+
# [[1, 2], [3, 1, 5]].sum => [1, 2, 3, 1, 5]
|
|
26
|
+
#
|
|
27
|
+
# The default sum of an empty list is zero. You can override this default:
|
|
28
|
+
#
|
|
29
|
+
# [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
|
|
30
|
+
def sum(identity = 0, &block)
|
|
31
|
+
if block_given?
|
|
32
|
+
map(&block).sum(identity)
|
|
33
|
+
else
|
|
34
|
+
inject { |sum, element| sum + element } || identity
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Convert an enumerable to a hash.
|
|
39
|
+
#
|
|
40
|
+
# people.index_by(&:login)
|
|
41
|
+
# => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
|
|
42
|
+
# people.index_by { |person| "#{person.first_name} #{person.last_name}" }
|
|
43
|
+
# => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
|
|
44
|
+
def index_by
|
|
45
|
+
if block_given?
|
|
46
|
+
Hash[map { |elem| [yield(elem), elem] }]
|
|
47
|
+
else
|
|
48
|
+
to_enum :index_by
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns +true+ if the enumerable has more than 1 element. Functionally
|
|
53
|
+
# equivalent to <tt>enum.to_a.size > 1</tt>. Can be called with a block too,
|
|
54
|
+
# much like any?, so <tt>people.many? { |p| p.age > 26 }</tt> returns +true+
|
|
55
|
+
# if more than one person is over 26.
|
|
56
|
+
def many?
|
|
57
|
+
cnt = 0
|
|
58
|
+
if block_given?
|
|
59
|
+
any? do |element|
|
|
60
|
+
cnt += 1 if yield element
|
|
61
|
+
cnt > 1
|
|
62
|
+
end
|
|
63
|
+
else
|
|
64
|
+
any? { (cnt += 1) > 1 }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The negative of the <tt>Enumerable#include?</tt>. Returns +true+ if the
|
|
69
|
+
# collection does not include the object.
|
|
70
|
+
def exclude?(object)
|
|
71
|
+
!include?(object)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class Range #:nodoc:
|
|
76
|
+
# Optimize range sum to use arithmetic progression if a block is not given and
|
|
77
|
+
# we have a range of numeric values.
|
|
78
|
+
def sum(identity = 0)
|
|
79
|
+
if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer))
|
|
80
|
+
super
|
|
81
|
+
else
|
|
82
|
+
actual_last = exclude_end? ? (last - 1) : last
|
|
83
|
+
if actual_last >= first
|
|
84
|
+
(actual_last - first + 1) * (actual_last + first) / 2
|
|
85
|
+
else
|
|
86
|
+
identity
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class Hash
|
|
2
|
+
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
|
3
|
+
#
|
|
4
|
+
# h1 = { x: { y: [4,5,6] }, z: [7,8,9] }
|
|
5
|
+
# h2 = { x: { y: [7,8,9] }, z: 'xyz' }
|
|
6
|
+
#
|
|
7
|
+
# h1.deep_merge(h2) #=> {x: {y: [7, 8, 9]}, z: "xyz"}
|
|
8
|
+
# h2.deep_merge(h1) #=> {x: {y: [4, 5, 6]}, z: [7, 8, 9]}
|
|
9
|
+
# h1.deep_merge(h2) { |key, old, new| Array.wrap(old) + Array.wrap(new) }
|
|
10
|
+
# #=> {:x=>{:y=>[4, 5, 6, 7, 8, 9]}, :z=>[7, 8, 9, "xyz"]}
|
|
11
|
+
def deep_merge(other_hash, &block)
|
|
12
|
+
dup.deep_merge!(other_hash, &block)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Same as +deep_merge+, but modifies +self+.
|
|
16
|
+
def deep_merge!(other_hash, &block)
|
|
17
|
+
other_hash.each_pair do |k,v|
|
|
18
|
+
tv = self[k]
|
|
19
|
+
if tv.is_a?(Hash) && v.is_a?(Hash)
|
|
20
|
+
self[k] = tv.deep_merge(v, &block)
|
|
21
|
+
else
|
|
22
|
+
self[k] = block && tv ? block.call(k, tv, v) : v
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class Hash
|
|
2
|
+
# Return a hash that includes everything but the given keys. This is useful for
|
|
3
|
+
# limiting a set of parameters to everything but a few known toggles:
|
|
4
|
+
#
|
|
5
|
+
# @person.update(params[:person].except(:admin))
|
|
6
|
+
def except(*keys)
|
|
7
|
+
dup.except!(*keys)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Replaces the hash without the given keys.
|
|
11
|
+
def except!(*keys)
|
|
12
|
+
keys.each { |key| delete(key) }
|
|
13
|
+
self
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class Hash
|
|
2
|
+
# Returns an <tt>MotionSupport::HashWithIndifferentAccess</tt> out of its receiver:
|
|
3
|
+
#
|
|
4
|
+
# { a: 1 }.with_indifferent_access['a'] # => 1
|
|
5
|
+
def with_indifferent_access
|
|
6
|
+
MotionSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Called when object is nested under an object that receives
|
|
10
|
+
# #with_indifferent_access. This method will be called on the current object
|
|
11
|
+
# by the enclosing object and is aliased to #with_indifferent_access by
|
|
12
|
+
# default. Subclasses of Hash may overwrite this method to return +self+ if
|
|
13
|
+
# converting to an <tt>MotionSupport::HashWithIndifferentAccess</tt> would not be
|
|
14
|
+
# desirable.
|
|
15
|
+
#
|
|
16
|
+
# b = { b: 1 }
|
|
17
|
+
# { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access
|
|
18
|
+
alias nested_under_indifferent_access with_indifferent_access
|
|
19
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
class Hash
|
|
2
|
+
# Return a new hash with all keys converted using the block operation.
|
|
3
|
+
#
|
|
4
|
+
# hash = { name: 'Rob', age: '28' }
|
|
5
|
+
#
|
|
6
|
+
# hash.transform_keys{ |key| key.to_s.upcase }
|
|
7
|
+
# # => { "NAME" => "Rob", "AGE" => "28" }
|
|
8
|
+
def transform_keys
|
|
9
|
+
result = {}
|
|
10
|
+
each_key do |key|
|
|
11
|
+
result[yield(key)] = self[key]
|
|
12
|
+
end
|
|
13
|
+
result
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Destructively convert all keys using the block operations.
|
|
17
|
+
# Same as transform_keys but modifies +self+.
|
|
18
|
+
def transform_keys!
|
|
19
|
+
keys.each do |key|
|
|
20
|
+
self[yield(key)] = delete(key)
|
|
21
|
+
end
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Return a new hash with all keys converted to strings.
|
|
26
|
+
#
|
|
27
|
+
# hash = { name: 'Rob', age: '28' }
|
|
28
|
+
#
|
|
29
|
+
# hash.stringify_keys
|
|
30
|
+
# #=> { "name" => "Rob", "age" => "28" }
|
|
31
|
+
def stringify_keys
|
|
32
|
+
transform_keys{ |key| key.to_s }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Destructively convert all keys to strings. Same as
|
|
36
|
+
# +stringify_keys+, but modifies +self+.
|
|
37
|
+
def stringify_keys!
|
|
38
|
+
transform_keys!{ |key| key.to_s }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Return a new hash with all keys converted to symbols, as long as
|
|
42
|
+
# they respond to +to_sym+.
|
|
43
|
+
#
|
|
44
|
+
# hash = { 'name' => 'Rob', 'age' => '28' }
|
|
45
|
+
#
|
|
46
|
+
# hash.symbolize_keys
|
|
47
|
+
# #=> { name: "Rob", age: "28" }
|
|
48
|
+
def symbolize_keys
|
|
49
|
+
transform_keys{ |key| key.to_sym rescue key }
|
|
50
|
+
end
|
|
51
|
+
alias_method :to_options, :symbolize_keys
|
|
52
|
+
|
|
53
|
+
# Destructively convert all keys to symbols, as long as they respond
|
|
54
|
+
# to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
|
|
55
|
+
def symbolize_keys!
|
|
56
|
+
transform_keys!{ |key| key.to_sym rescue key }
|
|
57
|
+
end
|
|
58
|
+
alias_method :to_options!, :symbolize_keys!
|
|
59
|
+
|
|
60
|
+
# Validate all keys in a hash match <tt>*valid_keys</tt>, raising ArgumentError
|
|
61
|
+
# on a mismatch. Note that keys are NOT treated indifferently, meaning if you
|
|
62
|
+
# use strings for keys but assert symbols as keys, this will fail.
|
|
63
|
+
#
|
|
64
|
+
# { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years"
|
|
65
|
+
# { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: name"
|
|
66
|
+
# { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing
|
|
67
|
+
def assert_valid_keys(*valid_keys)
|
|
68
|
+
valid_keys.flatten!
|
|
69
|
+
each_key do |k|
|
|
70
|
+
raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Return a new hash with all keys converted by the block operation.
|
|
75
|
+
# This includes the keys from the root hash and from all
|
|
76
|
+
# nested hashes.
|
|
77
|
+
#
|
|
78
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
|
79
|
+
#
|
|
80
|
+
# hash.deep_transform_keys{ |key| key.to_s.upcase }
|
|
81
|
+
# # => { "PERSON" => { "NAME" => "Rob", "AGE" => "28" } }
|
|
82
|
+
def deep_transform_keys(&block)
|
|
83
|
+
result = {}
|
|
84
|
+
each do |key, value|
|
|
85
|
+
result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
|
|
86
|
+
end
|
|
87
|
+
result
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Destructively convert all keys by using the block operation.
|
|
91
|
+
# This includes the keys from the root hash and from all
|
|
92
|
+
# nested hashes.
|
|
93
|
+
def deep_transform_keys!(&block)
|
|
94
|
+
keys.each do |key|
|
|
95
|
+
value = delete(key)
|
|
96
|
+
self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value
|
|
97
|
+
end
|
|
98
|
+
self
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Return a new hash with all keys converted to strings.
|
|
102
|
+
# This includes the keys from the root hash and from all
|
|
103
|
+
# nested hashes.
|
|
104
|
+
#
|
|
105
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
|
106
|
+
#
|
|
107
|
+
# hash.deep_stringify_keys
|
|
108
|
+
# # => { "person" => { "name" => "Rob", "age" => "28" } }
|
|
109
|
+
def deep_stringify_keys
|
|
110
|
+
deep_transform_keys{ |key| key.to_s }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Destructively convert all keys to strings.
|
|
114
|
+
# This includes the keys from the root hash and from all
|
|
115
|
+
# nested hashes.
|
|
116
|
+
def deep_stringify_keys!
|
|
117
|
+
deep_transform_keys!{ |key| key.to_s }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Return a new hash with all keys converted to symbols, as long as
|
|
121
|
+
# they respond to +to_sym+. This includes the keys from the root hash
|
|
122
|
+
# and from all nested hashes.
|
|
123
|
+
#
|
|
124
|
+
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
|
|
125
|
+
#
|
|
126
|
+
# hash.deep_symbolize_keys
|
|
127
|
+
# # => { person: { name: "Rob", age: "28" } }
|
|
128
|
+
def deep_symbolize_keys
|
|
129
|
+
deep_transform_keys{ |key| key.to_sym rescue key }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Destructively convert all keys to symbols, as long as they respond
|
|
133
|
+
# to +to_sym+. This includes the keys from the root hash and from all
|
|
134
|
+
# nested hashes.
|
|
135
|
+
def deep_symbolize_keys!
|
|
136
|
+
deep_transform_keys!{ |key| key.to_sym rescue key }
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class Hash
|
|
2
|
+
# Merges the caller into +other_hash+. For example,
|
|
3
|
+
#
|
|
4
|
+
# options = options.reverse_merge(size: 25, velocity: 10)
|
|
5
|
+
#
|
|
6
|
+
# is equivalent to
|
|
7
|
+
#
|
|
8
|
+
# options = { size: 25, velocity: 10 }.merge(options)
|
|
9
|
+
#
|
|
10
|
+
# This is particularly useful for initializing an options hash
|
|
11
|
+
# with default values.
|
|
12
|
+
def reverse_merge(other_hash)
|
|
13
|
+
other_hash.merge(self)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Destructive +reverse_merge+.
|
|
17
|
+
def reverse_merge!(other_hash)
|
|
18
|
+
# right wins if there is no left
|
|
19
|
+
merge!( other_hash ){|key,left,right| left }
|
|
20
|
+
end
|
|
21
|
+
alias_method :reverse_update, :reverse_merge!
|
|
22
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
class Hash
|
|
2
|
+
# Slice a hash to include only the given keys. This is useful for
|
|
3
|
+
# limiting an options hash to valid keys before passing to a method:
|
|
4
|
+
#
|
|
5
|
+
# def search(criteria = {})
|
|
6
|
+
# criteria.assert_valid_keys(:mass, :velocity, :time)
|
|
7
|
+
# end
|
|
8
|
+
#
|
|
9
|
+
# search(options.slice(:mass, :velocity, :time))
|
|
10
|
+
#
|
|
11
|
+
# If you have an array of keys you want to limit to, you should splat them:
|
|
12
|
+
#
|
|
13
|
+
# valid_keys = [:mass, :velocity, :time]
|
|
14
|
+
# search(options.slice(*valid_keys))
|
|
15
|
+
def slice(*keys)
|
|
16
|
+
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
|
|
17
|
+
keys.each_with_object(self.class.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Replaces the hash with only the given keys.
|
|
21
|
+
# Returns a hash containing the removed key/value pairs.
|
|
22
|
+
#
|
|
23
|
+
# { a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b)
|
|
24
|
+
# # => {:c=>3, :d=>4}
|
|
25
|
+
def slice!(*keys)
|
|
26
|
+
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
|
|
27
|
+
omit = slice(*self.keys - keys)
|
|
28
|
+
hash = slice(*keys)
|
|
29
|
+
replace(hash)
|
|
30
|
+
omit
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Removes and returns the key/value pairs matching the given keys.
|
|
34
|
+
#
|
|
35
|
+
# { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2}
|
|
36
|
+
# { a: 1, b: 2 }.extract!(:a, :x) # => {:a=>1}
|
|
37
|
+
def extract!(*keys)
|
|
38
|
+
keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class Integer
|
|
2
|
+
# Ordinalize turns a number into an ordinal string used to denote the
|
|
3
|
+
# position in an ordered sequence such as 1st, 2nd, 3rd, 4th.
|
|
4
|
+
#
|
|
5
|
+
# 1.ordinalize # => "1st"
|
|
6
|
+
# 2.ordinalize # => "2nd"
|
|
7
|
+
# 1002.ordinalize # => "1002nd"
|
|
8
|
+
# 1003.ordinalize # => "1003rd"
|
|
9
|
+
# -11.ordinalize # => "-11th"
|
|
10
|
+
# -1001.ordinalize # => "-1001st"
|
|
11
|
+
def ordinalize
|
|
12
|
+
MotionSupport::Inflector.ordinalize(self)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Ordinal returns the suffix used to denote the position
|
|
16
|
+
# in an ordered sequence such as 1st, 2nd, 3rd, 4th.
|
|
17
|
+
#
|
|
18
|
+
# 1.ordinal # => "st"
|
|
19
|
+
# 2.ordinal # => "nd"
|
|
20
|
+
# 1002.ordinal # => "nd"
|
|
21
|
+
# 1003.ordinal # => "rd"
|
|
22
|
+
# -11.ordinal # => "th"
|
|
23
|
+
# -1001.ordinal # => "st"
|
|
24
|
+
def ordinal
|
|
25
|
+
MotionSupport::Inflector.ordinal(self)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class Integer
|
|
2
|
+
# Check whether the integer is evenly divisible by the argument.
|
|
3
|
+
#
|
|
4
|
+
# 0.multiple_of?(0) #=> true
|
|
5
|
+
# 6.multiple_of?(5) #=> false
|
|
6
|
+
# 10.multiple_of?(2) #=> true
|
|
7
|
+
def multiple_of?(number)
|
|
8
|
+
number != 0 ? self % number == 0 : zero?
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
class Integer
|
|
2
|
+
# Enables the use of time calculations and declarations, like <tt>45.minutes +
|
|
3
|
+
# 2.hours + 4.years</tt>.
|
|
4
|
+
#
|
|
5
|
+
# These methods use Time#advance for precise date calculations when using
|
|
6
|
+
# <tt>from_now</tt>, +ago+, etc. as well as adding or subtracting their
|
|
7
|
+
# results from a Time object.
|
|
8
|
+
#
|
|
9
|
+
# # equivalent to Time.now.advance(months: 1)
|
|
10
|
+
# 1.month.from_now
|
|
11
|
+
#
|
|
12
|
+
# # equivalent to Time.now.advance(years: 2)
|
|
13
|
+
# 2.years.from_now
|
|
14
|
+
#
|
|
15
|
+
# # equivalent to Time.now.advance(months: 4, years: 5)
|
|
16
|
+
# (4.months + 5.years).from_now
|
|
17
|
+
#
|
|
18
|
+
# While these methods provide precise calculation when used as in the examples
|
|
19
|
+
# above, care should be taken to note that this is not true if the result of
|
|
20
|
+
# +months+, +years+, etc is converted before use:
|
|
21
|
+
#
|
|
22
|
+
# # equivalent to 30.days.to_i.from_now
|
|
23
|
+
# 1.month.to_i.from_now
|
|
24
|
+
#
|
|
25
|
+
# # equivalent to 365.25.days.to_f.from_now
|
|
26
|
+
# 1.year.to_f.from_now
|
|
27
|
+
#
|
|
28
|
+
# In such cases, Ruby's core
|
|
29
|
+
# Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
|
|
30
|
+
# Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
|
|
31
|
+
# date and time arithmetic.
|
|
32
|
+
def months
|
|
33
|
+
MotionSupport::Duration.new(self * 30.days, [[:months, self]])
|
|
34
|
+
end
|
|
35
|
+
alias :month :months
|
|
36
|
+
|
|
37
|
+
def years
|
|
38
|
+
MotionSupport::Duration.new(self * 365.25.days, [[:years, self]])
|
|
39
|
+
end
|
|
40
|
+
alias :year :years
|
|
41
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
class Module
|
|
2
|
+
# Encapsulates the common pattern of:
|
|
3
|
+
#
|
|
4
|
+
# alias_method :foo_without_feature, :foo
|
|
5
|
+
# alias_method :foo, :foo_with_feature
|
|
6
|
+
#
|
|
7
|
+
# With this, you simply do:
|
|
8
|
+
#
|
|
9
|
+
# alias_method_chain :foo, :feature
|
|
10
|
+
#
|
|
11
|
+
# And both aliases are set up for you.
|
|
12
|
+
#
|
|
13
|
+
# Query and bang methods (foo?, foo!) keep the same punctuation:
|
|
14
|
+
#
|
|
15
|
+
# alias_method_chain :foo?, :feature
|
|
16
|
+
#
|
|
17
|
+
# is equivalent to
|
|
18
|
+
#
|
|
19
|
+
# alias_method :foo_without_feature?, :foo?
|
|
20
|
+
# alias_method :foo?, :foo_with_feature?
|
|
21
|
+
#
|
|
22
|
+
# so you can safely chain foo, foo?, and foo! with the same feature.
|
|
23
|
+
def alias_method_chain(target, feature)
|
|
24
|
+
# Strip out punctuation on predicates or bang methods since
|
|
25
|
+
# e.g. target?_without_feature is not a valid method name.
|
|
26
|
+
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
|
|
27
|
+
yield(aliased_target, punctuation) if block_given?
|
|
28
|
+
|
|
29
|
+
with_method = "#{aliased_target}_with_#{feature}#{punctuation}"
|
|
30
|
+
without_method = "#{aliased_target}_without_#{feature}#{punctuation}"
|
|
31
|
+
|
|
32
|
+
alias_method without_method, target
|
|
33
|
+
alias_method target, with_method
|
|
34
|
+
|
|
35
|
+
case
|
|
36
|
+
when public_method_defined?(without_method)
|
|
37
|
+
public target
|
|
38
|
+
when protected_method_defined?(without_method)
|
|
39
|
+
protected target
|
|
40
|
+
when private_method_defined?(without_method)
|
|
41
|
+
private target
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Allows you to make aliases for attributes, which includes
|
|
46
|
+
# getter, setter, and query methods.
|
|
47
|
+
#
|
|
48
|
+
# class Content < ActiveRecord::Base
|
|
49
|
+
# # has a title attribute
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
# class Email < Content
|
|
53
|
+
# alias_attribute :subject, :title
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
# e = Email.find(1)
|
|
57
|
+
# e.title # => "Superstars"
|
|
58
|
+
# e.subject # => "Superstars"
|
|
59
|
+
# e.subject? # => true
|
|
60
|
+
# e.subject = "Megastars"
|
|
61
|
+
# e.title # => "Megastars"
|
|
62
|
+
def alias_attribute(new_name, old_name)
|
|
63
|
+
module_exec do
|
|
64
|
+
define_method(new_name) { self.send(old_name) } # def subject; self.title; end
|
|
65
|
+
define_method("#{new_name}?") { self.send("#{old_name}?") } # def subject?; self.title?; end
|
|
66
|
+
define_method("#{new_name}=") { |v| self.send("#{old_name}=", v) } # def subject=(v); self.title = v; end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class Module
|
|
2
|
+
# A module may or may not have a name.
|
|
3
|
+
#
|
|
4
|
+
# module M; end
|
|
5
|
+
# M.name # => "M"
|
|
6
|
+
#
|
|
7
|
+
# m = Module.new
|
|
8
|
+
# m.name # => nil
|
|
9
|
+
#
|
|
10
|
+
# A module gets a name when it is first assigned to a constant. Either
|
|
11
|
+
# via the +module+ or +class+ keyword or by an explicit assignment:
|
|
12
|
+
#
|
|
13
|
+
# m = Module.new # creates an anonymous module
|
|
14
|
+
# M = m # => m gets a name here as a side-effect
|
|
15
|
+
# m.name # => "M"
|
|
16
|
+
def anonymous?
|
|
17
|
+
name.nil?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class Module
|
|
2
|
+
# Declares an attribute reader backed by an internally-named instance variable.
|
|
3
|
+
def attr_internal_reader(*attrs)
|
|
4
|
+
attrs.each {|attr_name| attr_internal_define(attr_name, :reader)}
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Declares an attribute writer backed by an internally-named instance variable.
|
|
8
|
+
def attr_internal_writer(*attrs)
|
|
9
|
+
attrs.each {|attr_name| attr_internal_define(attr_name, :writer)}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Declares an attribute reader and writer backed by an internally-named instance
|
|
13
|
+
# variable.
|
|
14
|
+
def attr_internal_accessor(*attrs)
|
|
15
|
+
attr_internal_reader(*attrs)
|
|
16
|
+
attr_internal_writer(*attrs)
|
|
17
|
+
end
|
|
18
|
+
alias_method :attr_internal, :attr_internal_accessor
|
|
19
|
+
|
|
20
|
+
class << self; attr_accessor :attr_internal_naming_format end
|
|
21
|
+
self.attr_internal_naming_format = '@_%s'
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
def attr_internal_ivar_name(attr)
|
|
25
|
+
Module.attr_internal_naming_format % attr
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def attr_internal_define(attr_name, type)
|
|
29
|
+
internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '')
|
|
30
|
+
class_eval do # class_eval is necessary on 1.9 or else the methods a made private
|
|
31
|
+
# use native attr_* methods as they are faster on some Ruby implementations
|
|
32
|
+
send("attr_#{type}", internal_name)
|
|
33
|
+
end
|
|
34
|
+
attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer
|
|
35
|
+
alias_method attr_name, internal_name
|
|
36
|
+
remove_method internal_name
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
class Module
|
|
2
|
+
def mattr_reader(*syms)
|
|
3
|
+
receiver = self
|
|
4
|
+
options = syms.extract_options!
|
|
5
|
+
syms.each do |sym|
|
|
6
|
+
raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
|
|
7
|
+
class_exec do
|
|
8
|
+
unless class_variable_defined?("@@#{sym}")
|
|
9
|
+
class_variable_set("@@#{sym}", nil)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
define_singleton_method sym do
|
|
13
|
+
class_variable_get("@@#{sym}")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
unless options[:instance_reader] == false || options[:instance_accessor] == false
|
|
18
|
+
class_exec do
|
|
19
|
+
define_method sym do
|
|
20
|
+
receiver.class_variable_get("@@#{sym}")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def mattr_writer(*syms)
|
|
28
|
+
receiver = self
|
|
29
|
+
options = syms.extract_options!
|
|
30
|
+
syms.each do |sym|
|
|
31
|
+
raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
|
|
32
|
+
class_exec do
|
|
33
|
+
define_singleton_method "#{sym}=" do |obj|
|
|
34
|
+
class_variable_set("@@#{sym}", obj)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
unless options[:instance_writer] == false || options[:instance_accessor] == false
|
|
39
|
+
class_exec do
|
|
40
|
+
define_method "#{sym}=" do |obj|
|
|
41
|
+
receiver.class_variable_set("@@#{sym}", obj)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Extends the module object with module and instance accessors for class attributes,
|
|
49
|
+
# just like the native attr* accessors for instance attributes.
|
|
50
|
+
#
|
|
51
|
+
# module AppConfiguration
|
|
52
|
+
# mattr_accessor :google_api_key
|
|
53
|
+
#
|
|
54
|
+
# self.google_api_key = "123456789"
|
|
55
|
+
# end
|
|
56
|
+
#
|
|
57
|
+
# AppConfiguration.google_api_key # => "123456789"
|
|
58
|
+
# AppConfiguration.google_api_key = "overriding the api key!"
|
|
59
|
+
# AppConfiguration.google_api_key # => "overriding the api key!"
|
|
60
|
+
def mattr_accessor(*syms)
|
|
61
|
+
mattr_reader(*syms)
|
|
62
|
+
mattr_writer(*syms)
|
|
63
|
+
end
|
|
64
|
+
end
|