hashie 3.5.7 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +240 -164
  3. data/CONTRIBUTING.md +13 -6
  4. data/README.md +210 -29
  5. data/Rakefile +2 -2
  6. data/UPGRADING.md +83 -7
  7. data/hashie.gemspec +13 -7
  8. data/lib/hashie.rb +21 -19
  9. data/lib/hashie/clash.rb +12 -1
  10. data/lib/hashie/dash.rb +42 -21
  11. data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
  12. data/lib/hashie/extensions/coercion.rb +26 -19
  13. data/lib/hashie/extensions/dash/indifferent_access.rb +20 -1
  14. data/lib/hashie/extensions/dash/property_translation.rb +54 -28
  15. data/lib/hashie/extensions/deep_fetch.rb +5 -3
  16. data/lib/hashie/extensions/deep_find.rb +14 -5
  17. data/lib/hashie/extensions/deep_locate.rb +22 -8
  18. data/lib/hashie/extensions/deep_merge.rb +26 -10
  19. data/lib/hashie/extensions/indifferent_access.rb +8 -8
  20. data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
  21. data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
  22. data/lib/hashie/extensions/mash/keep_original_keys.rb +4 -5
  23. data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
  24. data/lib/hashie/extensions/mash/safe_assignment.rb +3 -1
  25. data/lib/hashie/extensions/mash/symbolize_keys.rb +1 -1
  26. data/lib/hashie/extensions/method_access.rb +47 -14
  27. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +28 -4
  28. data/lib/hashie/extensions/ruby_version_check.rb +5 -1
  29. data/lib/hashie/extensions/strict_key_access.rb +16 -13
  30. data/lib/hashie/extensions/stringify_keys.rb +1 -1
  31. data/lib/hashie/extensions/symbolize_keys.rb +1 -1
  32. data/lib/hashie/hash.rb +18 -11
  33. data/lib/hashie/mash.rb +131 -70
  34. data/lib/hashie/railtie.rb +7 -0
  35. data/lib/hashie/rash.rb +6 -6
  36. data/lib/hashie/utils.rb +28 -0
  37. data/lib/hashie/version.rb +1 -1
  38. metadata +19 -128
  39. data/spec/hashie/array_spec.rb +0 -29
  40. data/spec/hashie/clash_spec.rb +0 -70
  41. data/spec/hashie/dash_spec.rb +0 -573
  42. data/spec/hashie/extensions/autoload_spec.rb +0 -24
  43. data/spec/hashie/extensions/coercion_spec.rb +0 -631
  44. data/spec/hashie/extensions/dash/coercion_spec.rb +0 -13
  45. data/spec/hashie/extensions/dash/indifferent_access_spec.rb +0 -84
  46. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -97
  47. data/spec/hashie/extensions/deep_find_spec.rb +0 -138
  48. data/spec/hashie/extensions/deep_locate_spec.rb +0 -137
  49. data/spec/hashie/extensions/deep_merge_spec.rb +0 -70
  50. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -47
  51. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -282
  52. data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +0 -208
  53. data/spec/hashie/extensions/key_conversion_spec.rb +0 -12
  54. data/spec/hashie/extensions/mash/keep_original_keys_spec.rb +0 -46
  55. data/spec/hashie/extensions/mash/safe_assignment_spec.rb +0 -50
  56. data/spec/hashie/extensions/mash/symbolize_keys_spec.rb +0 -39
  57. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  58. data/spec/hashie/extensions/method_access_spec.rb +0 -188
  59. data/spec/hashie/extensions/strict_key_access_spec.rb +0 -110
  60. data/spec/hashie/extensions/stringify_keys_spec.rb +0 -124
  61. data/spec/hashie/extensions/symbolize_keys_spec.rb +0 -129
  62. data/spec/hashie/hash_spec.rb +0 -84
  63. data/spec/hashie/mash_spec.rb +0 -763
  64. data/spec/hashie/parsers/yaml_erb_parser_spec.rb +0 -46
  65. data/spec/hashie/rash_spec.rb +0 -83
  66. data/spec/hashie/trash_spec.rb +0 -268
  67. data/spec/hashie/utils_spec.rb +0 -25
  68. data/spec/hashie/version_spec.rb +0 -7
  69. data/spec/hashie_spec.rb +0 -13
  70. data/spec/integration/omniauth-oauth2/app.rb +0 -53
  71. data/spec/integration/omniauth-oauth2/integration_spec.rb +0 -26
  72. data/spec/integration/omniauth-oauth2/some_site.rb +0 -38
  73. data/spec/integration/omniauth/app.rb +0 -11
  74. data/spec/integration/omniauth/integration_spec.rb +0 -38
  75. data/spec/integration/rails-without-dependency/integration_spec.rb +0 -15
  76. data/spec/integration/rails/app.rb +0 -48
  77. data/spec/integration/rails/integration_spec.rb +0 -26
  78. data/spec/spec_helper.rb +0 -23
  79. data/spec/support/integration_specs.rb +0 -36
  80. data/spec/support/logger.rb +0 -24
  81. data/spec/support/module_context.rb +0 -11
  82. 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
@@ -64,7 +64,7 @@ module Hashie
64
64
  # Raise (or yield) unless something matches the key.
65
65
  #
66
66
  def fetch(*args)
67
- fail ArgumentError, "Expected 1-2 arguments, got #{args.length}" \
67
+ raise ArgumentError, "Expected 1-2 arguments, got #{args.length}" \
68
68
  unless (1..2).cover?(args.length)
69
69
 
70
70
  key, default = args
@@ -78,7 +78,7 @@ module Hashie
78
78
  elsif default
79
79
  default
80
80
  else
81
- fail KeyError, "key not found: #{key.inspect}"
81
+ raise KeyError, "key not found: #{key.inspect}"
82
82
  end
83
83
  end
84
84
 
@@ -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
@@ -125,11 +125,11 @@ module Hashie
125
125
  end
126
126
 
127
127
  def method_missing(*args, &block)
128
- @hash.send(*args, &block)
128
+ @hash.send(*args, &block) || super
129
129
  end
130
130
 
131
- def respond_to_missing?(*args)
132
- @hash.respond_to?(*args)
131
+ def respond_to_missing?(method_name, _include_private = false)
132
+ @hash.respond_to?(method_name)
133
133
  end
134
134
 
135
135
  private
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.5.7'
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.5.7
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: 2017-12-19 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,54 +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/omniauth-oauth2/app.rb
140
- - spec/integration/omniauth-oauth2/integration_spec.rb
141
- - spec/integration/omniauth-oauth2/some_site.rb
142
- - spec/integration/omniauth/app.rb
143
- - spec/integration/omniauth/integration_spec.rb
144
- - spec/integration/rails-without-dependency/integration_spec.rb
145
- - spec/integration/rails/app.rb
146
- - spec/integration/rails/integration_spec.rb
147
- - spec/spec_helper.rb
148
- - spec/support/integration_specs.rb
149
- - spec/support/logger.rb
150
- - spec/support/module_context.rb
151
- - spec/support/ruby_version_check.rb
152
- homepage: https://github.com/intridea/hashie
84
+ homepage: https://github.com/hashie/hashie
153
85
  licenses:
154
86
  - MIT
155
- 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
156
92
  post_install_message:
157
93
  rdoc_options: []
158
94
  require_paths:
@@ -168,53 +104,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
168
104
  - !ruby/object:Gem::Version
169
105
  version: '0'
170
106
  requirements: []
171
- rubyforge_project:
172
- rubygems_version: 2.6.13
107
+ rubygems_version: 3.0.3
173
108
  signing_key:
174
109
  specification_version: 4
175
110
  summary: Your friendly neighborhood hash library.
176
- test_files:
177
- - spec/hashie/parsers/yaml_erb_parser_spec.rb
178
- - spec/hashie/array_spec.rb
179
- - spec/hashie/hash_spec.rb
180
- - spec/hashie/version_spec.rb
181
- - spec/hashie/clash_spec.rb
182
- - spec/hashie/mash_spec.rb
183
- - spec/hashie/extensions/stringify_keys_spec.rb
184
- - spec/hashie/extensions/deep_locate_spec.rb
185
- - spec/hashie/extensions/coercion_spec.rb
186
- - spec/hashie/extensions/mash/safe_assignment_spec.rb
187
- - spec/hashie/extensions/mash/symbolize_keys_spec.rb
188
- - spec/hashie/extensions/mash/keep_original_keys_spec.rb
189
- - spec/hashie/extensions/key_conversion_spec.rb
190
- - spec/hashie/extensions/ignore_undeclared_spec.rb
191
- - spec/hashie/extensions/autoload_spec.rb
192
- - spec/hashie/extensions/strict_key_access_spec.rb
193
- - spec/hashie/extensions/deep_fetch_spec.rb
194
- - spec/hashie/extensions/method_access_spec.rb
195
- - spec/hashie/extensions/deep_find_spec.rb
196
- - spec/hashie/extensions/deep_merge_spec.rb
197
- - spec/hashie/extensions/merge_initializer_spec.rb
198
- - spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb
199
- - spec/hashie/extensions/dash/coercion_spec.rb
200
- - spec/hashie/extensions/dash/indifferent_access_spec.rb
201
- - spec/hashie/extensions/symbolize_keys_spec.rb
202
- - spec/hashie/extensions/indifferent_access_spec.rb
203
- - spec/hashie/rash_spec.rb
204
- - spec/hashie/utils_spec.rb
205
- - spec/hashie/dash_spec.rb
206
- - spec/hashie/trash_spec.rb
207
- - spec/spec_helper.rb
208
- - spec/integration/rails-without-dependency/integration_spec.rb
209
- - spec/integration/omniauth-oauth2/integration_spec.rb
210
- - spec/integration/omniauth-oauth2/app.rb
211
- - spec/integration/omniauth-oauth2/some_site.rb
212
- - spec/integration/omniauth/integration_spec.rb
213
- - spec/integration/omniauth/app.rb
214
- - spec/integration/rails/integration_spec.rb
215
- - spec/integration/rails/app.rb
216
- - spec/support/module_context.rb
217
- - spec/support/logger.rb
218
- - spec/support/integration_specs.rb
219
- - spec/support/ruby_version_check.rb
220
- - 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([: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,573 +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([: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 [: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
- end
406
- end
407
-
408
- describe Hashie::Dash, 'inheritance' do
409
- before do
410
- @top = Class.new(Hashie::Dash)
411
- @middle = Class.new(@top)
412
- @bottom = Class.new(@middle)
413
- end
414
-
415
- it 'reports empty properties when nothing defined' do
416
- expect(@top.properties).to be_empty
417
- expect(@top.defaults).to be_empty
418
- end
419
-
420
- it 'inherits properties downwards' do
421
- @top.property :echo
422
- expect(@middle.properties).to include(:echo)
423
- expect(@bottom.properties).to include(:echo)
424
- end
425
-
426
- it 'doesnt inherit properties upwards' do
427
- @middle.property :echo
428
- expect(@top.properties).not_to include(:echo)
429
- expect(@bottom.properties).to include(:echo)
430
- end
431
-
432
- it 'allows overriding a default on an existing property' do
433
- @top.property :echo
434
- @middle.property :echo, default: 123
435
- expect(@bottom.properties.to_a).to eq [:echo]
436
- expect(@bottom.new.echo).to eq 123
437
- end
438
-
439
- it 'allows clearing an existing default' do
440
- @top.property :echo
441
- @middle.property :echo, default: 123
442
- @bottom.property :echo
443
- expect(@bottom.properties.to_a).to eq [:echo]
444
- expect(@bottom.new.echo).to be_nil
445
- end
446
-
447
- it 'allows nil defaults' do
448
- @bottom.property :echo, default: nil
449
- expect(@bottom.new).to have_key(:echo)
450
- expect(@bottom.new).to_not have_key('echo')
451
- end
452
- end
453
-
454
- describe SubclassedTest do
455
- subject { SubclassedTest.new(first_name: 'Bob', last_name: 'McNob', email: 'bob@example.com') }
456
-
457
- describe '#count' do
458
- subject { super().count }
459
- it { should be_zero }
460
- end
461
-
462
- it { should respond_to(:first_name) }
463
- it { should respond_to(:first_name=) }
464
- it { should respond_to(:last_name) }
465
- it { should respond_to(:last_name=) }
466
-
467
- it 'has one additional property' do
468
- expect(described_class.property?(:last_name)).to be_truthy
469
- end
470
-
471
- it "didn't override superclass inheritance logic" do
472
- expect(described_class.instance_variable_get('@inheritance_test')).to be_truthy
473
- end
474
- end
475
-
476
- class ConditionallyRequiredTest < Hashie::Dash
477
- property :username
478
- property :password, required: -> { !username.nil? }, message: 'must be set, too.'
479
- end
480
-
481
- describe ConditionallyRequiredTest do
482
- it 'does not allow a conditionally required property to be set to nil if required' do
483
- expect { ConditionallyRequiredTest.new(username: 'bob.smith', password: nil) }.to raise_error(ArgumentError, "The property 'password' must be set, too.")
484
- end
485
-
486
- it 'allows a conditionally required property to be set to nil if not required' do
487
- expect { ConditionallyRequiredTest.new(username: nil, password: nil) }.not_to raise_error
488
- end
489
-
490
- it 'allows a conditionally required property to be set if required' do
491
- expect { ConditionallyRequiredTest.new(username: 'bob.smith', password: '$ecure!') }.not_to raise_error
492
- end
493
- end
494
-
495
- class MixedPropertiesTest < Hashie::Dash
496
- property :symbol
497
- property 'string'
498
- end
499
-
500
- describe MixedPropertiesTest do
501
- subject { MixedPropertiesTest.new('string' => 'string', symbol: 'symbol') }
502
-
503
- it { should respond_to('string') }
504
- it { should respond_to(:symbol) }
505
-
506
- it 'property?' do
507
- expect(described_class.property?('string')).to be_truthy
508
- expect(described_class.property?(:symbol)).to be_truthy
509
- end
510
-
511
- it 'fetch' do
512
- expect(subject['string']).to eq('string')
513
- expect { subject[:string] }.to raise_error(NoMethodError)
514
- expect(subject[:symbol]).to eq('symbol')
515
- expect { subject['symbol'] }.to raise_error(NoMethodError)
516
- end
517
-
518
- it 'double define' do
519
- klass = Class.new(MixedPropertiesTest) do
520
- property 'symbol'
521
- end
522
- instance = klass.new(symbol: 'one', 'symbol' => 'two')
523
- expect(instance[:symbol]).to eq('one')
524
- expect(instance['symbol']).to eq('two')
525
- end
526
-
527
- it 'assign' do
528
- subject['string'] = 'updated'
529
- expect(subject['string']).to eq('updated')
530
-
531
- expect { subject[:string] = 'updated' }.to raise_error(NoMethodError)
532
-
533
- subject[:symbol] = 'updated'
534
- expect(subject[:symbol]).to eq('updated')
535
-
536
- expect { subject['symbol'] = 'updated' }.to raise_error(NoMethodError)
537
- end
538
- end
539
-
540
- context 'Dynamic Dash Class' do
541
- it 'define property' do
542
- klass = Class.new(Hashie::Dash)
543
- my_property = 'my_property'
544
- my_orig = my_property.dup
545
-
546
- klass.property(my_property)
547
-
548
- expect(my_property).to eq(my_orig)
549
- end
550
- end
551
-
552
- context 'with method access' do
553
- class DashWithMethodAccess < Hashie::Dash
554
- include Hashie::Extensions::IndifferentAccess
555
- include Hashie::Extensions::MethodQuery
556
-
557
- property :test
558
- end
559
-
560
- subject(:dash) { DashWithMethodAccess.new(test: 'value') }
561
-
562
- describe '#test' do
563
- subject { dash.test }
564
-
565
- it { is_expected.to eq('value') }
566
- end
567
-
568
- describe '#test?' do
569
- subject { dash.test? }
570
-
571
- it { is_expected.to eq true }
572
- end
573
- end