hashie 3.6.0 → 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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +228 -174
  3. data/CONTRIBUTING.md +13 -6
  4. data/README.md +127 -15
  5. data/UPGRADING.md +83 -7
  6. data/hashie.gemspec +13 -7
  7. data/lib/hashie.rb +21 -19
  8. data/lib/hashie/dash.rb +2 -1
  9. data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
  10. data/lib/hashie/extensions/coercion.rb +23 -16
  11. data/lib/hashie/extensions/dash/indifferent_access.rb +20 -1
  12. data/lib/hashie/extensions/dash/property_translation.rb +6 -3
  13. data/lib/hashie/extensions/deep_fetch.rb +4 -2
  14. data/lib/hashie/extensions/deep_find.rb +12 -3
  15. data/lib/hashie/extensions/deep_locate.rb +22 -7
  16. data/lib/hashie/extensions/deep_merge.rb +18 -1
  17. data/lib/hashie/extensions/indifferent_access.rb +1 -3
  18. data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
  19. data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
  20. data/lib/hashie/extensions/mash/keep_original_keys.rb +2 -1
  21. data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
  22. data/lib/hashie/extensions/mash/safe_assignment.rb +3 -1
  23. data/lib/hashie/extensions/method_access.rb +5 -2
  24. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +26 -4
  25. data/lib/hashie/extensions/ruby_version_check.rb +5 -1
  26. data/lib/hashie/extensions/strict_key_access.rb +8 -4
  27. data/lib/hashie/hash.rb +16 -9
  28. data/lib/hashie/mash.rb +114 -53
  29. data/lib/hashie/railtie.rb +7 -0
  30. data/lib/hashie/rash.rb +1 -1
  31. data/lib/hashie/utils.rb +28 -0
  32. data/lib/hashie/version.rb +1 -1
  33. metadata +19 -130
  34. data/spec/hashie/array_spec.rb +0 -29
  35. data/spec/hashie/clash_spec.rb +0 -70
  36. data/spec/hashie/dash_spec.rb +0 -598
  37. data/spec/hashie/extensions/autoload_spec.rb +0 -24
  38. data/spec/hashie/extensions/coercion_spec.rb +0 -639
  39. data/spec/hashie/extensions/dash/coercion_spec.rb +0 -13
  40. data/spec/hashie/extensions/dash/indifferent_access_spec.rb +0 -84
  41. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -97
  42. data/spec/hashie/extensions/deep_find_spec.rb +0 -138
  43. data/spec/hashie/extensions/deep_locate_spec.rb +0 -137
  44. data/spec/hashie/extensions/deep_merge_spec.rb +0 -70
  45. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -47
  46. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -295
  47. data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +0 -208
  48. data/spec/hashie/extensions/key_conversion_spec.rb +0 -12
  49. data/spec/hashie/extensions/mash/keep_original_keys_spec.rb +0 -46
  50. data/spec/hashie/extensions/mash/safe_assignment_spec.rb +0 -50
  51. data/spec/hashie/extensions/mash/symbolize_keys_spec.rb +0 -39
  52. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  53. data/spec/hashie/extensions/method_access_spec.rb +0 -226
  54. data/spec/hashie/extensions/strict_key_access_spec.rb +0 -110
  55. data/spec/hashie/extensions/stringify_keys_spec.rb +0 -124
  56. data/spec/hashie/extensions/symbolize_keys_spec.rb +0 -129
  57. data/spec/hashie/hash_spec.rb +0 -84
  58. data/spec/hashie/mash_spec.rb +0 -771
  59. data/spec/hashie/parsers/yaml_erb_parser_spec.rb +0 -46
  60. data/spec/hashie/rash_spec.rb +0 -83
  61. data/spec/hashie/trash_spec.rb +0 -328
  62. data/spec/hashie/utils_spec.rb +0 -25
  63. data/spec/hashie/version_spec.rb +0 -7
  64. data/spec/hashie_spec.rb +0 -13
  65. data/spec/integration/elasticsearch/integration_spec.rb +0 -40
  66. data/spec/integration/omniauth-oauth2/app.rb +0 -52
  67. data/spec/integration/omniauth-oauth2/integration_spec.rb +0 -26
  68. data/spec/integration/omniauth-oauth2/some_site.rb +0 -38
  69. data/spec/integration/omniauth/app.rb +0 -11
  70. data/spec/integration/omniauth/integration_spec.rb +0 -38
  71. data/spec/integration/rails-without-dependency/integration_spec.rb +0 -15
  72. data/spec/integration/rails/app.rb +0 -47
  73. data/spec/integration/rails/integration_spec.rb +0 -26
  74. data/spec/spec_helper.rb +0 -23
  75. data/spec/support/integration_specs.rb +0 -36
  76. data/spec/support/logger.rb +0 -24
  77. data/spec/support/module_context.rb +0 -11
  78. data/spec/support/ruby_version_check.rb +0 -6
@@ -7,6 +7,13 @@ begin
7
7
  initializer 'hashie.configure_logger', after: 'initialize_logger' do
8
8
  Hashie.logger = Rails.logger
9
9
  end
10
+
11
+ initializer 'hashie.patch_hash_except', after: 'load_active_support' do
12
+ if Rails::VERSION::MAJOR >= 6
13
+ require 'hashie/extensions/active_support/core_ext/hash'
14
+ Hashie::Mash.send(:include, Hashie::Extensions::ActiveSupport::CoreExt::Hash)
15
+ end
16
+ end
10
17
  end
11
18
  end
12
19
  rescue LoadError => e
data/lib/hashie/rash.rb CHANGED
@@ -117,7 +117,7 @@ module Hashie
117
117
  end
118
118
 
119
119
  when Regexp
120
- # Reverse operation: `rash[/regexp/]` returns all the hash's string keys which match the regexp
120
+ # Reverse operation: `rash[/regexp/]` returns all string keys matching the regexp
121
121
  @hash.each do |key, val|
122
122
  yield val if key.is_a?(String) && query =~ key
123
123
  end
data/lib/hashie/utils.rb CHANGED
@@ -12,5 +12,33 @@ module Hashie
12
12
  "defined in #{bound_method.owner}"
13
13
  end
14
14
  end
15
+
16
+ # Duplicates a value or returns the value when it is not duplicable
17
+ #
18
+ # @api public
19
+ #
20
+ # @param value [Object] the value to safely duplicate
21
+ # @return [Object] the duplicated value
22
+ def self.safe_dup(value)
23
+ case value
24
+ when Complex, FalseClass, NilClass, Rational, Method, Symbol, TrueClass, *integer_classes
25
+ value
26
+ else
27
+ value.dup
28
+ end
29
+ end
30
+
31
+ # Lists the classes Ruby uses for integers
32
+ #
33
+ # @api private
34
+ # @return [Array<Class>]
35
+ def self.integer_classes
36
+ @integer_classes ||=
37
+ if 0.class == Integer
38
+ [Integer]
39
+ else
40
+ [Fixnum, Bignum] # rubocop:disable Lint/UnifiedInteger
41
+ end
42
+ end
15
43
  end
16
44
  end
@@ -1,3 +1,3 @@
1
1
  module Hashie
2
- VERSION = '3.6.0'.freeze
2
+ VERSION = '4.1.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hashie
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.0
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
@@ -9,50 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-08-13 00:00:00.000000000 Z
12
+ date: 2020-02-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: rake
15
+ name: bundler
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - "<"
18
+ - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '11'
20
+ version: '0'
21
21
  type: :development
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - "<"
25
+ - - ">="
26
26
  - !ruby/object:Gem::Version
27
- version: '11'
28
- - !ruby/object:Gem::Dependency
29
- name: rspec
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - "~>"
33
- - !ruby/object:Gem::Version
34
- version: '3.0'
35
- type: :development
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - "~>"
40
- - !ruby/object:Gem::Version
41
- version: '3.0'
42
- - !ruby/object:Gem::Dependency
43
- name: rspec-pending_for
44
- requirement: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - "~>"
47
- - !ruby/object:Gem::Version
48
- version: '0.1'
49
- type: :development
50
- prerelease: false
51
- version_requirements: !ruby/object:Gem::Requirement
52
- requirements:
53
- - - "~>"
54
- - !ruby/object:Gem::Version
55
- version: '0.1'
27
+ version: '0'
56
28
  description: Hashie is a collection of classes and mixins that make hashes more powerful.
57
29
  email:
58
30
  - michael@intridea.com
@@ -73,6 +45,7 @@ files:
73
45
  - lib/hashie/array.rb
74
46
  - lib/hashie/clash.rb
75
47
  - lib/hashie/dash.rb
48
+ - lib/hashie/extensions/active_support/core_ext/hash.rb
76
49
  - lib/hashie/extensions/array/pretty_inspect.rb
77
50
  - lib/hashie/extensions/coercion.rb
78
51
  - lib/hashie/extensions/dash/coercion.rb
@@ -84,8 +57,11 @@ files:
84
57
  - lib/hashie/extensions/deep_merge.rb
85
58
  - lib/hashie/extensions/ignore_undeclared.rb
86
59
  - lib/hashie/extensions/indifferent_access.rb
60
+ - lib/hashie/extensions/key_conflict_warning.rb
87
61
  - lib/hashie/extensions/key_conversion.rb
62
+ - lib/hashie/extensions/mash/define_accessors.rb
88
63
  - lib/hashie/extensions/mash/keep_original_keys.rb
64
+ - lib/hashie/extensions/mash/permissive_respond_to.rb
89
65
  - lib/hashie/extensions/mash/safe_assignment.rb
90
66
  - lib/hashie/extensions/mash/symbolize_keys.rb
91
67
  - lib/hashie/extensions/merge_initializer.rb
@@ -105,55 +81,14 @@ files:
105
81
  - lib/hashie/trash.rb
106
82
  - lib/hashie/utils.rb
107
83
  - lib/hashie/version.rb
108
- - spec/hashie/array_spec.rb
109
- - spec/hashie/clash_spec.rb
110
- - spec/hashie/dash_spec.rb
111
- - spec/hashie/extensions/autoload_spec.rb
112
- - spec/hashie/extensions/coercion_spec.rb
113
- - spec/hashie/extensions/dash/coercion_spec.rb
114
- - spec/hashie/extensions/dash/indifferent_access_spec.rb
115
- - spec/hashie/extensions/deep_fetch_spec.rb
116
- - spec/hashie/extensions/deep_find_spec.rb
117
- - spec/hashie/extensions/deep_locate_spec.rb
118
- - spec/hashie/extensions/deep_merge_spec.rb
119
- - spec/hashie/extensions/ignore_undeclared_spec.rb
120
- - spec/hashie/extensions/indifferent_access_spec.rb
121
- - spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb
122
- - spec/hashie/extensions/key_conversion_spec.rb
123
- - spec/hashie/extensions/mash/keep_original_keys_spec.rb
124
- - spec/hashie/extensions/mash/safe_assignment_spec.rb
125
- - spec/hashie/extensions/mash/symbolize_keys_spec.rb
126
- - spec/hashie/extensions/merge_initializer_spec.rb
127
- - spec/hashie/extensions/method_access_spec.rb
128
- - spec/hashie/extensions/strict_key_access_spec.rb
129
- - spec/hashie/extensions/stringify_keys_spec.rb
130
- - spec/hashie/extensions/symbolize_keys_spec.rb
131
- - spec/hashie/hash_spec.rb
132
- - spec/hashie/mash_spec.rb
133
- - spec/hashie/parsers/yaml_erb_parser_spec.rb
134
- - spec/hashie/rash_spec.rb
135
- - spec/hashie/trash_spec.rb
136
- - spec/hashie/utils_spec.rb
137
- - spec/hashie/version_spec.rb
138
- - spec/hashie_spec.rb
139
- - spec/integration/elasticsearch/integration_spec.rb
140
- - spec/integration/omniauth-oauth2/app.rb
141
- - spec/integration/omniauth-oauth2/integration_spec.rb
142
- - spec/integration/omniauth-oauth2/some_site.rb
143
- - spec/integration/omniauth/app.rb
144
- - spec/integration/omniauth/integration_spec.rb
145
- - spec/integration/rails-without-dependency/integration_spec.rb
146
- - spec/integration/rails/app.rb
147
- - spec/integration/rails/integration_spec.rb
148
- - spec/spec_helper.rb
149
- - spec/support/integration_specs.rb
150
- - spec/support/logger.rb
151
- - spec/support/module_context.rb
152
- - spec/support/ruby_version_check.rb
153
- homepage: https://github.com/intridea/hashie
84
+ homepage: https://github.com/hashie/hashie
154
85
  licenses:
155
86
  - MIT
156
- metadata: {}
87
+ metadata:
88
+ bug_tracker_uri: https://github.com/hashie/hashie/issues
89
+ changelog_uri: https://github.com/hashie/hashie/blob/master/CHANGELOG.md
90
+ documentation_uri: https://www.rubydoc.info/gems/hashie
91
+ source_code_uri: https://github.com/hashie/hashie
157
92
  post_install_message:
158
93
  rdoc_options: []
159
94
  require_paths:
@@ -169,54 +104,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
104
  - !ruby/object:Gem::Version
170
105
  version: '0'
171
106
  requirements: []
172
- rubyforge_project:
173
- rubygems_version: 2.7.6
107
+ rubygems_version: 3.0.3
174
108
  signing_key:
175
109
  specification_version: 4
176
110
  summary: Your friendly neighborhood hash library.
177
- test_files:
178
- - spec/hashie/parsers/yaml_erb_parser_spec.rb
179
- - spec/hashie/array_spec.rb
180
- - spec/hashie/hash_spec.rb
181
- - spec/hashie/version_spec.rb
182
- - spec/hashie/clash_spec.rb
183
- - spec/hashie/mash_spec.rb
184
- - spec/hashie/extensions/stringify_keys_spec.rb
185
- - spec/hashie/extensions/deep_locate_spec.rb
186
- - spec/hashie/extensions/coercion_spec.rb
187
- - spec/hashie/extensions/mash/safe_assignment_spec.rb
188
- - spec/hashie/extensions/mash/symbolize_keys_spec.rb
189
- - spec/hashie/extensions/mash/keep_original_keys_spec.rb
190
- - spec/hashie/extensions/key_conversion_spec.rb
191
- - spec/hashie/extensions/ignore_undeclared_spec.rb
192
- - spec/hashie/extensions/autoload_spec.rb
193
- - spec/hashie/extensions/strict_key_access_spec.rb
194
- - spec/hashie/extensions/deep_fetch_spec.rb
195
- - spec/hashie/extensions/method_access_spec.rb
196
- - spec/hashie/extensions/deep_find_spec.rb
197
- - spec/hashie/extensions/deep_merge_spec.rb
198
- - spec/hashie/extensions/merge_initializer_spec.rb
199
- - spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb
200
- - spec/hashie/extensions/dash/coercion_spec.rb
201
- - spec/hashie/extensions/dash/indifferent_access_spec.rb
202
- - spec/hashie/extensions/symbolize_keys_spec.rb
203
- - spec/hashie/extensions/indifferent_access_spec.rb
204
- - spec/hashie/rash_spec.rb
205
- - spec/hashie/utils_spec.rb
206
- - spec/hashie/dash_spec.rb
207
- - spec/hashie/trash_spec.rb
208
- - spec/spec_helper.rb
209
- - spec/integration/rails-without-dependency/integration_spec.rb
210
- - spec/integration/omniauth-oauth2/integration_spec.rb
211
- - spec/integration/omniauth-oauth2/app.rb
212
- - spec/integration/omniauth-oauth2/some_site.rb
213
- - spec/integration/omniauth/integration_spec.rb
214
- - spec/integration/omniauth/app.rb
215
- - spec/integration/elasticsearch/integration_spec.rb
216
- - spec/integration/rails/integration_spec.rb
217
- - spec/integration/rails/app.rb
218
- - spec/support/module_context.rb
219
- - spec/support/logger.rb
220
- - spec/support/integration_specs.rb
221
- - spec/support/ruby_version_check.rb
222
- - spec/hashie_spec.rb
111
+ test_files: []
@@ -1,29 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Array do
4
- with_minimum_ruby('2.3.0') do
5
- describe '#dig' do
6
- let(:array) { Hashie::Array.new(%i[a b c]) }
7
-
8
- it 'works with a string index' do
9
- expect(array.dig('0')).to eq(:a)
10
- end
11
-
12
- it 'works with a numeric index' do
13
- expect(array.dig(1)).to eq(:b)
14
- end
15
-
16
- context 'when array is empty' do
17
- let(:array) { Hashie::Array.new([]) }
18
-
19
- it 'works with a first numeric and next string index' do
20
- expect(array.dig(0, 'hello')).to eq(nil)
21
- end
22
-
23
- it 'throws an error with first string and next numeric index' do
24
- expect { array.dig('hello', 0) }.to raise_error(TypeError)
25
- end
26
- end
27
- end
28
- end
29
- end
@@ -1,70 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Hashie::Clash do
4
- it 'is able to set an attribute via method_missing' do
5
- subject.foo('bar')
6
- expect(subject[:foo]).to eq 'bar'
7
- end
8
-
9
- it 'is able to set multiple attributes' do
10
- subject.foo('bar').baz('wok')
11
- expect(subject).to eq(foo: 'bar', baz: 'wok')
12
- end
13
-
14
- it 'converts multiple arguments into an array' do
15
- subject.foo(1, 2, 3)
16
- expect(subject[:foo]).to eq [1, 2, 3]
17
- end
18
-
19
- it 'is able to use bang notation to create a new Clash on a key' do
20
- subject.foo!
21
- expect(subject[:foo]).to be_kind_of(Hashie::Clash)
22
- end
23
-
24
- it 'is able to chain onto the new Clash when using bang notation' do
25
- subject.foo!.bar('abc').baz(123)
26
- expect(subject).to eq(foo: { bar: 'abc', baz: 123 })
27
- end
28
-
29
- it 'is able to jump back up to the parent in the chain with #_end!' do
30
- subject.foo!.bar('abc')._end!.baz(123)
31
- expect(subject).to eq(foo: { bar: 'abc' }, baz: 123)
32
- end
33
-
34
- it 'merges rather than replaces existing keys' do
35
- subject.where(abc: 'def').where(hgi: 123)
36
- expect(subject).to eq(where: { abc: 'def', hgi: 123 })
37
- end
38
-
39
- it 'is able to replace all of its own keys with #replace' do
40
- subject.foo(:bar).hello(:world)
41
- expect(subject.replace(baz: 123, hgi: 123)).to eq(baz: 123, hgi: 123)
42
- expect(subject).to eq(baz: 123, hgi: 123)
43
- expect(subject[:foo]).to be_nil
44
- expect(subject[:hello]).to be_nil
45
- end
46
-
47
- it 'merges multiple bang notation calls' do
48
- subject.where!.foo(123)
49
- subject.where!.bar(321)
50
- expect(subject).to eq(where: { foo: 123, bar: 321 })
51
- end
52
-
53
- it 'raises an exception when method is missing' do
54
- expect { subject.boo }.to raise_error(NoMethodError)
55
- end
56
-
57
- describe 'when inherited' do
58
- subject { Class.new(described_class).new }
59
-
60
- it 'bang nodes are instances of a subclass' do
61
- subject.where!.foo(123)
62
- expect(subject[:where]).to be_instance_of(subject.class)
63
- end
64
-
65
- it 'merged nodes are instances of a subclass' do
66
- subject.where(abc: 'def').where(hgi: 123)
67
- expect(subject[:where]).to be_instance_of(subject.class)
68
- end
69
- end
70
- end
@@ -1,598 +0,0 @@
1
- require 'spec_helper'
2
-
3
- Hashie::Hash.class_eval do
4
- def self.inherited(klass)
5
- klass.instance_variable_set('@inheritance_test', true)
6
- end
7
- end
8
-
9
- class DashTest < Hashie::Dash
10
- property :first_name, required: true
11
- property :email
12
- property :count, default: 0
13
- end
14
-
15
- class DashTestDefaultProc < Hashie::Dash
16
- property :fields, default: -> { [] }
17
- end
18
-
19
- class DashNoRequiredTest < Hashie::Dash
20
- property :first_name
21
- property :email
22
- property :count, default: 0
23
- end
24
-
25
- class DashWithCoercion < Hashie::Dash
26
- include Hashie::Extensions::Coercion
27
- property :person
28
- property :city
29
-
30
- coerce_key :person, ::DashNoRequiredTest
31
- end
32
-
33
- class PropertyBangTest < Hashie::Dash
34
- property :important!
35
- end
36
-
37
- class SubclassedTest < DashTest
38
- property :last_name, required: true
39
- end
40
-
41
- class RequiredMessageTest < DashTest
42
- property :first_name, required: true, message: 'must be set.'
43
- end
44
-
45
- class DashDefaultTest < Hashie::Dash
46
- property :aliases, default: ['Snake']
47
- end
48
-
49
- class DeferredTest < Hashie::Dash
50
- property :created_at, default: proc { Time.now }
51
- end
52
-
53
- class DeferredWithSelfTest < Hashie::Dash
54
- property :created_at, default: -> { Time.now }
55
- property :updated_at, default: ->(test) { test.created_at }
56
- end
57
-
58
- describe DashTestDefaultProc do
59
- it 'as_json behaves correctly with default proc' do
60
- object = described_class.new
61
- expect(object.as_json).to be == { 'fields' => [] }
62
- end
63
- end
64
-
65
- describe DashTest do
66
- def property_required_error(property)
67
- [ArgumentError, "The property '#{property}' is required for #{subject.class.name}."]
68
- end
69
-
70
- def property_required_custom_error(property)
71
- [ArgumentError, "The property '#{property}' must be set."]
72
- end
73
-
74
- def property_message_without_required_error
75
- [ArgumentError, 'The :message option should be used with :required option.']
76
- end
77
-
78
- def no_property_error(property)
79
- [NoMethodError, "The property '#{property}' is not defined for #{subject.class.name}."]
80
- end
81
-
82
- subject { DashTest.new(first_name: 'Bob', email: 'bob@example.com') }
83
- let(:required_message) { RequiredMessageTest.new(first_name: 'Bob') }
84
-
85
- it('subclasses Hashie::Hash') { should respond_to(:to_mash) }
86
-
87
- describe '#to_s' do
88
- subject { super().to_s }
89
- it { should eq '#<DashTest count=0 email="bob@example.com" first_name="Bob">' }
90
- end
91
-
92
- it 'lists all set properties in inspect' do
93
- subject.first_name = 'Bob'
94
- subject.email = 'bob@example.com'
95
- expect(subject.inspect).to eq '#<DashTest count=0 email="bob@example.com" first_name="Bob">'
96
- end
97
-
98
- describe '#count' do
99
- subject { super().count }
100
- it { should be_zero }
101
- end
102
-
103
- it { should respond_to(:first_name) }
104
- it { should respond_to(:first_name=) }
105
- it { should_not respond_to(:nonexistent) }
106
-
107
- it 'errors out for a non-existent property' do
108
- expect { subject['nonexistent'] }.to raise_error(*no_property_error('nonexistent'))
109
- end
110
-
111
- it 'errors out when attempting to set a required property to nil' do
112
- expect { subject.first_name = nil }.to raise_error(*property_required_error('first_name'))
113
- end
114
-
115
- it 'errors out when message added to not required property' do
116
- expect do
117
- class DashMessageOptionWithoutRequiredTest < Hashie::Dash
118
- property :first_name, message: 'is required.'
119
- end
120
- end.to raise_error(*property_message_without_required_error)
121
-
122
- expect do
123
- class DashMessageOptionWithoutRequiredTest < Hashie::Dash
124
- property :first_name, required: false, message: 'is required.'
125
- end
126
- end.to raise_error(*property_message_without_required_error)
127
- end
128
-
129
- context 'writing to properties' do
130
- it 'fails writing a required property to nil' do
131
- expect { subject.first_name = nil }.to raise_error(*property_required_error('first_name'))
132
- expect { required_message.first_name = nil }.to raise_error(*property_required_custom_error('first_name'))
133
- end
134
-
135
- it 'fails writing a required property to nil using []=' do
136
- expect { subject[:first_name] = nil }.to raise_error(*property_required_error('first_name'))
137
- expect { required_message[:first_name] = nil }.to raise_error(*property_required_custom_error('first_name'))
138
- end
139
-
140
- it 'fails writing to a non-existent property using []=' do
141
- expect { subject['nonexistent'] = 123 }.to raise_error(*no_property_error('nonexistent'))
142
- end
143
-
144
- it 'works for an existing property using []=' do
145
- subject[:first_name] = 'Bob'
146
- expect(subject[:first_name]).to eq 'Bob'
147
- expect { subject['first_name'] }.to raise_error(*no_property_error('first_name'))
148
- end
149
-
150
- it 'works for an existing property using a method call' do
151
- subject.first_name = 'Franklin'
152
- expect(subject.first_name).to eq 'Franklin'
153
- end
154
- end
155
-
156
- context 'reading from properties' do
157
- it 'fails reading from a non-existent property using []' do
158
- expect { subject['nonexistent'] }.to raise_error(*no_property_error('nonexistent'))
159
- end
160
-
161
- it 'is able to retrieve properties through blocks' do
162
- subject[:first_name] = 'Aiden'
163
- value = nil
164
- subject.[](:first_name) { |v| value = v }
165
- expect(value).to eq 'Aiden'
166
- end
167
-
168
- it 'is able to retrieve properties through blocks with method calls' do
169
- subject[:first_name] = 'Frodo'
170
- value = nil
171
- subject.first_name { |v| value = v }
172
- expect(value).to eq 'Frodo'
173
- end
174
- end
175
-
176
- context 'reading from deferred properties' do
177
- it 'evaluates proc after initial read' do
178
- expect(DeferredTest.new[:created_at]).to be_instance_of(Time)
179
- end
180
-
181
- it 'does not evalute proc after subsequent reads' do
182
- deferred = DeferredTest.new
183
- expect(deferred[:created_at].object_id).to eq deferred[:created_at].object_id
184
- end
185
- end
186
-
187
- context 'reading from a deferred property based on context' do
188
- it 'provides the current hash as context for evaluation' do
189
- deferred = DeferredWithSelfTest.new
190
- expect(deferred[:created_at].object_id).to eq deferred[:created_at].object_id
191
- expect(deferred[:updated_at].object_id).to eq deferred[:created_at].object_id
192
- end
193
- end
194
-
195
- context 'converting from a Mash' do
196
- class ConvertingFromMash < Hashie::Dash
197
- property :property, required: true
198
- end
199
-
200
- context 'without keeping the original keys' do
201
- let(:mash) { Hashie::Mash.new(property: 'test') }
202
-
203
- it 'does not pick up the property from the stringified key' do
204
- expect { ConvertingFromMash.new(mash) }.to raise_error(NoMethodError)
205
- end
206
- end
207
-
208
- context 'when keeping the original keys' do
209
- class KeepingMash < Hashie::Mash
210
- include Hashie::Extensions::Mash::KeepOriginalKeys
211
- end
212
-
213
- let(:mash) { KeepingMash.new(property: 'test') }
214
-
215
- it 'picks up the property from the original key' do
216
- expect { ConvertingFromMash.new(mash) }.not_to raise_error
217
- end
218
- end
219
- end
220
-
221
- describe '#new' do
222
- it 'fails with non-existent properties' do
223
- expect { described_class.new(bork: '') }.to raise_error(*no_property_error('bork'))
224
- end
225
-
226
- it 'sets properties that it is able to' do
227
- obj = described_class.new first_name: 'Michael'
228
- expect(obj.first_name).to eq 'Michael'
229
- end
230
-
231
- it 'accepts nil' do
232
- expect { DashNoRequiredTest.new(nil) }.not_to raise_error
233
- end
234
-
235
- it 'accepts block to define a global default' do
236
- obj = described_class.new { |_, key| key.to_s.upcase }
237
- expect(obj.first_name).to eq 'FIRST_NAME'
238
- expect(obj.count).to be_zero
239
- end
240
-
241
- it 'fails when required values are missing' do
242
- expect { DashTest.new }.to raise_error(*property_required_error('first_name'))
243
- end
244
-
245
- it 'does not overwrite default values' do
246
- obj1 = DashDefaultTest.new
247
- obj1.aliases << 'El Rey'
248
- obj2 = DashDefaultTest.new
249
- expect(obj2.aliases).not_to include 'El Rey'
250
- end
251
- end
252
-
253
- describe '#merge' do
254
- it 'creates a new instance of the Dash' do
255
- new_dash = subject.merge(first_name: 'Robert')
256
- expect(subject.object_id).not_to eq new_dash.object_id
257
- end
258
-
259
- it 'merges the given hash' do
260
- new_dash = subject.merge(first_name: 'Robert', email: 'robert@example.com')
261
- expect(new_dash.first_name).to eq 'Robert'
262
- expect(new_dash.email).to eq 'robert@example.com'
263
- end
264
-
265
- it 'fails with non-existent properties' do
266
- expect { subject.merge(middle_name: 'James') }.to raise_error(*no_property_error('middle_name'))
267
- end
268
-
269
- it 'errors out when attempting to set a required property to nil' do
270
- expect { subject.merge(first_name: nil) }.to raise_error(*property_required_error('first_name'))
271
- end
272
-
273
- context 'given a block' do
274
- it "sets merged key's values to the block's return value" do
275
- expect(subject.merge(first_name: 'Jim') do |key, oldval, newval|
276
- "#{key}: #{newval} #{oldval}"
277
- end.first_name).to eq 'first_name: Jim Bob'
278
- end
279
- end
280
- end
281
-
282
- describe '#merge!' do
283
- it 'modifies the existing instance of the Dash' do
284
- original_dash = subject.merge!(first_name: 'Robert')
285
- expect(subject.object_id).to eq original_dash.object_id
286
- end
287
-
288
- it 'merges the given hash' do
289
- subject.merge!(first_name: 'Robert', email: 'robert@example.com')
290
- expect(subject.first_name).to eq 'Robert'
291
- expect(subject.email).to eq 'robert@example.com'
292
- end
293
-
294
- it 'fails with non-existent properties' do
295
- expect { subject.merge!(middle_name: 'James') }.to raise_error(NoMethodError)
296
- end
297
-
298
- it 'errors out when attempting to set a required property to nil' do
299
- expect { subject.merge!(first_name: nil) }.to raise_error(ArgumentError)
300
- end
301
-
302
- context 'given a block' do
303
- it "sets merged key's values to the block's return value" do
304
- expect(subject.merge!(first_name: 'Jim') do |key, oldval, newval|
305
- "#{key}: #{newval} #{oldval}"
306
- end.first_name).to eq 'first_name: Jim Bob'
307
- end
308
- end
309
- end
310
-
311
- describe 'properties' do
312
- it 'lists defined properties' do
313
- expect(described_class.properties).to eq Set.new(%i[first_name email count])
314
- end
315
-
316
- it 'checks if a property exists' do
317
- expect(described_class.property?(:first_name)).to be_truthy
318
- expect(described_class.property?('first_name')).to be_falsy
319
- end
320
-
321
- it 'checks if a property is required' do
322
- expect(described_class.required?(:first_name)).to be_truthy
323
- expect(described_class.required?('first_name')).to be_falsy
324
- end
325
-
326
- it 'doesnt include property from subclass' do
327
- expect(described_class.property?(:last_name)).to be_falsy
328
- end
329
-
330
- it 'lists declared defaults' do
331
- expect(described_class.defaults).to eq(count: 0)
332
- end
333
-
334
- it 'allows properties that end in bang' do
335
- expect(PropertyBangTest.property?(:important!)).to be_truthy
336
- end
337
- end
338
-
339
- describe '#replace' do
340
- before { subject.replace(first_name: 'Cain') }
341
-
342
- it 'return self' do
343
- expect(subject.replace(email: 'bar').to_hash).to eq(email: 'bar', count: 0)
344
- end
345
-
346
- it 'sets all specified keys to their corresponding values' do
347
- expect(subject.first_name).to eq 'Cain'
348
- end
349
-
350
- it 'leaves only specified keys and keys with default values' do
351
- expect(subject.keys.sort_by(&:to_s)).to eq %i[count first_name]
352
- expect(subject.email).to be_nil
353
- expect(subject.count).to eq 0
354
- end
355
-
356
- context 'when replacing keys with default values' do
357
- before { subject.replace(count: 3) }
358
-
359
- it 'sets all specified keys to their corresponding values' do
360
- expect(subject.count).to eq 3
361
- end
362
- end
363
- end
364
-
365
- describe '#update_attributes!(params)' do
366
- let(:params) { { first_name: 'Alice', email: 'alice@example.com' } }
367
-
368
- context 'when there is coercion' do
369
- let(:params_before) { { city: 'nyc', person: { first_name: 'Bob', email: 'bob@example.com' } } }
370
- let(:params_after) { { city: 'sfo', person: { first_name: 'Alice', email: 'alice@example.com' } } }
371
-
372
- subject { DashWithCoercion.new(params_before) }
373
-
374
- it 'update the attributes' do
375
- expect(subject.person.first_name).to eq params_before[:person][:first_name]
376
- subject.update_attributes!(params_after)
377
- expect(subject.person.first_name).to eq params_after[:person][:first_name]
378
- end
379
- end
380
-
381
- it 'update the attributes' do
382
- subject.update_attributes!(params)
383
- expect(subject.first_name).to eq params[:first_name]
384
- expect(subject.email).to eq params[:email]
385
- expect(subject.count).to eq subject.class.defaults[:count]
386
- end
387
-
388
- context 'when required property is update to nil' do
389
- let(:params) { { first_name: nil, email: 'alice@example.com' } }
390
-
391
- it 'raise an ArgumentError' do
392
- expect { subject.update_attributes!(params) }.to raise_error(ArgumentError)
393
- end
394
- end
395
-
396
- context 'when a default property is update to nil' do
397
- let(:params) { { count: nil, email: 'alice@example.com' } }
398
-
399
- it 'set the property back to the default value' do
400
- subject.update_attributes!(params)
401
- expect(subject.email).to eq params[:email]
402
- expect(subject.count).to eq subject.class.defaults[:count]
403
- end
404
- end
405
-
406
- context 'codependent attributes' do
407
- let(:codependent) do
408
- Class.new(Hashie::Dash) do
409
- property :a, required: -> { b.nil? }, message: 'is required if b is not set.'
410
- property :b, required: -> { a.nil? }, message: 'is required if a is not set.'
411
- end
412
- end
413
-
414
- it 'does not raise an error when only the first property is set' do
415
- expect { codependent.new(a: 'ant', b: nil) }.not_to raise_error
416
- end
417
-
418
- it 'does not raise an error when only the second property is set' do
419
- expect { codependent.new(a: nil, b: 'bat') }.not_to raise_error
420
- end
421
-
422
- it 'does not raise an error when both properties are set' do
423
- expect { codependent.new(a: 'ant', b: 'bat') }.not_to raise_error
424
- end
425
-
426
- it 'raises an error when neither property is set' do
427
- expect { codependent.new(a: nil, b: nil) }.to raise_error(ArgumentError)
428
- end
429
- end
430
- end
431
- end
432
-
433
- describe Hashie::Dash, 'inheritance' do
434
- before do
435
- @top = Class.new(Hashie::Dash)
436
- @middle = Class.new(@top)
437
- @bottom = Class.new(@middle)
438
- end
439
-
440
- it 'reports empty properties when nothing defined' do
441
- expect(@top.properties).to be_empty
442
- expect(@top.defaults).to be_empty
443
- end
444
-
445
- it 'inherits properties downwards' do
446
- @top.property :echo
447
- expect(@middle.properties).to include(:echo)
448
- expect(@bottom.properties).to include(:echo)
449
- end
450
-
451
- it 'doesnt inherit properties upwards' do
452
- @middle.property :echo
453
- expect(@top.properties).not_to include(:echo)
454
- expect(@bottom.properties).to include(:echo)
455
- end
456
-
457
- it 'allows overriding a default on an existing property' do
458
- @top.property :echo
459
- @middle.property :echo, default: 123
460
- expect(@bottom.properties.to_a).to eq [:echo]
461
- expect(@bottom.new.echo).to eq 123
462
- end
463
-
464
- it 'allows clearing an existing default' do
465
- @top.property :echo
466
- @middle.property :echo, default: 123
467
- @bottom.property :echo
468
- expect(@bottom.properties.to_a).to eq [:echo]
469
- expect(@bottom.new.echo).to be_nil
470
- end
471
-
472
- it 'allows nil defaults' do
473
- @bottom.property :echo, default: nil
474
- expect(@bottom.new).to have_key(:echo)
475
- expect(@bottom.new).to_not have_key('echo')
476
- end
477
- end
478
-
479
- describe SubclassedTest do
480
- subject { SubclassedTest.new(first_name: 'Bob', last_name: 'McNob', email: 'bob@example.com') }
481
-
482
- describe '#count' do
483
- subject { super().count }
484
- it { should be_zero }
485
- end
486
-
487
- it { should respond_to(:first_name) }
488
- it { should respond_to(:first_name=) }
489
- it { should respond_to(:last_name) }
490
- it { should respond_to(:last_name=) }
491
-
492
- it 'has one additional property' do
493
- expect(described_class.property?(:last_name)).to be_truthy
494
- end
495
-
496
- it "didn't override superclass inheritance logic" do
497
- expect(described_class.instance_variable_get('@inheritance_test')).to be_truthy
498
- end
499
- end
500
-
501
- class ConditionallyRequiredTest < Hashie::Dash
502
- property :username
503
- property :password, required: -> { !username.nil? }, message: 'must be set, too.'
504
- end
505
-
506
- describe ConditionallyRequiredTest do
507
- it 'does not allow a conditionally required property to be set to nil if required' do
508
- expect { ConditionallyRequiredTest.new(username: 'bob.smith', password: nil) }.to raise_error(ArgumentError, "The property 'password' must be set, too.")
509
- end
510
-
511
- it 'allows a conditionally required property to be set to nil if not required' do
512
- expect { ConditionallyRequiredTest.new(username: nil, password: nil) }.not_to raise_error
513
- end
514
-
515
- it 'allows a conditionally required property to be set if required' do
516
- expect { ConditionallyRequiredTest.new(username: 'bob.smith', password: '$ecure!') }.not_to raise_error
517
- end
518
- end
519
-
520
- class MixedPropertiesTest < Hashie::Dash
521
- property :symbol
522
- property 'string'
523
- end
524
-
525
- describe MixedPropertiesTest do
526
- subject { MixedPropertiesTest.new('string' => 'string', symbol: 'symbol') }
527
-
528
- it { should respond_to('string') }
529
- it { should respond_to(:symbol) }
530
-
531
- it 'property?' do
532
- expect(described_class.property?('string')).to be_truthy
533
- expect(described_class.property?(:symbol)).to be_truthy
534
- end
535
-
536
- it 'fetch' do
537
- expect(subject['string']).to eq('string')
538
- expect { subject[:string] }.to raise_error(NoMethodError)
539
- expect(subject[:symbol]).to eq('symbol')
540
- expect { subject['symbol'] }.to raise_error(NoMethodError)
541
- end
542
-
543
- it 'double define' do
544
- klass = Class.new(MixedPropertiesTest) do
545
- property 'symbol'
546
- end
547
- instance = klass.new(symbol: 'one', 'symbol' => 'two')
548
- expect(instance[:symbol]).to eq('one')
549
- expect(instance['symbol']).to eq('two')
550
- end
551
-
552
- it 'assign' do
553
- subject['string'] = 'updated'
554
- expect(subject['string']).to eq('updated')
555
-
556
- expect { subject[:string] = 'updated' }.to raise_error(NoMethodError)
557
-
558
- subject[:symbol] = 'updated'
559
- expect(subject[:symbol]).to eq('updated')
560
-
561
- expect { subject['symbol'] = 'updated' }.to raise_error(NoMethodError)
562
- end
563
- end
564
-
565
- context 'Dynamic Dash Class' do
566
- it 'define property' do
567
- klass = Class.new(Hashie::Dash)
568
- my_property = 'my_property'
569
- my_orig = my_property.dup
570
-
571
- klass.property(my_property)
572
-
573
- expect(my_property).to eq(my_orig)
574
- end
575
- end
576
-
577
- context 'with method access' do
578
- class DashWithMethodAccess < Hashie::Dash
579
- include Hashie::Extensions::IndifferentAccess
580
- include Hashie::Extensions::MethodQuery
581
-
582
- property :test
583
- end
584
-
585
- subject(:dash) { DashWithMethodAccess.new(test: 'value') }
586
-
587
- describe '#test' do
588
- subject { dash.test }
589
-
590
- it { is_expected.to eq('value') }
591
- end
592
-
593
- describe '#test?' do
594
- subject { dash.test? }
595
-
596
- it { is_expected.to eq true }
597
- end
598
- end