hashie 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6033c13b11711b7a5951864fcec0108fc3f93acc
4
- data.tar.gz: e8f54e062f1593b56cc1c0ecea1b7cb5d43dd218
3
+ metadata.gz: f8151e8630508db39e56ae5ac2b5333913573f3f
4
+ data.tar.gz: de13b700b22ff856acabc7be36581e32f50ea43e
5
5
  SHA512:
6
- metadata.gz: a039149a6d895259bd07ab8a9761bc6d4414b69451c8b8cf4a334e2da448cfd336841d8a7fe481c2911503c4d448276a1d6005240ee0ac18b9b2db00fa0d09e9
7
- data.tar.gz: 918c0b19e472b27f71776573e241738fe0d9bd6f1d700b891bd36a7496a5f5410f638b4c98be62f2ce9fa58835e363086ac4322df4de3afc90542e15c30c295b
6
+ metadata.gz: 4a2ea8a3fd07a80a75bbb4a687258c0ae92f3a85fdef18178bc9319fc0bc122a1c2b7d7f47fdfd6ba378d3459165642cdf8610e491d7e6438254a289434ed75f
7
+ data.tar.gz: cf309ea2aec15071033875bcdeb4885f2a4755a0611856acdb44c4d92c07b2b8a739d5e81c42ba163014a0485788f7597ca055ae86f913105e6efea3f90b8519
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 3.1 (7/25/2014)
2
+
3
+ * [#169](https://github.com/intridea/hashie/pull/169): Hash#to_hash will also convert nested objects that implement to_hash - [@gregory](https://github.com/gregory).
4
+ * [#171](https://github.com/intridea/hashie/pull/171): Include Trash and Dash class name when raising `NoMethodError` - [@gregory](https://github.com/gregory).
5
+ * [#172](https://github.com/intridea/hashie/pull/172): Added Dash and Trash#update_attributes! - [@gregory](https://github.com/gregory).
6
+ * [#173](https://github.com/intridea/hashie/pull/173): Auto include Dash::IndifferentAccess when IndiferentAccess is included in Dash - [@gregory](https://github.com/gregory).
7
+ * [#174](https://github.com/intridea/hashie/pull/174): Fixed `from` and `transform_with` Trash features when IndifferentAccess is included - [@gregory](https://github.com/gregory).
8
+
1
9
  ## 3.0 (6/3/2014)
2
10
 
3
11
  **Note:** This version introduces several backward incompatible API changes. See [UPGRADING](UPGRADING.md) for details.
@@ -8,10 +16,8 @@
8
16
  * [#152](https://github.com/intridea/hashie/pull/152): Do not automatically stringify keys in Hashie::Hash#to_hash, pass `:stringify_keys` to achieve backward compatible behavior - [@dblock](https://github.com/dblock).
9
17
  * [#148](https://github.com/intridea/hashie/pull/148): Consolidated Hashie::Hash#stringify_keys implementation - [@dblock](https://github.com/dblock).
10
18
  * [#149](https://github.com/intridea/hashie/issues/149): Allow IgnoreUndeclared and DeepMerge to be used with undeclared properties - [@jhaesus](https://github.com/jhaesus).
11
- * Your contribution here.
12
19
 
13
20
  ## 2.1.1 (4/12/2014)
14
-
15
21
  * [#144](https://github.com/intridea/hashie/issues/144): Fixed regression invoking `to_hash` with no parameters - [@mbleigh](https://github.com/mbleigh).
16
22
 
17
23
  ## 2.1.0 (4/6/2014)
data/README.md CHANGED
@@ -12,7 +12,7 @@ $ gem install hashie
12
12
 
13
13
  ## Upgrading
14
14
 
15
- You're reading the documentation for the stable release of Hashie, 3.0. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
15
+ You're reading the documentation for the stable release of Hashie, 3.1. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
16
16
 
17
17
  ## Hash Extensions
18
18
 
@@ -81,8 +81,6 @@ This extension can be mixed in to instantly give you indifferent access to your
81
81
 
82
82
  A unique feature of Hashie's IndifferentAccess mixin is that it will inject itself recursively into subhashes *without* reinitializing the hash in question. This means you can safely merge together indifferent and non-indifferent hashes arbitrarily deeply without worrying about whether you'll be able to `hash[:other][:another]` properly.
83
83
 
84
- Use `Hashie::Extensions::Dash::IndifferentAccess` for instances of `Hashie::Dash`.
85
-
86
84
  ### IgnoreUndeclared
87
85
 
88
86
  This extension can be mixed in to silently ignore undeclared properties on initialization instead of raising an error. This is useful when using a Trash to capture a subset of a larger hash.
@@ -205,6 +203,11 @@ p.occupation # => 'Rubyist'
205
203
  p.email # => 'abc@def.com'
206
204
  p[:awesome] # => NoMethodError
207
205
  p[:occupation] # => 'Rubyist'
206
+ p.update_attributes!(name: 'Trudy', occupation: 'Evil')
207
+ p.occupation # => 'Evil'
208
+ p.name # => 'Trudy'
209
+ p.update_attributes!(occupation: nil)
210
+ p.occupation # => 'Rubyist'
208
211
  ```
209
212
 
210
213
  Properties defined as symbols are not the same thing as properties defined as strings.
data/lib/hashie/dash.rb CHANGED
@@ -91,7 +91,7 @@ module Hashie
91
91
  end
92
92
 
93
93
  initialize_attributes(attributes)
94
- assert_required_properties_set!
94
+ assert_required_attributes_set!
95
95
  end
96
96
 
97
97
  alias_method :_regular_reader, :[]
@@ -142,6 +142,19 @@ module Hashie
142
142
  self
143
143
  end
144
144
 
145
+ def update_attributes!(attributes)
146
+ initialize_attributes(attributes)
147
+
148
+ self.class.defaults.each_pair do |prop, value|
149
+ self[prop] = begin
150
+ value.dup
151
+ rescue TypeError
152
+ value
153
+ end if self[prop].nil?
154
+ end
155
+ assert_required_attributes_set!
156
+ end
157
+
145
158
  private
146
159
 
147
160
  def initialize_attributes(attributes)
@@ -151,27 +164,29 @@ module Hashie
151
164
  end
152
165
 
153
166
  def assert_property_exists!(property)
154
- unless self.class.property?(property)
155
- fail NoMethodError, "The property '#{property}' is not defined for this Dash."
156
- end
167
+ fail_no_property_error!(property) unless self.class.property?(property)
157
168
  end
158
169
 
159
- def assert_required_properties_set!
170
+ def assert_required_attributes_set!
160
171
  self.class.required_properties.each do |required_property|
161
172
  assert_property_set!(required_property)
162
173
  end
163
174
  end
164
175
 
165
176
  def assert_property_set!(property)
166
- if send(property).nil?
167
- fail ArgumentError, "The property '#{property}' is required for this Dash."
168
- end
177
+ fail_property_required_error!(property) if send(property).nil?
169
178
  end
170
179
 
171
180
  def assert_property_required!(property, value)
172
- if self.class.required?(property) && value.nil?
173
- fail ArgumentError, "The property '#{property}' is required for this Dash."
174
- end
181
+ fail_property_required_error!(property) if self.class.required?(property) && value.nil?
182
+ end
183
+
184
+ def fail_property_required_error!(property)
185
+ fail ArgumentError, "The property '#{property}' is required for #{self.class.name}."
186
+ end
187
+
188
+ def fail_no_property_error!(property)
189
+ fail NoMethodError, "The property '#{property}' is not defined for #{self.class.name}."
175
190
  end
176
191
  end
177
192
  end
@@ -14,6 +14,21 @@ module Hashie
14
14
  name = name.to_s
15
15
  !!properties.find { |property| property.to_s == name }
16
16
  end
17
+
18
+ def translation_exists?(name)
19
+ name = name.to_s
20
+ !!translations.keys.find { |key| key.to_s == name }
21
+ end
22
+
23
+ def transformed_property(property_name, value)
24
+ transform = transforms[property_name] || transforms[:"#{property_name}"]
25
+ transform.call(value)
26
+ end
27
+
28
+ def transformation_exists?(name)
29
+ name = name.to_s
30
+ !!transforms.keys.find { |key| key.to_s == name }
31
+ end
17
32
  end
18
33
  end
19
34
  end
@@ -24,6 +24,10 @@ module Hashie
24
24
  #
25
25
  module IndifferentAccess
26
26
  def self.included(base)
27
+ Hashie::Extensions::Dash::IndifferentAccess::ClassMethods.tap do |extension|
28
+ base.extend(extension) if base <= Hashie::Dash && !base.singleton_class.included_modules.include?(extension)
29
+ end
30
+
27
31
  base.class_eval do
28
32
  alias_method :regular_writer, :[]=
29
33
  alias_method :[]=, :indifferent_writer
data/lib/hashie/hash.rb CHANGED
@@ -31,7 +31,7 @@ module Hashie
31
31
  out[assignment_key] << (Hash === array_object ? flexibly_convert_to_hash(array_object, options) : array_object)
32
32
  end
33
33
  else
34
- out[assignment_key] = Hash === self[k] ? flexibly_convert_to_hash(self[k], options) : self[k]
34
+ out[assignment_key] = (Hash === self[k] || self[k].respond_to?(:to_hash)) ? flexibly_convert_to_hash(self[k], options) : self[k]
35
35
  end
36
36
  end
37
37
  out
data/lib/hashie/trash.rb CHANGED
@@ -42,15 +42,27 @@ module Hashie
42
42
  # Set a value on the Dash in a Hash-like way. Only works
43
43
  # on pre-existing properties.
44
44
  def []=(property, value)
45
- if self.class.translations.key? property
45
+ if self.class.translation_exists? property
46
46
  send("#{property}=", value)
47
- elsif self.class.transforms.key? property
48
- super property, self.class.transforms[property].call(value)
47
+ elsif self.class.transformation_exists? property
48
+ super property, self.class.transformed_property(property, value)
49
49
  elsif property_exists? property
50
50
  super
51
51
  end
52
52
  end
53
53
 
54
+ def self.transformed_property(property_name, value)
55
+ transforms[property_name].call(value)
56
+ end
57
+
58
+ def self.translation_exists?(name)
59
+ translations.key? name
60
+ end
61
+
62
+ def self.transformation_exists?(name)
63
+ transforms.key? name
64
+ end
65
+
54
66
  def self.permitted_input_keys
55
67
  @permitted_input_keys ||= properties.map { |property| inverse_translations.fetch property, property }
56
68
  end
@@ -76,9 +88,7 @@ module Hashie
76
88
  # Raises an NoMethodError if the property doesn't exist
77
89
  #
78
90
  def property_exists?(property)
79
- unless self.class.property?(property)
80
- fail NoMethodError, "The property '#{property}' is not defined for this Trash."
81
- end
91
+ fail_no_property_error!(property) unless self.class.property?(property)
82
92
  true
83
93
  end
84
94
 
@@ -1,3 +1,3 @@
1
1
  module Hashie
2
- VERSION = '3.0.0'
2
+ VERSION = '3.1.0'
3
3
  end
@@ -18,6 +18,14 @@ class DashNoRequiredTest < Hashie::Dash
18
18
  property :count, default: 0
19
19
  end
20
20
 
21
+ class DashWithCoercion < Hashie::Dash
22
+ include Hashie::Extensions::Coercion
23
+ property :person
24
+ property :city
25
+
26
+ coerce_key :person, ::DashNoRequiredTest
27
+ end
28
+
21
29
  class PropertyBangTest < Hashie::Dash
22
30
  property :important!
23
31
  end
@@ -35,6 +43,14 @@ class DeferredTest < Hashie::Dash
35
43
  end
36
44
 
37
45
  describe DashTest do
46
+ def property_required_error(property)
47
+ [ArgumentError, "The property '#{property}' is required for #{subject.class.name}."]
48
+ end
49
+
50
+ def no_property_error(property)
51
+ [NoMethodError, "The property '#{property}' is not defined for #{subject.class.name}."]
52
+ end
53
+
38
54
  subject { DashTest.new(first_name: 'Bob', email: 'bob@example.com') }
39
55
 
40
56
  it('subclasses Hashie::Hash') { should respond_to(:to_mash) }
@@ -60,30 +76,30 @@ describe DashTest do
60
76
  it { should_not respond_to(:nonexistent) }
61
77
 
62
78
  it 'errors out for a non-existent property' do
63
- expect { subject['nonexistent'] }.to raise_error(NoMethodError)
79
+ expect { subject['nonexistent'] }.to raise_error(*no_property_error('nonexistent'))
64
80
  end
65
81
 
66
82
  it 'errors out when attempting to set a required property to nil' do
67
- expect { subject.first_name = nil }.to raise_error(ArgumentError)
83
+ expect { subject.first_name = nil }.to raise_error(*property_required_error('first_name'))
68
84
  end
69
85
 
70
86
  context 'writing to properties' do
71
87
  it 'fails writing a required property to nil' do
72
- expect { subject.first_name = nil }.to raise_error(ArgumentError)
88
+ expect { subject.first_name = nil }.to raise_error(*property_required_error('first_name'))
73
89
  end
74
90
 
75
91
  it 'fails writing a required property to nil using []=' do
76
- expect { subject[:first_name] = nil }.to raise_error(ArgumentError)
92
+ expect { subject[:first_name] = nil }.to raise_error(*property_required_error('first_name'))
77
93
  end
78
94
 
79
95
  it 'fails writing to a non-existent property using []=' do
80
- expect { subject['nonexistent'] = 123 }.to raise_error(NoMethodError)
96
+ expect { subject['nonexistent'] = 123 }.to raise_error(*no_property_error('nonexistent'))
81
97
  end
82
98
 
83
99
  it 'works for an existing property using []=' do
84
100
  subject[:first_name] = 'Bob'
85
101
  expect(subject[:first_name]).to eq 'Bob'
86
- expect { subject['first_name'] }.to raise_error(NoMethodError)
102
+ expect { subject['first_name'] }.to raise_error(*no_property_error('first_name'))
87
103
  end
88
104
 
89
105
  it 'works for an existing property using a method call' do
@@ -94,7 +110,7 @@ describe DashTest do
94
110
 
95
111
  context 'reading from properties' do
96
112
  it 'fails reading from a non-existent property using []' do
97
- expect { subject['nonexistent'] }.to raise_error(NoMethodError)
113
+ expect { subject['nonexistent'] }.to raise_error(*no_property_error('nonexistent'))
98
114
  end
99
115
 
100
116
  it 'is able to retrieve properties through blocks' do
@@ -125,7 +141,7 @@ describe DashTest do
125
141
 
126
142
  describe '#new' do
127
143
  it 'fails with non-existent properties' do
128
- expect { described_class.new(bork: '') }.to raise_error(NoMethodError)
144
+ expect { described_class.new(bork: '') }.to raise_error(*no_property_error('bork'))
129
145
  end
130
146
 
131
147
  it 'sets properties that it is able to' do
@@ -144,7 +160,7 @@ describe DashTest do
144
160
  end
145
161
 
146
162
  it 'fails when required values are missing' do
147
- expect { DashTest.new }.to raise_error(ArgumentError)
163
+ expect { DashTest.new }.to raise_error(*property_required_error('first_name'))
148
164
  end
149
165
 
150
166
  it 'does not overwrite default values' do
@@ -168,11 +184,11 @@ describe DashTest do
168
184
  end
169
185
 
170
186
  it 'fails with non-existent properties' do
171
- expect { subject.merge(middle_name: 'James') }.to raise_error(NoMethodError)
187
+ expect { subject.merge(middle_name: 'James') }.to raise_error(*no_property_error('middle_name'))
172
188
  end
173
189
 
174
190
  it 'errors out when attempting to set a required property to nil' do
175
- expect { subject.merge(first_name: nil) }.to raise_error(ArgumentError)
191
+ expect { subject.merge(first_name: nil) }.to raise_error(*property_required_error('first_name'))
176
192
  end
177
193
 
178
194
  context 'given a block' do
@@ -266,6 +282,49 @@ describe DashTest do
266
282
  end
267
283
  end
268
284
  end
285
+
286
+ describe '#update_attributes!(params)' do
287
+ let(:params) { { first_name: 'Alice', email: 'alice@example.com' } }
288
+
289
+ context 'when there is coercion' do
290
+ let(:params_before) { { city: 'nyc', person: { first_name: 'Bob', email: 'bob@example.com' } } }
291
+ let(:params_after) { { city: 'sfo', person: { first_name: 'Alice', email: 'alice@example.com' } } }
292
+
293
+ subject { DashWithCoercion.new(params_before) }
294
+
295
+ it 'update the attributes' do
296
+ expect(subject.person.first_name).to eq params_before[:person][:first_name]
297
+ subject.update_attributes!(params_after)
298
+ expect(subject.person.first_name).to eq params_after[:person][:first_name]
299
+ end
300
+ end
301
+
302
+ it 'update the attributes' do
303
+ subject.update_attributes!(params)
304
+ expect(subject.first_name).to eq params[:first_name]
305
+ expect(subject.email).to eq params[:email]
306
+ expect(subject.count).to eq subject.class.defaults[:count]
307
+ end
308
+
309
+ context 'when required property is update to nil' do
310
+ let(:params) { { first_name: nil, email: 'alice@example.com' } }
311
+
312
+ it 'raise an ArgumentError' do
313
+ expect { subject.update_attributes!(params) }.to raise_error(ArgumentError)
314
+ end
315
+ end
316
+
317
+ context 'when a default property is update to nil' do
318
+ let(:params) { { count: nil, email: 'alice@example.com' } }
319
+
320
+ it 'set the property back to the default value' do
321
+ subject.update_attributes!(params)
322
+ expect(subject.email).to eq params[:email]
323
+ expect(subject.count).to eq subject.class.defaults[:count]
324
+ end
325
+ end
326
+ end
327
+
269
328
  end
270
329
 
271
330
  describe Hashie::Dash, 'inheritance' do
@@ -1,11 +1,37 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Hashie::Extensions::Dash::IndifferentAccess do
4
+ class TrashWithIndifferentAccess < Hashie::Trash
5
+ include Hashie::Extensions::Dash::IndifferentAccess
6
+ property :per_page, transform_with: lambda { |v| v.to_i }
7
+ property :total, from: :total_pages
8
+ end
9
+
4
10
  class DashWithIndifferentAccess < Hashie::Dash
5
11
  include Hashie::Extensions::Dash::IndifferentAccess
6
12
  property :name
7
13
  end
8
14
 
15
+ context 'when included in Trash' do
16
+ let(:params) { { per_page: '1', total_pages: 2 } }
17
+ subject { TrashWithIndifferentAccess.new(params) }
18
+
19
+ it 'gets the expected behaviour' do
20
+ expect(subject.per_page).to eq params[:per_page].to_i
21
+ expect(subject.total).to eq params[:total_pages]
22
+ end
23
+ end
24
+
25
+ context 'when included in Dash' do
26
+ let(:patch) { Hashie::Extensions::Dash::IndifferentAccess::ClassMethods }
27
+ let(:dash_class) { Class.new(Hashie::Dash) }
28
+
29
+ it 'extends with the patch once' do
30
+ expect(patch).to receive(:extended).with(dash_class).once
31
+ dash_class.send(:include, Hashie::Extensions::Dash::IndifferentAccess)
32
+ end
33
+ end
34
+
9
35
  context 'initialized with' do
10
36
  it 'string' do
11
37
  instance = DashWithIndifferentAccess.new('name' => 'Name')
@@ -26,6 +26,20 @@ describe Hashie::Extensions::IndifferentAccess do
26
26
  end
27
27
  end
28
28
 
29
+ class IndifferentHashWithDash < Hashie::Dash
30
+ include Hashie::Extensions::IndifferentAccess
31
+ property :foo
32
+ end
33
+
34
+ describe 'when included in dash' do
35
+ let(:params) { { foo: 'bar' } }
36
+ subject { IndifferentHashWithDash.new(params) }
37
+
38
+ it 'initialize with a symbol' do
39
+ expect(subject.foo).to eq params[:foo]
40
+ end
41
+ end
42
+
29
43
  shared_examples_for 'hash with indifferent access' do
30
44
  it 'is able to access via string or symbol' do
31
45
  h = subject.build(abc: 123)
@@ -55,4 +55,30 @@ describe Hash do
55
55
  h[:key] = BareCustomMash.new
56
56
  expect { h.to_hash }.not_to raise_error
57
57
  end
58
+
59
+ describe 'when the value is an object that respond_to to_hash' do
60
+ class ClassRespondsToHash
61
+ def to_hash(options = {})
62
+ Hashie::Hash['a' => 'hey', b: 'bar', 123 => 'bob', 'array' => [1, 2, 3]].to_hash(options)
63
+ end
64
+ end
65
+
66
+ it '#to_hash returns a hash with same keys' do
67
+ hash = Hashie::Hash['a' => 'hey', 123 => 'bob', 'array' => [1, 2, 3], subhash: ClassRespondsToHash.new]
68
+ stringified_hash = hash.to_hash
69
+ expect(stringified_hash).to eq('a' => 'hey', 123 => 'bob', 'array' => [1, 2, 3], subhash: { 'a' => 'hey', b: 'bar', 123 => 'bob', 'array' => [1, 2, 3] })
70
+ end
71
+
72
+ it '#to_hash with stringify_keys set to true returns a hash with stringified_keys' do
73
+ hash = Hashie::Hash['a' => 'hey', 123 => 'bob', 'array' => [1, 2, 3], subhash: ClassRespondsToHash.new]
74
+ symbolized_hash = hash.to_hash(stringify_keys: true)
75
+ expect(symbolized_hash).to eq('a' => 'hey', '123' => 'bob', 'array' => [1, 2, 3], 'subhash' => { 'a' => 'hey', 'b' => 'bar', '123' => 'bob', 'array' => [1, 2, 3] })
76
+ end
77
+
78
+ it '#to_hash with symbolize_keys set to true returns a hash with symbolized keys' do
79
+ hash = Hashie::Hash['a' => 'hey', 123 => 'bob', 'array' => [1, 2, 3], subhash: ClassRespondsToHash.new]
80
+ symbolized_hash = hash.to_hash(symbolize_keys: true)
81
+ expect(symbolized_hash).to eq(:a => 'hey', :"123" => 'bob', :array => [1, 2, 3], subhash: { :a => 'hey', :b => 'bar', :'123' => 'bob', :array => [1, 2, 3] })
82
+ end
83
+ end
58
84
  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.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-06-03 00:00:00.000000000 Z
12
+ date: 2014-06-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake