hashie 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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