hashie 2.1.2 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +524 -59
  3. data/CONTRIBUTING.md +24 -7
  4. data/README.md +781 -90
  5. data/Rakefile +19 -2
  6. data/UPGRADING.md +245 -0
  7. data/hashie.gemspec +21 -13
  8. data/lib/hashie.rb +60 -21
  9. data/lib/hashie/array.rb +21 -0
  10. data/lib/hashie/clash.rb +24 -12
  11. data/lib/hashie/dash.rb +96 -33
  12. data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
  13. data/lib/hashie/extensions/array/pretty_inspect.rb +19 -0
  14. data/lib/hashie/extensions/coercion.rb +124 -18
  15. data/lib/hashie/extensions/dash/coercion.rb +25 -0
  16. data/lib/hashie/extensions/dash/indifferent_access.rb +56 -0
  17. data/lib/hashie/extensions/dash/property_translation.rb +191 -0
  18. data/lib/hashie/extensions/deep_fetch.rb +7 -5
  19. data/lib/hashie/extensions/deep_find.rb +69 -0
  20. data/lib/hashie/extensions/deep_locate.rb +113 -0
  21. data/lib/hashie/extensions/deep_merge.rb +35 -12
  22. data/lib/hashie/extensions/ignore_undeclared.rb +11 -5
  23. data/lib/hashie/extensions/indifferent_access.rb +28 -16
  24. data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
  25. data/lib/hashie/extensions/key_conversion.rb +0 -82
  26. data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
  27. data/lib/hashie/extensions/mash/keep_original_keys.rb +53 -0
  28. data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
  29. data/lib/hashie/extensions/mash/safe_assignment.rb +18 -0
  30. data/lib/hashie/extensions/mash/symbolize_keys.rb +38 -0
  31. data/lib/hashie/extensions/method_access.rb +154 -11
  32. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +48 -0
  33. data/lib/hashie/extensions/pretty_inspect.rb +19 -0
  34. data/lib/hashie/extensions/ruby_version.rb +60 -0
  35. data/lib/hashie/extensions/ruby_version_check.rb +21 -0
  36. data/lib/hashie/extensions/strict_key_access.rb +77 -0
  37. data/lib/hashie/extensions/stringify_keys.rb +71 -0
  38. data/lib/hashie/extensions/symbolize_keys.rb +71 -0
  39. data/lib/hashie/hash.rb +27 -8
  40. data/lib/hashie/logger.rb +18 -0
  41. data/lib/hashie/mash.rb +235 -57
  42. data/lib/hashie/railtie.rb +21 -0
  43. data/lib/hashie/rash.rb +40 -16
  44. data/lib/hashie/trash.rb +2 -88
  45. data/lib/hashie/utils.rb +44 -0
  46. data/lib/hashie/version.rb +1 -1
  47. metadata +42 -81
  48. data/.gitignore +0 -9
  49. data/.rspec +0 -2
  50. data/.rubocop.yml +0 -36
  51. data/.travis.yml +0 -15
  52. data/Gemfile +0 -11
  53. data/Guardfile +0 -5
  54. data/lib/hashie/hash_extensions.rb +0 -47
  55. data/spec/hashie/clash_spec.rb +0 -48
  56. data/spec/hashie/dash_spec.rb +0 -338
  57. data/spec/hashie/extensions/coercion_spec.rb +0 -156
  58. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -70
  59. data/spec/hashie/extensions/deep_merge_spec.rb +0 -22
  60. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -23
  61. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -152
  62. data/spec/hashie/extensions/key_conversion_spec.rb +0 -103
  63. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  64. data/spec/hashie/extensions/method_access_spec.rb +0 -121
  65. data/spec/hashie/hash_spec.rb +0 -66
  66. data/spec/hashie/mash_spec.rb +0 -467
  67. data/spec/hashie/rash_spec.rb +0 -44
  68. data/spec/hashie/trash_spec.rb +0 -193
  69. data/spec/hashie/version_spec.rb +0 -7
  70. data/spec/spec.opts +0 -3
  71. data/spec/spec_helper.rb +0 -8
@@ -2,28 +2,51 @@ module Hashie
2
2
  module Extensions
3
3
  module DeepMerge
4
4
  # Returns a new hash with +self+ and +other_hash+ merged recursively.
5
- def deep_merge(other_hash)
6
- dup.deep_merge!(other_hash)
5
+ def deep_merge(other_hash, &block)
6
+ copy = _deep_dup(self)
7
+ copy.extend(Hashie::Extensions::DeepMerge) unless copy.respond_to?(:deep_merge!)
8
+ copy.deep_merge!(other_hash, &block)
7
9
  end
8
10
 
9
11
  # Returns a new hash with +self+ and +other_hash+ merged recursively.
10
12
  # Modifies the receiver in place.
11
- def deep_merge!(other_hash)
12
- _recursive_merge(self, other_hash)
13
+ def deep_merge!(other_hash, &block)
14
+ return self unless other_hash.is_a?(::Hash)
15
+ _recursive_merge(self, other_hash, &block)
13
16
  self
14
17
  end
15
18
 
16
19
  private
17
20
 
18
- def _recursive_merge(hash, other_hash)
19
- if other_hash.is_a?(::Hash) && hash.is_a?(::Hash)
20
- other_hash.each do |k, v|
21
- hash[k] = hash.key?(k) ? _recursive_merge(hash[k], v) : v
22
- end
23
- hash
24
- else
25
- other_hash
21
+ def _deep_dup(hash)
22
+ copy = hash.dup
23
+
24
+ copy.each do |key, value|
25
+ copy[key] =
26
+ if value.is_a?(::Hash)
27
+ _deep_dup(value)
28
+ else
29
+ Hashie::Utils.safe_dup(value)
30
+ end
31
+ end
32
+
33
+ copy
34
+ end
35
+
36
+ def _recursive_merge(hash, other_hash, &block)
37
+ other_hash.each do |k, v|
38
+ hash[k] =
39
+ if hash.key?(k) && hash[k].is_a?(::Hash) && v.is_a?(::Hash)
40
+ _recursive_merge(hash[k], v, &block)
41
+ elsif v.is_a?(::Hash)
42
+ _recursive_merge({}, v, &block)
43
+ elsif hash.key?(k) && block_given?
44
+ yield(k, hash[k], v)
45
+ else
46
+ v.respond_to?(:deep_dup) ? v.deep_dup : v
47
+ end
26
48
  end
49
+ hash
27
50
  end
28
51
  end
29
52
  end
@@ -5,7 +5,7 @@ module Hashie
5
5
  # raising an error. This is useful when using a Trash to
6
6
  # capture a subset of a larger hash.
7
7
  #
8
- # Note that attempting to retrieve an undeclared property
8
+ # Note that attempting to retrieve or set an undeclared property
9
9
  # will still raise a NoMethodError, even if a value for
10
10
  # that property was provided at initialization.
11
11
  #
@@ -30,11 +30,17 @@ module Hashie
30
30
  # p.email # => NoMethodError
31
31
  module IgnoreUndeclared
32
32
  def initialize_attributes(attributes)
33
+ return unless attributes
34
+ klass = self.class
35
+ translations = klass.respond_to?(:translations) && klass.translations
33
36
  attributes.each_pair do |att, value|
34
- if self.class.property?(att) || (self.class.respond_to?(:translations) && self.class.translations.include?(att.to_sym))
35
- self[att] = value
36
- end
37
- end if attributes
37
+ next unless klass.property?(att) || (translations && translations.include?(att))
38
+ self[att] = value
39
+ end
40
+ end
41
+
42
+ def property_exists?(property)
43
+ self.class.property?(property)
38
44
  end
39
45
  end
40
46
  end
@@ -24,16 +24,18 @@ module Hashie
24
24
  #
25
25
  module IndifferentAccess
26
26
  def self.included(base)
27
+ Hashie::Extensions::Dash::IndifferentAccess.maybe_extend(base)
28
+
27
29
  base.class_eval do
28
- alias_method :regular_writer, :[]=
30
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
29
31
  alias_method :[]=, :indifferent_writer
30
32
  alias_method :store, :indifferent_writer
31
- %w(default update replace fetch delete key? values_at).each do |m|
32
- alias_method "regular_#{m}", m
33
+ %w[default update replace fetch delete key? values_at].each do |m|
34
+ alias_method "regular_#{m}", m unless method_defined?("regular_#{m}")
33
35
  alias_method m, "indifferent_#{m}"
34
36
  end
35
37
 
36
- %w(include? member? has_key?).each do |key_alias|
38
+ %w[include? member? has_key?].each do |key_alias|
37
39
  alias_method key_alias, :indifferent_key?
38
40
  end
39
41
 
@@ -71,17 +73,17 @@ module Hashie
71
73
  # their proper indifferent state. Used when IndifferentAccess
72
74
  # is injecting itself into member hashes.
73
75
  def convert!
74
- keys.each do |k|
75
- regular_writer convert_key(k), convert_value(regular_delete(k))
76
+ keys.each do |k| # rubocop:disable Performance/HashEachMethods
77
+ regular_writer convert_key(k), indifferent_value(regular_delete(k))
76
78
  end
77
79
  self
78
80
  end
79
81
 
80
- def convert_value(value)
82
+ def indifferent_value(value)
81
83
  if hash_lacking_indifference?(value)
82
- IndifferentAccess.inject(value.dup)
84
+ IndifferentAccess.inject!(value)
83
85
  elsif value.is_a?(::Array)
84
- value.dup.replace(value.map { |e| convert_value(e) })
86
+ value.replace(value.map { |e| indifferent_value(e) })
85
87
  else
86
88
  value
87
89
  end
@@ -100,11 +102,11 @@ module Hashie
100
102
  end
101
103
 
102
104
  def indifferent_writer(key, value)
103
- regular_writer convert_key(key), convert_value(value)
105
+ regular_writer convert_key(key), indifferent_value(value)
104
106
  end
105
107
 
106
- def indifferent_fetch(key, *args)
107
- regular_fetch convert_key(key), *args
108
+ def indifferent_fetch(key, *args, &block)
109
+ regular_fetch convert_key(key), *args, &block
108
110
  end
109
111
 
110
112
  def indifferent_delete(key)
@@ -129,18 +131,28 @@ module Hashie
129
131
  self
130
132
  end
131
133
 
134
+ def merge(*args)
135
+ result = super
136
+ IndifferentAccess.inject!(result) if hash_lacking_indifference?(result)
137
+ result.convert!
138
+ end
139
+
140
+ def merge!(*)
141
+ super.convert!
142
+ end
143
+
132
144
  protected
133
145
 
134
146
  def hash_lacking_indifference?(other)
135
147
  other.is_a?(::Hash) &&
136
- !(other.respond_to?(:indifferent_access?) &&
137
- other.indifferent_access?)
148
+ !(other.respond_to?(:indifferent_access?) &&
149
+ other.indifferent_access?)
138
150
  end
139
151
 
140
152
  def hash_with_indifference?(other)
141
153
  other.is_a?(::Hash) &&
142
- other.respond_to?(:indifferent_access?) &&
143
- other.indifferent_access?
154
+ other.respond_to?(:indifferent_access?) &&
155
+ other.indifferent_access?
144
156
  end
145
157
  end
146
158
  end
@@ -0,0 +1,55 @@
1
+ module Hashie
2
+ module Extensions
3
+ module KeyConflictWarning
4
+ class CannotDisableMashWarnings < StandardError
5
+ def initialize
6
+ super(
7
+ 'You cannot disable warnings on the base Mash class. ' \
8
+ 'Please subclass the Mash and disable it in the subclass.'
9
+ )
10
+ end
11
+ end
12
+
13
+ # Disable the logging of warnings based on keys conflicting keys/methods
14
+ #
15
+ # @api semipublic
16
+ # @return [void]
17
+ def disable_warnings(*method_keys)
18
+ raise CannotDisableMashWarnings if self == Hashie::Mash
19
+ if method_keys.any?
20
+ disabled_warnings.concat(method_keys).tap(&:flatten!).uniq!
21
+ else
22
+ disabled_warnings.clear
23
+ end
24
+
25
+ @disable_warnings = true
26
+ end
27
+
28
+ # Checks whether this class disables warnings for conflicting keys/methods
29
+ #
30
+ # @api semipublic
31
+ # @return [Boolean]
32
+ def disable_warnings?(method_key = nil)
33
+ return disabled_warnings.include?(method_key) if disabled_warnings.any? && method_key
34
+ @disable_warnings ||= false
35
+ end
36
+
37
+ # Returns an array of methods that this class disables warnings for.
38
+ #
39
+ # @api semipublic
40
+ # @return [Boolean]
41
+ def disabled_warnings
42
+ @_disabled_warnings ||= []
43
+ end
44
+
45
+ # Inheritance hook that sets class configuration when inherited.
46
+ #
47
+ # @api semipublic
48
+ # @return [void]
49
+ def inherited(subclass)
50
+ super
51
+ subclass.disable_warnings(disabled_warnings) if disable_warnings?
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,87 +1,5 @@
1
1
  module Hashie
2
2
  module Extensions
3
- module StringifyKeys
4
- # Convert all keys in the hash to strings.
5
- #
6
- # @example
7
- # test = {:abc => 'def'}
8
- # test.stringify_keys!
9
- # test # => {'abc' => 'def'}
10
- def stringify_keys!
11
- keys.each do |k|
12
- stringify_keys_recursively!(self[k])
13
- self[k.to_s] = delete(k)
14
- end
15
- self
16
- end
17
-
18
- # Return a new hash with all keys converted
19
- # to strings.
20
- def stringify_keys
21
- dup.stringify_keys!
22
- end
23
-
24
- protected
25
-
26
- # Stringify all keys recursively within nested
27
- # hashes and arrays.
28
- def stringify_keys_recursively!(object)
29
- if self.class === object
30
- object.stringify_keys!
31
- elsif ::Array === object
32
- object.each do |i|
33
- stringify_keys_recursively!(i)
34
- end
35
- object
36
- elsif object.respond_to?(:stringify_keys!)
37
- object.stringify_keys!
38
- else
39
- object
40
- end
41
- end
42
- end
43
-
44
- module SymbolizeKeys
45
- # Convert all keys in the hash to symbols.
46
- #
47
- # @example
48
- # test = {'abc' => 'def'}
49
- # test.symbolize_keys!
50
- # test # => {:abc => 'def'}
51
- def symbolize_keys!
52
- keys.each do |k|
53
- symbolize_keys_recursively!(self[k])
54
- self[k.to_sym] = delete(k)
55
- end
56
- self
57
- end
58
-
59
- # Return a new hash with all keys converted
60
- # to symbols.
61
- def symbolize_keys
62
- dup.symbolize_keys!
63
- end
64
-
65
- protected
66
-
67
- # Symbolize all keys recursively within nested
68
- # hashes and arrays.
69
- def symbolize_keys_recursively!(object)
70
- if self.class === object
71
- object.symbolize_keys!
72
- elsif ::Array === object
73
- object.each do |i|
74
- symbolize_keys_recursively!(i)
75
- end
76
- object
77
- elsif object.respond_to?(:symbolize_keys!)
78
- object.symbolize_keys!
79
- else
80
- object
81
- end
82
- end
83
- end
84
-
85
3
  module KeyConversion
86
4
  def self.included(base)
87
5
  base.send :include, SymbolizeKeys
@@ -0,0 +1,90 @@
1
+ module Hashie
2
+ module Extensions
3
+ module Mash
4
+ module DefineAccessors
5
+ def self.included(klass)
6
+ klass.class_eval do
7
+ mod = Ext.new
8
+ include mod
9
+ end
10
+ end
11
+
12
+ def self.extended(obj)
13
+ included(obj.singleton_class)
14
+ end
15
+
16
+ class Ext < Module
17
+ def initialize
18
+ mod = self
19
+ define_method(:method_missing) do |method_name, *args, &block|
20
+ key, suffix = method_name_and_suffix(method_name)
21
+ case suffix
22
+ when '='.freeze
23
+ mod.define_writer(key, method_name)
24
+ when '?'.freeze
25
+ mod.define_predicate(key, method_name)
26
+ when '!'.freeze
27
+ mod.define_initializing_reader(key, method_name)
28
+ when '_'.freeze
29
+ mod.define_underbang_reader(key, method_name)
30
+ else
31
+ mod.define_reader(key, method_name)
32
+ end
33
+ send(method_name, *args, &block)
34
+ end
35
+ end
36
+
37
+ def define_reader(key, method_name)
38
+ define_method(method_name) do |&block|
39
+ if key? method_name
40
+ self.[](method_name, &block)
41
+ else
42
+ self.[](key, &block)
43
+ end
44
+ end
45
+ end
46
+
47
+ def define_writer(key, method_name)
48
+ define_method(method_name) do |value = nil|
49
+ if key? method_name
50
+ self.[](method_name, &proc)
51
+ else
52
+ assign_property(key, value)
53
+ end
54
+ end
55
+ end
56
+
57
+ def define_predicate(key, method_name)
58
+ define_method(method_name) do
59
+ if key? method_name
60
+ self.[](method_name, &proc)
61
+ else
62
+ !!self[key]
63
+ end
64
+ end
65
+ end
66
+
67
+ def define_initializing_reader(key, method_name)
68
+ define_method(method_name) do
69
+ if key? method_name
70
+ self.[](method_name, &proc)
71
+ else
72
+ initializing_reader(key)
73
+ end
74
+ end
75
+ end
76
+
77
+ def define_underbang_reader(key, method_name)
78
+ define_method(method_name) do
79
+ if key? method_name
80
+ self.[](key, &proc)
81
+ else
82
+ underbang_reader(key)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,53 @@
1
+ module Hashie
2
+ module Extensions
3
+ module Mash
4
+ # Overrides the indifferent access of a Mash to keep keys in the
5
+ # original format given to the Mash.
6
+ #
7
+ # @example
8
+ # class KeepingMash < Hashie::Mash
9
+ # include Hashie::Extensions::Mash::KeepOriginalKeys
10
+ # end
11
+ #
12
+ # mash = KeepingMash.new(:symbol_key => :symbol, 'string_key' => 'string')
13
+ # mash.to_hash #=> { :symbol_key => :symbol, 'string_key' => 'string' }
14
+ # mash['string_key'] == mash[:string_key] #=> true
15
+ # mash[:symbol_key] == mash['symbol_key'] #=> true
16
+ module KeepOriginalKeys
17
+ def self.included(descendant)
18
+ error_message = "#{descendant} is not a kind of Hashie::Mash"
19
+ raise ArgumentError, error_message unless descendant <= Hashie::Mash
20
+ end
21
+
22
+ private
23
+
24
+ # Converts the key when necessary to access the correct Mash key.
25
+ #
26
+ # @param [Object, String, Symbol] key the key to access.
27
+ # @return [Object] the value assigned to the key.
28
+ def convert_key(key)
29
+ if regular_key?(key)
30
+ key
31
+ elsif (converted_key = __convert(key)) && regular_key?(converted_key)
32
+ converted_key
33
+ else
34
+ key
35
+ end
36
+ end
37
+
38
+ # Converts symbol/string keys to their alternative formats, but leaves
39
+ # other keys alone.
40
+ #
41
+ # @param [Object, String, Symbol] key the key to convert.
42
+ # @return [Object, String, Symbol] the converted key.
43
+ def __convert(key)
44
+ case key
45
+ when Symbol then key.to_s
46
+ when String then key.to_sym
47
+ else key
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end