motion-support 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|