hashie 4.1.0 → 5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +138 -53
- data/CONTRIBUTING.md +38 -1
- data/LICENSE +1 -1
- data/README.md +920 -205
- data/Rakefile +32 -1
- data/UPGRADING.md +56 -0
- data/hashie.gemspec +10 -5
- data/lib/hashie/array.rb +15 -12
- data/lib/hashie/clash.rb +59 -4
- data/lib/hashie/dash.rb +160 -32
- data/lib/hashie/extensions/active_support/core_ext/hash.rb +38 -0
- data/lib/hashie/extensions/array/pretty_inspect.rb +20 -0
- data/lib/hashie/extensions/block_access.rb +71 -0
- data/lib/hashie/extensions/coercion.rb +180 -26
- data/lib/hashie/extensions/dash/coercion.rb +34 -3
- data/lib/hashie/extensions/dash/indifferent_access.rb +90 -3
- data/lib/hashie/extensions/dash/lazy.rb +170 -0
- data/lib/hashie/extensions/dash/predefined_values.rb +148 -0
- data/lib/hashie/extensions/dash/property_translation.rb +138 -15
- data/lib/hashie/extensions/deep_fetch.rb +21 -9
- data/lib/hashie/extensions/deep_find.rb +20 -0
- data/lib/hashie/extensions/deep_grep.rb +69 -0
- data/lib/hashie/extensions/deep_locate.rb +16 -29
- data/lib/hashie/extensions/deep_merge.rb +16 -0
- data/lib/hashie/extensions/deep_merge_initializer.rb +42 -0
- data/lib/hashie/extensions/ignore_undeclared.rb +24 -7
- data/lib/hashie/extensions/indifferent_access.rb +124 -5
- data/lib/hashie/extensions/kernel_coercion.rb +58 -0
- data/lib/hashie/extensions/key_conflict_warning.rb +39 -4
- data/lib/hashie/extensions/key_conversion.rb +9 -0
- data/lib/hashie/extensions/mash/define_accessors.rb +71 -8
- data/lib/hashie/extensions/mash/keep_original_keys.rb +13 -9
- data/lib/hashie/extensions/mash/permissive_respond_to.rb +2 -0
- data/lib/hashie/extensions/mash/required_keys.rb +111 -0
- data/lib/hashie/extensions/mash/safe_assignment.rb +22 -4
- data/lib/hashie/extensions/mash/strict_key_access.rb +65 -0
- data/lib/hashie/extensions/mash/symbolize_keys.rb +11 -7
- data/lib/hashie/extensions/mash/transform_keys.rb +86 -0
- data/lib/hashie/extensions/mash/underscore_keys.rb +32 -0
- data/lib/hashie/extensions/merge_initializer.rb +9 -0
- data/lib/hashie/extensions/method_access.rb +94 -8
- data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +28 -35
- data/lib/hashie/extensions/parsers/yaml_parser.rb +81 -0
- data/lib/hashie/extensions/persistable.rb +137 -0
- data/lib/hashie/extensions/pretty_inspect.rb +14 -0
- data/lib/hashie/extensions/ruby_version.rb +18 -1
- data/lib/hashie/extensions/ruby_version_check.rb +17 -0
- data/lib/hashie/extensions/strict_key_access.rb +59 -5
- data/lib/hashie/extensions/stringify_keys.rb +72 -17
- data/lib/hashie/extensions/symbolize_keys.rb +84 -17
- data/lib/hashie/hash.rb +18 -2
- data/lib/hashie/logger.rb +18 -1
- data/lib/hashie/mash.rb +314 -82
- data/lib/hashie/railtie.rb +27 -1
- data/lib/hashie/rash.rb +64 -4
- data/lib/hashie/trash.rb +8 -0
- data/lib/hashie/utils.rb +17 -6
- data/lib/hashie/version.rb +3 -1
- data/lib/hashie.rb +46 -0
- metadata +22 -9
|
@@ -1,12 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Hashie
|
|
2
4
|
module Extensions
|
|
3
5
|
module Dash
|
|
6
|
+
# Allows string and symbol access to declared Dash properties.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# klass = Class.new(Hashie::Dash) do
|
|
10
|
+
# include Hashie::Extensions::Dash::IndifferentAccess
|
|
11
|
+
# property :name
|
|
12
|
+
# end
|
|
13
|
+
# klass.new(name: 'Ada')['name'] # => 'Ada'
|
|
4
14
|
module IndifferentAccess
|
|
15
|
+
# @example Include indifferent access in a Dash subclass
|
|
16
|
+
# klass = Class.new(Hashie::Dash) do
|
|
17
|
+
# include Hashie::Extensions::Dash::IndifferentAccess
|
|
18
|
+
# property :name
|
|
19
|
+
# end
|
|
20
|
+
# klass.property?('name') # => true
|
|
5
21
|
def self.included(base)
|
|
6
22
|
base.extend ClassMethods
|
|
7
23
|
base.send :include, Hashie::Extensions::IndifferentAccess
|
|
8
24
|
end
|
|
9
25
|
|
|
26
|
+
# @example Add Dash-specific class methods when needed
|
|
27
|
+
# klass = Class.new(Hashie::Dash) { property :name }
|
|
28
|
+
# Hashie::Extensions::Dash::IndifferentAccess.maybe_extend(klass)
|
|
29
|
+
# klass.property?('name') # => true
|
|
30
|
+
# klass.singleton_class.included_modules.include?(Hashie::Extensions::Dash::IndifferentAccess::ClassMethods) # => true
|
|
10
31
|
def self.maybe_extend(base)
|
|
11
32
|
return unless requires_class_methods?(base)
|
|
12
33
|
|
|
@@ -19,25 +40,91 @@ module Hashie
|
|
|
19
40
|
end
|
|
20
41
|
private_class_method :requires_class_methods?
|
|
21
42
|
|
|
43
|
+
# @example Include defaults under string keys in the returned hash
|
|
44
|
+
# klass = Class.new(Hashie::Dash) do
|
|
45
|
+
# include Hashie::Extensions::Dash::IndifferentAccess
|
|
46
|
+
# property :name, default: 'Ada'
|
|
47
|
+
# property :age
|
|
48
|
+
# end
|
|
49
|
+
# klass.new(age: 37).to_h # => {'name' => 'Ada', 'age' => 37}
|
|
50
|
+
def to_h
|
|
51
|
+
defaults = self.class.properties.to_h do |prop|
|
|
52
|
+
[Hashie::Extensions::IndifferentAccess.convert_key(prop), self.class.defaults[prop]]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
defaults.merge(self)
|
|
56
|
+
end
|
|
57
|
+
# @example
|
|
58
|
+
# klass = Class.new(Hashie::Dash) do
|
|
59
|
+
# include Hashie::Extensions::Dash::IndifferentAccess
|
|
60
|
+
# property :name, default: 'Ada'
|
|
61
|
+
# end
|
|
62
|
+
# klass.new.to_hash # => {'name' => 'Ada'}
|
|
63
|
+
alias to_hash to_h
|
|
64
|
+
|
|
65
|
+
# Class-level property and translation lookup with indifferent keys.
|
|
66
|
+
#
|
|
67
|
+
# @example
|
|
68
|
+
# klass = Class.new(Hashie::Dash) do
|
|
69
|
+
# include Hashie::Extensions::Dash::IndifferentAccess
|
|
70
|
+
# property :name
|
|
71
|
+
# end
|
|
72
|
+
# klass.property?('name') # => true
|
|
22
73
|
module ClassMethods
|
|
23
74
|
# Check to see if the specified property has already been
|
|
24
75
|
# defined.
|
|
76
|
+
#
|
|
77
|
+
# @example
|
|
78
|
+
# klass = Class.new(Hashie::Dash) do
|
|
79
|
+
# include Hashie::Extensions::Dash::IndifferentAccess
|
|
80
|
+
# property :name
|
|
81
|
+
# end
|
|
82
|
+
# klass.property?('name') # => true
|
|
83
|
+
# klass.property?(:missing) # => false
|
|
25
84
|
def property?(name)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
85
|
+
targets = if translation_for?(name)
|
|
86
|
+
Array(translations.find { |key, _| key.to_s == name.to_s }.last)
|
|
87
|
+
else
|
|
88
|
+
[name]
|
|
89
|
+
end
|
|
90
|
+
targets.all? do |target|
|
|
91
|
+
properties.any? { |property| property.to_s == target.to_s }
|
|
92
|
+
end
|
|
29
93
|
end
|
|
30
94
|
|
|
95
|
+
# @example Look up a translation with a string or symbol
|
|
96
|
+
# klass = Class.new(Hashie::Dash) do
|
|
97
|
+
# include Hashie::Extensions::Dash::PropertyTranslation
|
|
98
|
+
# include Hashie::Extensions::Dash::IndifferentAccess
|
|
99
|
+
# property :name, from: :fullName
|
|
100
|
+
# end
|
|
101
|
+
# klass.translation_exists?('fullName') # => true
|
|
102
|
+
# klass.translation_exists?(:missing) # => false
|
|
31
103
|
def translation_exists?(name)
|
|
32
104
|
name = name.to_s
|
|
33
105
|
!!translations.keys.find { |key| key.to_s == name }
|
|
34
106
|
end
|
|
35
107
|
|
|
108
|
+
# @example Apply a transformer declared with a symbol key
|
|
109
|
+
# klass = Class.new(Hashie::Dash) do
|
|
110
|
+
# include Hashie::Extensions::Dash::PropertyTranslation
|
|
111
|
+
# include Hashie::Extensions::Dash::IndifferentAccess
|
|
112
|
+
# property :age, transform_with: ->(value) { value.to_i }
|
|
113
|
+
# end
|
|
114
|
+
# klass.transformed_property('age', '37') # => 37
|
|
36
115
|
def transformed_property(property_name, value)
|
|
37
116
|
transform = transforms[property_name] || transforms[property_name.to_sym]
|
|
38
117
|
transform.call(value)
|
|
39
118
|
end
|
|
40
119
|
|
|
120
|
+
# @example Find a transformer regardless of key type
|
|
121
|
+
# klass = Class.new(Hashie::Dash) do
|
|
122
|
+
# include Hashie::Extensions::Dash::PropertyTranslation
|
|
123
|
+
# include Hashie::Extensions::Dash::IndifferentAccess
|
|
124
|
+
# property :age, transform_with: ->(value) { value.to_i }
|
|
125
|
+
# end
|
|
126
|
+
# klass.transformation_exists?('age') # => true
|
|
127
|
+
# klass.transformation_exists?(:missing) # => false
|
|
41
128
|
def transformation_exists?(name)
|
|
42
129
|
name = name.to_s
|
|
43
130
|
!!transforms.keys.find { |key| key.to_s == name }
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hashie
|
|
4
|
+
module Extensions
|
|
5
|
+
module Dash
|
|
6
|
+
# Loads declared properties on demand from an initializer block.
|
|
7
|
+
# Include after other Dash extensions. Hash enumeration only sees loaded
|
|
8
|
+
# entries; to_h loads every declared property.
|
|
9
|
+
#
|
|
10
|
+
# @example Load and memoize a property
|
|
11
|
+
# klass = Class.new(Hashie::Dash) do
|
|
12
|
+
# include Hashie::Extensions::Dash::Lazy
|
|
13
|
+
# property :name
|
|
14
|
+
# end
|
|
15
|
+
# calls = []
|
|
16
|
+
# dash = klass.new { |key| calls << key; 'Ada' }
|
|
17
|
+
# calls # => []
|
|
18
|
+
# dash.name # => 'Ada'
|
|
19
|
+
# dash.name # => 'Ada'
|
|
20
|
+
# calls # => [:name]
|
|
21
|
+
module Lazy
|
|
22
|
+
# @example Hash initialization without a loader retains normal behavior
|
|
23
|
+
# klass = Class.new(Hashie::Dash) do
|
|
24
|
+
# include Hashie::Extensions::Dash::Lazy
|
|
25
|
+
# property :name, required: true
|
|
26
|
+
# end
|
|
27
|
+
# klass.new(name: 'Ada').name # => 'Ada'
|
|
28
|
+
def initialize(attributes = {}, &loader)
|
|
29
|
+
@lazy_loader = loader
|
|
30
|
+
@lazy_loading = []
|
|
31
|
+
super(attributes, &nil)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @example Read a property without loading its neighbors
|
|
35
|
+
# klass = Class.new(Hashie::Dash) do
|
|
36
|
+
# include Hashie::Extensions::Dash::Lazy
|
|
37
|
+
# property :name
|
|
38
|
+
# property :age
|
|
39
|
+
# end
|
|
40
|
+
# dash = klass.new { |key| { name: 'Ada', age: 37 }[key] }
|
|
41
|
+
# dash[:name] # => 'Ada'
|
|
42
|
+
# dash.keys # => [:name]
|
|
43
|
+
def [](property, &block)
|
|
44
|
+
return super unless @lazy_loader
|
|
45
|
+
|
|
46
|
+
assert_property_exists!(property)
|
|
47
|
+
canonical_property = lazy_property(property)
|
|
48
|
+
load_property(canonical_property) unless key?(property)
|
|
49
|
+
value = super
|
|
50
|
+
assert_property_required!(canonical_property, value)
|
|
51
|
+
value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @example Fetch a declared value through its loader
|
|
55
|
+
# klass = Class.new(Hashie::Dash) do
|
|
56
|
+
# include Hashie::Extensions::Dash::Lazy
|
|
57
|
+
# property :name
|
|
58
|
+
# end
|
|
59
|
+
# klass.new { |_key| 'Ada' }.fetch(:name) # => 'Ada'
|
|
60
|
+
def fetch(property, *args, &block)
|
|
61
|
+
self[property] if @lazy_loader && self.class.property?(property)
|
|
62
|
+
super
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @example Load the first property before digging into its value
|
|
66
|
+
# klass = Class.new(Hashie::Dash) do
|
|
67
|
+
# include Hashie::Extensions::Dash::Lazy
|
|
68
|
+
# property :user
|
|
69
|
+
# end
|
|
70
|
+
# klass.new { |_key| { name: 'Ada' } }.dig(:user, :name) # => 'Ada'
|
|
71
|
+
def dig(property, *path)
|
|
72
|
+
self[property] if @lazy_loader && self.class.property?(property)
|
|
73
|
+
super
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @example Convert all declared properties to a plain hash
|
|
77
|
+
# klass = Class.new(Hashie::Dash) do
|
|
78
|
+
# include Hashie::Extensions::Dash::Lazy
|
|
79
|
+
# property :name
|
|
80
|
+
# end
|
|
81
|
+
# klass.new { |_key| 'Ada' }.to_h # => { name: 'Ada' }
|
|
82
|
+
def to_h
|
|
83
|
+
self.class.properties.each { |property| self[property] } if @lazy_loader
|
|
84
|
+
super
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @example Convert all declared properties for hash coercion
|
|
88
|
+
# klass = Class.new(Hashie::Dash) do
|
|
89
|
+
# include Hashie::Extensions::Dash::Lazy
|
|
90
|
+
# property :name
|
|
91
|
+
# end
|
|
92
|
+
# klass.new { |_key| 'Ada' }.to_hash # => { name: 'Ada' }
|
|
93
|
+
alias to_hash to_h
|
|
94
|
+
|
|
95
|
+
# @example Freezing resolves pending values before preventing mutation
|
|
96
|
+
# klass = Class.new(Hashie::Dash) do
|
|
97
|
+
# include Hashie::Extensions::Dash::Lazy
|
|
98
|
+
# property :name
|
|
99
|
+
# end
|
|
100
|
+
# klass.new { |_key| 'Ada' }.freeze.name # => 'Ada'
|
|
101
|
+
def freeze
|
|
102
|
+
to_h if @lazy_loader && !frozen?
|
|
103
|
+
super
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def initialize_attributes(attributes)
|
|
109
|
+
return super unless @lazy_loader
|
|
110
|
+
|
|
111
|
+
@lazy_attributes = attributes || {}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def assert_required_attributes_set!
|
|
115
|
+
super unless @lazy_loader
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def initialize_copy(other)
|
|
119
|
+
super
|
|
120
|
+
@lazy_loading = []
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def lazy_property(property)
|
|
124
|
+
return property if self.class.properties.include?(property)
|
|
125
|
+
|
|
126
|
+
self.class.properties.find { |key| key.to_s == property.to_s } || property
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def lazy_attribute_key(property)
|
|
130
|
+
return property if @lazy_attributes.key?(property)
|
|
131
|
+
return unless respond_to?(:convert_key)
|
|
132
|
+
|
|
133
|
+
converted = convert_key(property)
|
|
134
|
+
converted if @lazy_attributes.key?(converted)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def load_property(property)
|
|
138
|
+
raise ArgumentError, "circular lazy loading for property `#{property}`" if @lazy_loading.include?(property)
|
|
139
|
+
|
|
140
|
+
@lazy_loading << property
|
|
141
|
+
begin
|
|
142
|
+
source = if self.class.respond_to?(:inverse_translations)
|
|
143
|
+
self.class.inverse_translations.fetch(property, property)
|
|
144
|
+
else
|
|
145
|
+
property
|
|
146
|
+
end
|
|
147
|
+
attribute = lazy_attribute_key(property)
|
|
148
|
+
if attribute
|
|
149
|
+
source = property
|
|
150
|
+
value = @lazy_attributes[attribute]
|
|
151
|
+
else
|
|
152
|
+
attribute = lazy_attribute_key(source)
|
|
153
|
+
value = attribute ? @lazy_attributes[attribute] : @lazy_loader.call(source)
|
|
154
|
+
end
|
|
155
|
+
if value.nil?
|
|
156
|
+
value = self.class.defaults[property].dup
|
|
157
|
+
value = value.arity == 1 ? value.call(self) : value.call if value.is_a?(Proc)
|
|
158
|
+
elsif source != property
|
|
159
|
+
translator = self.class.translations_hash[source][property]
|
|
160
|
+
value = translator.call(value) if translator.respond_to?(:call)
|
|
161
|
+
end
|
|
162
|
+
self[property] = value
|
|
163
|
+
ensure
|
|
164
|
+
@lazy_loading.pop
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hashie
|
|
4
|
+
module Extensions
|
|
5
|
+
module Dash
|
|
6
|
+
# Extends a Dash with the ability to accept only predefined values on a property.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
#
|
|
10
|
+
# class PersonHash < Hashie::Dash
|
|
11
|
+
# include Hashie::Extensions::Dash::PredefinedValues
|
|
12
|
+
#
|
|
13
|
+
# property :gender, values: [:male, :female, :prefer_not_to_say]
|
|
14
|
+
# property :age, values: (0..150) # a Range
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# expect { PersonHash.new(gender: :male, age: -1) }
|
|
18
|
+
# .to raise_error(ArgumentError, /value `-1` is not allowed for `age`/)
|
|
19
|
+
# PersonHash.new(gender: :male, age: 42).age # => 42
|
|
20
|
+
module PredefinedValues
|
|
21
|
+
# @example Install predefined-value declarations
|
|
22
|
+
# klass = Class.new(Hashie::Dash) do
|
|
23
|
+
# include Hashie::Extensions::Dash::PredefinedValues
|
|
24
|
+
# end
|
|
25
|
+
# klass.values_for_properties # => {}
|
|
26
|
+
def self.included(base)
|
|
27
|
+
base.instance_variable_set(:@values_for_properties, {})
|
|
28
|
+
base.extend(ClassMethods)
|
|
29
|
+
base.include(InstanceMethods)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Declares and inherits permitted values for properties.
|
|
33
|
+
#
|
|
34
|
+
# @example
|
|
35
|
+
# klass = Class.new(Hashie::Dash) do
|
|
36
|
+
# include Hashie::Extensions::Dash::PredefinedValues
|
|
37
|
+
# property :status, values: [:open, :closed]
|
|
38
|
+
# end
|
|
39
|
+
# klass.values_for_properties # => {status: [:open, :closed]}
|
|
40
|
+
module ClassMethods
|
|
41
|
+
# @example Read the constraints for declared properties
|
|
42
|
+
# klass = Class.new(Hashie::Dash) do
|
|
43
|
+
# include Hashie::Extensions::Dash::PredefinedValues
|
|
44
|
+
# property :age, values: (0..150)
|
|
45
|
+
# end
|
|
46
|
+
# klass.values_for_properties # => {age: (0..150)}
|
|
47
|
+
attr_reader :values_for_properties
|
|
48
|
+
|
|
49
|
+
# @example Inherit constraints without sharing the constraint map
|
|
50
|
+
# parent = Class.new(Hashie::Dash) do
|
|
51
|
+
# include Hashie::Extensions::Dash::PredefinedValues
|
|
52
|
+
# property :status, values: [:open, :closed]
|
|
53
|
+
# end
|
|
54
|
+
# child = Class.new(parent) { property :age, values: (0..150) }
|
|
55
|
+
# child.values_for_properties.keys # => [:status, :age]
|
|
56
|
+
# parent.values_for_properties.keys # => [:status]
|
|
57
|
+
def inherited(klass)
|
|
58
|
+
super
|
|
59
|
+
klass.instance_variable_set(:@values_for_properties, values_for_properties.dup)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @example Accept an array or range of values
|
|
63
|
+
# klass = Class.new(Hashie::Dash) do
|
|
64
|
+
# include Hashie::Extensions::Dash::PredefinedValues
|
|
65
|
+
# property :status, values: [:open, :closed]
|
|
66
|
+
# property :age, values: (0..150)
|
|
67
|
+
# end
|
|
68
|
+
# klass.new(status: :open, age: 42).age # => 42
|
|
69
|
+
# expect { klass.new(status: :unknown) }.to raise_error(ArgumentError, /status/)
|
|
70
|
+
def property(property_name, options = {})
|
|
71
|
+
super
|
|
72
|
+
|
|
73
|
+
return unless (predefined_values = options[:values])
|
|
74
|
+
|
|
75
|
+
assert_predefined_values!(predefined_values)
|
|
76
|
+
set_predefined_values(property_name, predefined_values)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def assert_predefined_values!(predefined_values)
|
|
82
|
+
return if supported_type?(predefined_values)
|
|
83
|
+
|
|
84
|
+
raise ArgumentError, %(`values` accepts an `Array` or a `Range`)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def supported_type?(predefined_values)
|
|
88
|
+
[::Array, ::Range].any? { |klass| predefined_values.is_a?(klass) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def set_predefined_values(property_name, predefined_values)
|
|
92
|
+
@values_for_properties[property_name] = predefined_values
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Validates property constraints when constructing a Dash.
|
|
97
|
+
#
|
|
98
|
+
# @example
|
|
99
|
+
# klass = Class.new(Hashie::Dash) do
|
|
100
|
+
# include Hashie::Extensions::Dash::PredefinedValues
|
|
101
|
+
# property :status, values: [:open, :closed]
|
|
102
|
+
# end
|
|
103
|
+
# klass.new(status: :open).status # => :open
|
|
104
|
+
module InstanceMethods
|
|
105
|
+
# @example Reject values outside the declared set
|
|
106
|
+
# klass = Class.new(Hashie::Dash) do
|
|
107
|
+
# include Hashie::Extensions::Dash::PredefinedValues
|
|
108
|
+
# property :status, values: [:open, :closed]
|
|
109
|
+
# end
|
|
110
|
+
# expect { klass.new(status: :unknown) }.to raise_error(ArgumentError, /status/)
|
|
111
|
+
# klass.new.status # => nil
|
|
112
|
+
def initialize(*)
|
|
113
|
+
super
|
|
114
|
+
|
|
115
|
+
assert_property_values!
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def assert_property_values!
|
|
121
|
+
self.class.values_for_properties.each_key do |property|
|
|
122
|
+
value = send(property)
|
|
123
|
+
|
|
124
|
+
allowed_values = values_for_properties(property)
|
|
125
|
+
fail_property_value_error!(property, value, allowed_values) if value && !allowed_values.include?(value)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def fail_property_value_error!(property, value, allowed_values)
|
|
130
|
+
expected_values = if allowed_values.is_a?(::Range)
|
|
131
|
+
"a value in `#{allowed_values}`"
|
|
132
|
+
else
|
|
133
|
+
"one of `#{allowed_values.inspect}`"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
raise ArgumentError,
|
|
137
|
+
"value `#{value.inspect}` is not allowed for `#{property}`; " \
|
|
138
|
+
"expected #{expected_values}"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def values_for_properties(property)
|
|
142
|
+
self.class.values_for_properties[property]
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|