hashie 2.1.2 → 4.1.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 +7 -0
- data/CHANGELOG.md +524 -59
- data/CONTRIBUTING.md +24 -7
- data/README.md +781 -90
- data/Rakefile +19 -2
- data/UPGRADING.md +245 -0
- data/hashie.gemspec +21 -13
- data/lib/hashie.rb +60 -21
- data/lib/hashie/array.rb +21 -0
- data/lib/hashie/clash.rb +24 -12
- data/lib/hashie/dash.rb +96 -33
- data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
- data/lib/hashie/extensions/array/pretty_inspect.rb +19 -0
- data/lib/hashie/extensions/coercion.rb +124 -18
- data/lib/hashie/extensions/dash/coercion.rb +25 -0
- data/lib/hashie/extensions/dash/indifferent_access.rb +56 -0
- data/lib/hashie/extensions/dash/property_translation.rb +191 -0
- data/lib/hashie/extensions/deep_fetch.rb +7 -5
- data/lib/hashie/extensions/deep_find.rb +69 -0
- data/lib/hashie/extensions/deep_locate.rb +113 -0
- data/lib/hashie/extensions/deep_merge.rb +35 -12
- data/lib/hashie/extensions/ignore_undeclared.rb +11 -5
- data/lib/hashie/extensions/indifferent_access.rb +28 -16
- data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
- data/lib/hashie/extensions/key_conversion.rb +0 -82
- data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
- data/lib/hashie/extensions/mash/keep_original_keys.rb +53 -0
- data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
- data/lib/hashie/extensions/mash/safe_assignment.rb +18 -0
- data/lib/hashie/extensions/mash/symbolize_keys.rb +38 -0
- data/lib/hashie/extensions/method_access.rb +154 -11
- data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +48 -0
- data/lib/hashie/extensions/pretty_inspect.rb +19 -0
- data/lib/hashie/extensions/ruby_version.rb +60 -0
- data/lib/hashie/extensions/ruby_version_check.rb +21 -0
- data/lib/hashie/extensions/strict_key_access.rb +77 -0
- data/lib/hashie/extensions/stringify_keys.rb +71 -0
- data/lib/hashie/extensions/symbolize_keys.rb +71 -0
- data/lib/hashie/hash.rb +27 -8
- data/lib/hashie/logger.rb +18 -0
- data/lib/hashie/mash.rb +235 -57
- data/lib/hashie/railtie.rb +21 -0
- data/lib/hashie/rash.rb +40 -16
- data/lib/hashie/trash.rb +2 -88
- data/lib/hashie/utils.rb +44 -0
- data/lib/hashie/version.rb +1 -1
- metadata +42 -81
- data/.gitignore +0 -9
- data/.rspec +0 -2
- data/.rubocop.yml +0 -36
- data/.travis.yml +0 -15
- data/Gemfile +0 -11
- data/Guardfile +0 -5
- data/lib/hashie/hash_extensions.rb +0 -47
- data/spec/hashie/clash_spec.rb +0 -48
- data/spec/hashie/dash_spec.rb +0 -338
- data/spec/hashie/extensions/coercion_spec.rb +0 -156
- data/spec/hashie/extensions/deep_fetch_spec.rb +0 -70
- data/spec/hashie/extensions/deep_merge_spec.rb +0 -22
- data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -23
- data/spec/hashie/extensions/indifferent_access_spec.rb +0 -152
- data/spec/hashie/extensions/key_conversion_spec.rb +0 -103
- data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
- data/spec/hashie/extensions/method_access_spec.rb +0 -121
- data/spec/hashie/hash_spec.rb +0 -66
- data/spec/hashie/mash_spec.rb +0 -467
- data/spec/hashie/rash_spec.rb +0 -44
- data/spec/hashie/trash_spec.rb +0 -193
- data/spec/hashie/version_spec.rb +0 -7
- data/spec/spec.opts +0 -3
- data/spec/spec_helper.rb +0 -8
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'hashie/extensions/ruby_version'
|
2
|
+
|
3
|
+
module Hashie
|
4
|
+
module Extensions
|
5
|
+
module RubyVersionCheck
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def with_minimum_ruby(version)
|
12
|
+
yield if with_minimum_ruby?(version)
|
13
|
+
end
|
14
|
+
|
15
|
+
def with_minimum_ruby?(version)
|
16
|
+
RubyVersion.new(RUBY_VERSION) >= RubyVersion.new(version)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Hashie
|
2
|
+
module Extensions
|
3
|
+
# SRP: This extension will fail an error whenever a key is accessed
|
4
|
+
# that does not exist in the hash.
|
5
|
+
#
|
6
|
+
# EXAMPLE:
|
7
|
+
#
|
8
|
+
# class StrictKeyAccessHash < Hash
|
9
|
+
# include Hashie::Extensions::StrictKeyAccess
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# >> hash = StrictKeyAccessHash[foo: "bar"]
|
13
|
+
# => {:foo=>"bar"}
|
14
|
+
# >> hash[:foo]
|
15
|
+
# => "bar"
|
16
|
+
# >> hash[:cow]
|
17
|
+
# KeyError: key not found: :cow
|
18
|
+
#
|
19
|
+
# NOTE: For googlers coming from Python to Ruby, this extension makes a Hash
|
20
|
+
# behave more like a "Dictionary".
|
21
|
+
#
|
22
|
+
module StrictKeyAccess
|
23
|
+
class DefaultError < StandardError
|
24
|
+
def initialize
|
25
|
+
super('Setting or using a default with Hashie::Extensions::StrictKeyAccess'\
|
26
|
+
' does not make sense'
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# NOTE: Defaults don't make any sense with a StrictKeyAccess.
|
32
|
+
# NOTE: When key lookup fails a KeyError is raised.
|
33
|
+
#
|
34
|
+
# Normal:
|
35
|
+
#
|
36
|
+
# >> a = Hash.new(123)
|
37
|
+
# => {}
|
38
|
+
# >> a["noes"]
|
39
|
+
# => 123
|
40
|
+
#
|
41
|
+
# With StrictKeyAccess:
|
42
|
+
#
|
43
|
+
# >> a = StrictKeyAccessHash.new(123)
|
44
|
+
# => {}
|
45
|
+
# >> a["noes"]
|
46
|
+
# KeyError: key not found: "noes"
|
47
|
+
#
|
48
|
+
def [](key)
|
49
|
+
fetch(key)
|
50
|
+
end
|
51
|
+
|
52
|
+
def default(_ = nil)
|
53
|
+
raise DefaultError
|
54
|
+
end
|
55
|
+
|
56
|
+
def default=(_)
|
57
|
+
raise DefaultError
|
58
|
+
end
|
59
|
+
|
60
|
+
def default_proc
|
61
|
+
raise DefaultError
|
62
|
+
end
|
63
|
+
|
64
|
+
def default_proc=(_)
|
65
|
+
raise DefaultError
|
66
|
+
end
|
67
|
+
|
68
|
+
def key(value)
|
69
|
+
super.tap do |result|
|
70
|
+
if result.nil? && (!key?(result) || self[result] != value)
|
71
|
+
raise KeyError, "key not found with value of #{value.inspect}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Hashie
|
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
|
+
StringifyKeys.stringify_keys!(self)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return a new hash with all keys converted
|
16
|
+
# to strings.
|
17
|
+
def stringify_keys
|
18
|
+
StringifyKeys.stringify_keys(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
# Stringify all keys recursively within nested
|
23
|
+
# hashes and arrays.
|
24
|
+
# @api private
|
25
|
+
def stringify_keys_recursively!(object)
|
26
|
+
case object
|
27
|
+
when self.class
|
28
|
+
stringify_keys!(object)
|
29
|
+
when ::Array
|
30
|
+
object.each do |i|
|
31
|
+
stringify_keys_recursively!(i)
|
32
|
+
end
|
33
|
+
when ::Hash
|
34
|
+
stringify_keys!(object)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Convert all keys in the hash to strings.
|
39
|
+
#
|
40
|
+
# @param [::Hash] hash
|
41
|
+
# @example
|
42
|
+
# test = {:abc => 'def'}
|
43
|
+
# test.stringify_keys!
|
44
|
+
# test # => {'abc' => 'def'}
|
45
|
+
def stringify_keys!(hash)
|
46
|
+
hash.extend(Hashie::Extensions::StringifyKeys) unless hash.respond_to?(:stringify_keys!)
|
47
|
+
hash.keys.each do |k| # rubocop:disable Performance/HashEachMethods
|
48
|
+
stringify_keys_recursively!(hash[k])
|
49
|
+
hash[k.to_s] = hash.delete(k)
|
50
|
+
end
|
51
|
+
hash
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return a copy of hash with all keys converted
|
55
|
+
# to strings.
|
56
|
+
# @param [::Hash] hash
|
57
|
+
def stringify_keys(hash)
|
58
|
+
copy = hash.dup
|
59
|
+
copy.extend(Hashie::Extensions::StringifyKeys) unless copy.respond_to?(:stringify_keys!)
|
60
|
+
copy.tap do |new_hash|
|
61
|
+
stringify_keys!(new_hash)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class << self
|
67
|
+
include ClassMethods
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Hashie
|
2
|
+
module Extensions
|
3
|
+
module SymbolizeKeys
|
4
|
+
# Convert all keys in the hash to symbols.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# test = {'abc' => 'def'}
|
8
|
+
# test.symbolize_keys!
|
9
|
+
# test # => {:abc => 'def'}
|
10
|
+
def symbolize_keys!
|
11
|
+
SymbolizeKeys.symbolize_keys!(self)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return a new hash with all keys converted
|
16
|
+
# to symbols.
|
17
|
+
def symbolize_keys
|
18
|
+
SymbolizeKeys.symbolize_keys(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
# Symbolize all keys recursively within nested
|
23
|
+
# hashes and arrays.
|
24
|
+
# @api private
|
25
|
+
def symbolize_keys_recursively!(object)
|
26
|
+
case object
|
27
|
+
when self.class
|
28
|
+
symbolize_keys!(object)
|
29
|
+
when ::Array
|
30
|
+
object.each do |i|
|
31
|
+
symbolize_keys_recursively!(i)
|
32
|
+
end
|
33
|
+
when ::Hash
|
34
|
+
symbolize_keys!(object)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Convert all keys in hash to symbols.
|
39
|
+
#
|
40
|
+
# @param [Hash] hash
|
41
|
+
# @example
|
42
|
+
# test = {'abc' => 'def'}
|
43
|
+
# Hashie.symbolize_keys! test
|
44
|
+
# test # => {:abc => 'def'}
|
45
|
+
def symbolize_keys!(hash)
|
46
|
+
hash.extend(Hashie::Extensions::SymbolizeKeys) unless hash.respond_to?(:symbolize_keys!)
|
47
|
+
hash.keys.each do |k| # rubocop:disable Performance/HashEachMethods
|
48
|
+
symbolize_keys_recursively!(hash[k])
|
49
|
+
hash[k.to_sym] = hash.delete(k)
|
50
|
+
end
|
51
|
+
hash
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return a copy of hash with all keys converted
|
55
|
+
# to symbols.
|
56
|
+
# @param [::Hash] hash
|
57
|
+
def symbolize_keys(hash)
|
58
|
+
copy = hash.dup
|
59
|
+
copy.extend(Hashie::Extensions::SymbolizeKeys) unless copy.respond_to?(:symbolize_keys!)
|
60
|
+
copy.tap do |new_hash|
|
61
|
+
symbolize_keys!(new_hash)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class << self
|
67
|
+
include ClassMethods
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/hashie/hash.rb
CHANGED
@@ -1,25 +1,38 @@
|
|
1
|
-
require 'hashie/
|
1
|
+
require 'hashie/extensions/stringify_keys'
|
2
|
+
require 'hashie/extensions/pretty_inspect'
|
2
3
|
|
3
4
|
module Hashie
|
4
5
|
# A Hashie Hash is simply a Hash that has convenience
|
5
6
|
# functions baked in such as stringify_keys that may
|
6
7
|
# not be available in all libraries.
|
7
8
|
class Hash < ::Hash
|
8
|
-
include
|
9
|
+
include Hashie::Extensions::PrettyInspect
|
10
|
+
include Hashie::Extensions::StringifyKeys
|
11
|
+
|
12
|
+
# Convert this hash into a Mash
|
13
|
+
def to_mash
|
14
|
+
::Hashie::Mash.new(self)
|
15
|
+
end
|
9
16
|
|
10
17
|
# Converts a mash back to a hash (with stringified or symbolized keys)
|
11
18
|
def to_hash(options = {})
|
12
19
|
out = {}
|
13
|
-
|
14
|
-
assignment_key =
|
15
|
-
|
20
|
+
each_key do |k|
|
21
|
+
assignment_key =
|
22
|
+
if options[:stringify_keys]
|
23
|
+
k.to_s
|
24
|
+
elsif options[:symbolize_keys]
|
25
|
+
k.to_s.to_sym
|
26
|
+
else
|
27
|
+
k
|
28
|
+
end
|
16
29
|
if self[k].is_a?(Array)
|
17
30
|
out[assignment_key] ||= []
|
18
31
|
self[k].each do |array_object|
|
19
|
-
out[assignment_key] << (
|
32
|
+
out[assignment_key] << maybe_convert_to_hash(array_object, options)
|
20
33
|
end
|
21
34
|
else
|
22
|
-
out[assignment_key] = (
|
35
|
+
out[assignment_key] = maybe_convert_to_hash(self[k], options)
|
23
36
|
end
|
24
37
|
end
|
25
38
|
out
|
@@ -32,8 +45,14 @@ module Hashie
|
|
32
45
|
|
33
46
|
private
|
34
47
|
|
48
|
+
def maybe_convert_to_hash(object, options)
|
49
|
+
return object unless object.is_a?(Hash) || object.respond_to?(:to_hash)
|
50
|
+
|
51
|
+
flexibly_convert_to_hash(object, options)
|
52
|
+
end
|
53
|
+
|
35
54
|
def flexibly_convert_to_hash(object, options = {})
|
36
|
-
if object.method(:to_hash).arity
|
55
|
+
if object.method(:to_hash).arity.zero?
|
37
56
|
object.to_hash
|
38
57
|
else
|
39
58
|
object.to_hash(options)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Hashie
|
4
|
+
# The logger that Hashie uses for reporting errors.
|
5
|
+
#
|
6
|
+
# @return [Logger]
|
7
|
+
def self.logger
|
8
|
+
@logger ||= Logger.new(STDOUT)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Sets the logger that Hashie uses for reporting errors.
|
12
|
+
#
|
13
|
+
# @param logger [Logger] The logger to set as Hashie's logger.
|
14
|
+
# @return [void]
|
15
|
+
def self.logger=(logger)
|
16
|
+
@logger = logger
|
17
|
+
end
|
18
|
+
end
|
data/lib/hashie/mash.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
require 'hashie/hash'
|
2
|
+
require 'hashie/array'
|
3
|
+
require 'hashie/utils'
|
4
|
+
require 'hashie/logger'
|
5
|
+
require 'hashie/extensions/key_conflict_warning'
|
2
6
|
|
3
7
|
module Hashie
|
4
8
|
# Mash allows you to create pseudo-objects that have method-like
|
@@ -12,9 +16,12 @@ module Hashie
|
|
12
16
|
#
|
13
17
|
# * No punctuation: Returns the value of the hash for that key, or nil if none exists.
|
14
18
|
# * Assignment (<tt>=</tt>): Sets the attribute of the given method name.
|
15
|
-
# *
|
16
|
-
#
|
17
|
-
# *
|
19
|
+
# * Truthiness (<tt>?</tt>): Returns true or false depending on the truthiness of
|
20
|
+
# the attribute, or false if the key is not set.
|
21
|
+
# * Bang (<tt>!</tt>): Forces the existence of this key, used for deep Mashes. Think of it
|
22
|
+
# as "touch" for mashes.
|
23
|
+
# * Under Bang (<tt>_</tt>): Like Bang, but returns a new Mash rather than creating a key.
|
24
|
+
# Used to test existance in deep Mashes.
|
18
25
|
#
|
19
26
|
# == Basic Example
|
20
27
|
#
|
@@ -55,9 +62,37 @@ module Hashie
|
|
55
62
|
# mash.author # => <Mash>
|
56
63
|
#
|
57
64
|
class Mash < Hash
|
58
|
-
|
59
|
-
include Hashie::
|
60
|
-
|
65
|
+
include Hashie::Extensions::PrettyInspect
|
66
|
+
include Hashie::Extensions::RubyVersionCheck
|
67
|
+
extend Hashie::Extensions::KeyConflictWarning
|
68
|
+
|
69
|
+
ALLOWED_SUFFIXES = %w[? ! = _].freeze
|
70
|
+
|
71
|
+
def self.load(path, options = {})
|
72
|
+
@_mashes ||= new
|
73
|
+
|
74
|
+
return @_mashes[path] if @_mashes.key?(path)
|
75
|
+
raise ArgumentError, "The following file doesn't exist: #{path}" unless File.file?(path)
|
76
|
+
|
77
|
+
options = options.dup
|
78
|
+
parser = options.delete(:parser) { Hashie::Extensions::Parsers::YamlErbParser }
|
79
|
+
@_mashes[path] = new(parser.perform(path, options)).freeze
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_module(mash_method_name = :settings)
|
83
|
+
mash = self
|
84
|
+
Module.new do |m|
|
85
|
+
m.send :define_method, mash_method_name.to_sym do
|
86
|
+
mash
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def with_accessors!
|
92
|
+
extend Hashie::Extensions::Mash::DefineAccessors
|
93
|
+
end
|
94
|
+
|
95
|
+
alias to_s inspect
|
61
96
|
|
62
97
|
# If you pass in an existing hash, it will
|
63
98
|
# convert it to a Mash including recursively
|
@@ -68,22 +103,28 @@ module Hashie
|
|
68
103
|
default ? super(default) : super(&blk)
|
69
104
|
end
|
70
105
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
106
|
+
# Creates a new anonymous subclass with key conflict
|
107
|
+
# warnings disabled. You may pass an array of method
|
108
|
+
# symbols to restrict the disabled warnings to.
|
109
|
+
# Hashie::Mash.quiet.new(hash) all warnings disabled.
|
110
|
+
# Hashie::Mash.quiet(:zip).new(hash) only zip warning
|
111
|
+
# is disabled.
|
112
|
+
def self.quiet(*method_keys)
|
113
|
+
@memoized_classes ||= {}
|
114
|
+
@memoized_classes[method_keys] ||= Class.new(self) do
|
115
|
+
disable_warnings(*method_keys)
|
116
|
+
end
|
75
117
|
end
|
76
118
|
|
77
|
-
|
78
|
-
self['type']
|
79
|
-
end
|
119
|
+
class << self; alias [] new; end
|
80
120
|
|
81
|
-
|
82
|
-
|
121
|
+
alias regular_reader []
|
122
|
+
alias regular_writer []=
|
83
123
|
|
84
124
|
# Retrieves an attribute set in the Mash. Will convert
|
85
125
|
# any key passed in to a string before retrieving.
|
86
126
|
def custom_reader(key)
|
127
|
+
default_proc.call(self, key) if default_proc && !key?(key)
|
87
128
|
value = regular_reader(convert_key(key))
|
88
129
|
yield value if block_given?
|
89
130
|
value
|
@@ -93,11 +134,14 @@ module Hashie
|
|
93
134
|
# a string before it is set, and Hashes will be converted
|
94
135
|
# into Mashes for nesting purposes.
|
95
136
|
def custom_writer(key, value, convert = true) #:nodoc:
|
96
|
-
|
137
|
+
key_as_symbol = (key = convert_key(key)).to_sym
|
138
|
+
|
139
|
+
log_built_in_message(key_as_symbol) if log_collision?(key_as_symbol)
|
140
|
+
regular_writer(key, convert ? convert_value(value) : value)
|
97
141
|
end
|
98
142
|
|
99
|
-
|
100
|
-
|
143
|
+
alias [] custom_reader
|
144
|
+
alias []= custom_writer
|
101
145
|
|
102
146
|
# This is the bang method reader, it will return a new Mash
|
103
147
|
# if there isn't a value already assigned to the key requested.
|
@@ -126,44 +170,98 @@ module Hashie
|
|
126
170
|
super(convert_key(key))
|
127
171
|
end
|
128
172
|
|
129
|
-
|
173
|
+
def values_at(*keys)
|
174
|
+
super(*keys.map { |key| convert_key(key) })
|
175
|
+
end
|
176
|
+
|
177
|
+
# Returns a new instance of the class it was called on, using its keys as
|
178
|
+
# values, and its values as keys. The new values and keys will always be
|
179
|
+
# strings.
|
180
|
+
def invert
|
181
|
+
self.class.new(super)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Returns a new instance of the class it was called on, containing elements
|
185
|
+
# for which the given block returns false.
|
186
|
+
def reject(&blk)
|
187
|
+
self.class.new(super(&blk))
|
188
|
+
end
|
189
|
+
|
190
|
+
# Returns a new instance of the class it was called on, containing elements
|
191
|
+
# for which the given block returns true.
|
192
|
+
def select(&blk)
|
193
|
+
self.class.new(super(&blk))
|
194
|
+
end
|
195
|
+
|
196
|
+
alias regular_dup dup
|
130
197
|
# Duplicates the current mash as a new mash.
|
131
198
|
def dup
|
132
|
-
self.class.new(self, default)
|
199
|
+
self.class.new(self, default, &default_proc)
|
133
200
|
end
|
134
201
|
|
202
|
+
alias regular_key? key?
|
135
203
|
def key?(key)
|
136
204
|
super(convert_key(key))
|
137
205
|
end
|
138
|
-
|
139
|
-
|
140
|
-
|
206
|
+
alias has_key? key?
|
207
|
+
alias include? key?
|
208
|
+
alias member? key?
|
209
|
+
|
210
|
+
if with_minimum_ruby?('2.6.0')
|
211
|
+
# Performs a deep_update on a duplicate of the
|
212
|
+
# current mash.
|
213
|
+
def deep_merge(*other_hashes, &blk)
|
214
|
+
dup.deep_update(*other_hashes, &blk)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Recursively merges this mash with the passed
|
218
|
+
# in hash, merging each hash in the hierarchy.
|
219
|
+
def deep_update(*other_hashes, &blk)
|
220
|
+
other_hashes.each do |other_hash|
|
221
|
+
_deep_update(other_hash, &blk)
|
222
|
+
end
|
223
|
+
self
|
224
|
+
end
|
225
|
+
else
|
226
|
+
# Performs a deep_update on a duplicate of the
|
227
|
+
# current mash.
|
228
|
+
def deep_merge(other_hash, &blk)
|
229
|
+
dup.deep_update(other_hash, &blk)
|
230
|
+
end
|
141
231
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
232
|
+
# Recursively merges this mash with the passed
|
233
|
+
# in hash, merging each hash in the hierarchy.
|
234
|
+
def deep_update(other_hash, &blk)
|
235
|
+
_deep_update(other_hash, &blk)
|
236
|
+
self
|
237
|
+
end
|
146
238
|
end
|
147
|
-
alias_method :merge, :deep_merge
|
148
239
|
|
149
|
-
#
|
150
|
-
#
|
151
|
-
|
240
|
+
# Alias these lexically so they get the correctly defined
|
241
|
+
# #deep_merge and #deep_update based on ruby version.
|
242
|
+
alias merge deep_merge
|
243
|
+
alias deep_merge! deep_update
|
244
|
+
alias update deep_update
|
245
|
+
alias merge! update
|
246
|
+
|
247
|
+
def _deep_update(other_hash, &blk)
|
152
248
|
other_hash.each_pair do |k, v|
|
153
249
|
key = convert_key(k)
|
154
|
-
if
|
250
|
+
if v.is_a?(::Hash) && key?(key) && regular_reader(key).is_a?(Mash)
|
155
251
|
custom_reader(key).deep_update(v, &blk)
|
156
252
|
else
|
157
253
|
value = convert_value(v, true)
|
158
|
-
value = convert_value(
|
254
|
+
value = convert_value(yield(key, self[k], value), true) if blk && key?(k)
|
159
255
|
custom_writer(key, value, false)
|
160
256
|
end
|
161
257
|
end
|
162
|
-
self
|
163
258
|
end
|
164
|
-
|
165
|
-
|
166
|
-
|
259
|
+
private :_deep_update
|
260
|
+
|
261
|
+
# Assigns a value to a key
|
262
|
+
def assign_property(name, value)
|
263
|
+
self[name] = value
|
264
|
+
end
|
167
265
|
|
168
266
|
# Performs a shallow_update on a duplicate of the current mash
|
169
267
|
def shallow_merge(other_hash)
|
@@ -185,11 +283,14 @@ module Hashie
|
|
185
283
|
self
|
186
284
|
end
|
187
285
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
286
|
+
def respond_to_missing?(method_name, *args)
|
287
|
+
return true if key?(method_name)
|
288
|
+
suffix = method_suffix(method_name)
|
289
|
+
if suffix
|
290
|
+
true
|
291
|
+
else
|
292
|
+
super
|
293
|
+
end
|
193
294
|
end
|
194
295
|
|
195
296
|
def prefix_method?(method_name)
|
@@ -197,26 +298,78 @@ module Hashie
|
|
197
298
|
method_name.end_with?(*ALLOWED_SUFFIXES) && key?(method_name.chop)
|
198
299
|
end
|
199
300
|
|
200
|
-
def method_missing(method_name, *args, &blk)
|
301
|
+
def method_missing(method_name, *args, &blk) # rubocop:disable Style/MethodMissing
|
201
302
|
return self.[](method_name, &blk) if key?(method_name)
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
underbang_reader(match[1])
|
303
|
+
name, suffix = method_name_and_suffix(method_name)
|
304
|
+
case suffix
|
305
|
+
when '='.freeze
|
306
|
+
assign_property(name, args.first)
|
307
|
+
when '?'.freeze
|
308
|
+
!!self[name]
|
309
|
+
when '!'.freeze
|
310
|
+
initializing_reader(name)
|
311
|
+
when '_'.freeze
|
312
|
+
underbang_reader(name)
|
213
313
|
else
|
214
|
-
|
314
|
+
self[method_name]
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
# play nice with ActiveSupport Array#extract_options!
|
319
|
+
def extractable_options?
|
320
|
+
true
|
321
|
+
end
|
322
|
+
|
323
|
+
# another ActiveSupport method, see issue #270
|
324
|
+
def reverse_merge(other_hash)
|
325
|
+
self.class.new(other_hash).merge(self)
|
326
|
+
end
|
327
|
+
|
328
|
+
with_minimum_ruby('2.3.0') do
|
329
|
+
def dig(*keys)
|
330
|
+
super(*keys.map { |key| convert_key(key) })
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
with_minimum_ruby('2.4.0') do
|
335
|
+
def transform_values(&blk)
|
336
|
+
self.class.new(super(&blk))
|
337
|
+
end
|
338
|
+
|
339
|
+
# Returns a new instance of the class it was called on, with nil values
|
340
|
+
# removed.
|
341
|
+
def compact
|
342
|
+
self.class.new(super)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
with_minimum_ruby('2.5.0') do
|
347
|
+
def slice(*keys)
|
348
|
+
string_keys = keys.map { |key| convert_key(key) }
|
349
|
+
self.class.new(super(*string_keys))
|
350
|
+
end
|
351
|
+
|
352
|
+
def transform_keys(&blk)
|
353
|
+
self.class.new(super(&blk))
|
215
354
|
end
|
216
355
|
end
|
217
356
|
|
218
357
|
protected
|
219
358
|
|
359
|
+
def method_name_and_suffix(method_name)
|
360
|
+
method_name = method_name.to_s
|
361
|
+
if method_name.end_with?(*ALLOWED_SUFFIXES)
|
362
|
+
[method_name[0..-2], method_name[-1]]
|
363
|
+
else
|
364
|
+
[method_name[0..-1], nil]
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
def method_suffix(method_name)
|
369
|
+
method_name = method_name.to_s
|
370
|
+
method_name[-1] if method_name.end_with?(*ALLOWED_SUFFIXES)
|
371
|
+
end
|
372
|
+
|
220
373
|
def convert_key(key) #:nodoc:
|
221
374
|
key.to_s
|
222
375
|
end
|
@@ -230,11 +383,36 @@ module Hashie
|
|
230
383
|
when ::Hash
|
231
384
|
val = val.dup if duping
|
232
385
|
self.class.new(val)
|
233
|
-
when Array
|
234
|
-
val.map { |e| convert_value(e) }
|
386
|
+
when ::Array
|
387
|
+
Array.new(val.map { |e| convert_value(e) })
|
235
388
|
else
|
236
389
|
val
|
237
390
|
end
|
238
391
|
end
|
392
|
+
|
393
|
+
private
|
394
|
+
|
395
|
+
def log_built_in_message(method_key)
|
396
|
+
return if self.class.disable_warnings?(method_key)
|
397
|
+
|
398
|
+
method_information = Hashie::Utils.method_information(method(method_key))
|
399
|
+
|
400
|
+
Hashie.logger.warn(
|
401
|
+
'You are setting a key that conflicts with a built-in method ' \
|
402
|
+
"#{self.class}##{method_key} #{method_information}. " \
|
403
|
+
'This can cause unexpected behavior when accessing the key as a ' \
|
404
|
+
'property. You can still access the key via the #[] method.'
|
405
|
+
)
|
406
|
+
end
|
407
|
+
|
408
|
+
def log_collision?(method_key)
|
409
|
+
return unless respond_to?(method_key)
|
410
|
+
|
411
|
+
_, suffix = method_name_and_suffix(method_key)
|
412
|
+
|
413
|
+
(!suffix || suffix == '='.freeze) &&
|
414
|
+
!self.class.disable_warnings?(method_key) &&
|
415
|
+
!(regular_key?(method_key) || regular_key?(method_key.to_s))
|
416
|
+
end
|
239
417
|
end
|
240
418
|
end
|