hashie-pre 2.0.0.beta

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.
@@ -0,0 +1,77 @@
1
+ require 'hashie/dash'
2
+
3
+ module Hashie
4
+ # A Trash is a 'translated' Dash where the keys can be remapped from a source
5
+ # hash.
6
+ #
7
+ # Trashes are useful when you need to read data from another application,
8
+ # such as a Java api, where the keys are named differently from how we would
9
+ # in Ruby.
10
+ class Trash < Hashie::Dash
11
+
12
+ # Defines a property on the Trash. Options are as follows:
13
+ #
14
+ # * <tt>:default</tt> - Specify a default value for this property, to be
15
+ # returned before a value is set on the property in a new Dash.
16
+ # * <tt>:from</tt> - Specify the original key name that will be write only.
17
+ # * <tt>:with</tt> - Specify a lambda to be used to convert value.
18
+ # * <tt>:transform_with</tt> - Specify a lambda to be used to convert value
19
+ # without using the :from option. It transform the property itself.
20
+ def self.property(property_name, options = {})
21
+ super
22
+
23
+ if options[:from]
24
+ if property_name.to_sym == options[:from].to_sym
25
+ raise ArgumentError, "Property name (#{property_name}) and :from option must not be the same"
26
+ end
27
+ translations << options[:from].to_sym
28
+ if options[:with].respond_to? :call
29
+ class_eval do
30
+ define_method "#{options[:from]}=" do |val|
31
+ self[property_name.to_sym] = options[:with].call(val)
32
+ end
33
+ end
34
+ else
35
+ class_eval <<-RUBY
36
+ def #{options[:from]}=(val)
37
+ self[:#{property_name}] = val
38
+ end
39
+ RUBY
40
+ end
41
+ elsif options[:transform_with].respond_to? :call
42
+ transforms[property_name.to_sym] = options[:transform_with]
43
+ end
44
+ end
45
+
46
+ # Set a value on the Dash in a Hash-like way. Only works
47
+ # on pre-existing properties.
48
+ def []=(property, value)
49
+ if self.class.translations.include? property.to_sym
50
+ send("#{property}=", value)
51
+ elsif self.class.transforms.key? property.to_sym
52
+ super property, self.class.transforms[property.to_sym].call(value)
53
+ elsif property_exists? property
54
+ super
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def self.translations
61
+ @translations ||= []
62
+ end
63
+
64
+ def self.transforms
65
+ @transforms ||= {}
66
+ end
67
+
68
+ # Raises an NoMethodError if the property doesn't exist
69
+ #
70
+ def property_exists?(property)
71
+ unless self.class.property?(property.to_sym)
72
+ raise NoMethodError, "The property '#{property}' is not defined for this Trash."
73
+ end
74
+ true
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,3 @@
1
+ module Hashie
2
+ VERSION = '2.0.0.beta'
3
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hashie::Clash do
4
+ before do
5
+ @c = Hashie::Clash.new
6
+ end
7
+
8
+ it 'should be able to set an attribute via method_missing' do
9
+ @c.foo('bar')
10
+ @c[:foo].should == 'bar'
11
+ end
12
+
13
+ it 'should be able to set multiple attributes' do
14
+ @c.foo('bar').baz('wok')
15
+ @c.should == {:foo => 'bar', :baz => 'wok'}
16
+ end
17
+
18
+ it 'should convert multiple arguments into an array' do
19
+ @c.foo(1, 2, 3)
20
+ @c[:foo].should == [1,2,3]
21
+ end
22
+
23
+ it 'should be able to use bang notation to create a new Clash on a key' do
24
+ @c.foo!
25
+ @c[:foo].should be_kind_of(Hashie::Clash)
26
+ end
27
+
28
+ it 'should be able to chain onto the new Clash when using bang notation' do
29
+ @c.foo!.bar('abc').baz(123)
30
+ @c.should == {:foo => {:bar => 'abc', :baz => 123}}
31
+ end
32
+
33
+ it 'should be able to jump back up to the parent in the chain with #_end!' do
34
+ @c.foo!.bar('abc')._end!.baz(123)
35
+ @c.should == {:foo => {:bar => 'abc'}, :baz => 123}
36
+ end
37
+
38
+ it 'should merge rather than replace existing keys' do
39
+ @c.where(:abc => 'def').where(:hgi => 123)
40
+ @c.should == {:where => {:abc => 'def', :hgi => 123}}
41
+ end
42
+ end
@@ -0,0 +1,215 @@
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 DashNoRequiredTest < Hashie::Dash
16
+ property :first_name
17
+ property :email
18
+ property :count, :default => 0
19
+ end
20
+
21
+ class Subclassed < DashTest
22
+ property :last_name, :required => true
23
+ end
24
+
25
+ describe DashTest do
26
+
27
+ subject { DashTest.new(:first_name => 'Bob', :email => 'bob@example.com') }
28
+
29
+ it('subclasses Hashie::Hash') { should respond_to(:to_mash) }
30
+
31
+ its(:to_s) { should == '#<DashTest count=0 email="bob@example.com" first_name="Bob">' }
32
+
33
+ it 'lists all set properties in inspect' do
34
+ subject.first_name = 'Bob'
35
+ subject.email = 'bob@example.com'
36
+ subject.inspect.should == '#<DashTest count=0 email="bob@example.com" first_name="Bob">'
37
+ end
38
+
39
+ its(:count) { should be_zero }
40
+
41
+ it { should respond_to(:first_name) }
42
+ it { should respond_to(:first_name=) }
43
+ it { should_not respond_to(:nonexistent) }
44
+
45
+ it 'errors out for a non-existent property' do
46
+ lambda { subject['nonexistent'] }.should raise_error(NoMethodError)
47
+ end
48
+
49
+ it 'errors out when attempting to set a required property to nil' do
50
+ lambda { subject.first_name = nil }.should raise_error(ArgumentError)
51
+ end
52
+
53
+ context 'writing to properties' do
54
+
55
+ it 'fails writing a required property to nil' do
56
+ lambda { subject.first_name = nil }.should raise_error(ArgumentError)
57
+ end
58
+
59
+ it 'fails writing a required property to nil using []=' do
60
+ lambda { subject['first_name'] = nil }.should raise_error(ArgumentError)
61
+ end
62
+
63
+ it 'fails writing to a non-existent property using []=' do
64
+ lambda { subject['nonexistent'] = 123 }.should raise_error(NoMethodError)
65
+ end
66
+
67
+ it 'works for an existing property using []=' do
68
+ subject['first_name'] = 'Bob'
69
+ subject['first_name'].should == 'Bob'
70
+ subject[:first_name].should == 'Bob'
71
+ end
72
+
73
+ it 'works for an existing property using a method call' do
74
+ subject.first_name = 'Franklin'
75
+ subject.first_name.should == 'Franklin'
76
+ end
77
+ end
78
+
79
+ context 'reading from properties' do
80
+ it 'fails reading from a non-existent property using []' do
81
+ lambda { subject['nonexistent'] }.should raise_error(NoMethodError)
82
+ end
83
+
84
+ it "should be able to retrieve properties through blocks" do
85
+ subject["first_name"] = "Aiden"
86
+ value = nil
87
+ subject.[]("first_name") { |v| value = v }
88
+ value.should == "Aiden"
89
+ end
90
+
91
+ it "should be able to retrieve properties through blocks with method calls" do
92
+ subject["first_name"] = "Frodo"
93
+ value = nil
94
+ subject.first_name { |v| value = v }
95
+ value.should == "Frodo"
96
+ end
97
+ end
98
+
99
+ describe '.new' do
100
+ it 'fails with non-existent properties' do
101
+ lambda { described_class.new(:bork => '') }.should raise_error(NoMethodError)
102
+ end
103
+
104
+ it 'should set properties that it is able to' do
105
+ obj = described_class.new :first_name => 'Michael'
106
+ obj.first_name.should == 'Michael'
107
+ end
108
+
109
+ it 'accepts nil' do
110
+ lambda { DashNoRequiredTest.new(nil) }.should_not raise_error
111
+ end
112
+
113
+ it 'accepts block to define a global default' do
114
+ obj = described_class.new { |hash, key| key.to_s.upcase }
115
+ obj.first_name.should == 'FIRST_NAME'
116
+ obj.count.should be_zero
117
+ end
118
+
119
+ it "fails when required values are missing" do
120
+ expect { DashTest.new }.to raise_error(ArgumentError)
121
+ end
122
+
123
+ end
124
+
125
+ describe 'properties' do
126
+ it 'lists defined properties' do
127
+ described_class.properties.should == Set.new([:first_name, :email, :count])
128
+ end
129
+
130
+ it 'checks if a property exists' do
131
+ described_class.property?('first_name').should be_true
132
+ described_class.property?(:first_name).should be_true
133
+ end
134
+
135
+ it 'checks if a property is required' do
136
+ described_class.required?('first_name').should be_true
137
+ described_class.required?(:first_name).should be_true
138
+ end
139
+
140
+ it 'doesnt include property from subclass' do
141
+ described_class.property?(:last_name).should be_false
142
+ end
143
+
144
+ it 'lists declared defaults' do
145
+ described_class.defaults.should == { :count => 0 }
146
+ end
147
+ end
148
+ end
149
+
150
+ describe Hashie::Dash, 'inheritance' do
151
+ before do
152
+ @top = Class.new(Hashie::Dash)
153
+ @middle = Class.new(@top)
154
+ @bottom = Class.new(@middle)
155
+ end
156
+
157
+ it 'reports empty properties when nothing defined' do
158
+ @top.properties.should be_empty
159
+ @top.defaults.should be_empty
160
+ end
161
+
162
+ it 'inherits properties downwards' do
163
+ @top.property :echo
164
+ @middle.properties.should include(:echo)
165
+ @bottom.properties.should include(:echo)
166
+ end
167
+
168
+ it 'doesnt inherit properties upwards' do
169
+ @middle.property :echo
170
+ @top.properties.should_not include(:echo)
171
+ @bottom.properties.should include(:echo)
172
+ end
173
+
174
+ it 'allows overriding a default on an existing property' do
175
+ @top.property :echo
176
+ @middle.property :echo, :default => 123
177
+ @bottom.properties.to_a.should == [:echo]
178
+ @bottom.new.echo.should == 123
179
+ end
180
+
181
+ it 'allows clearing an existing default' do
182
+ @top.property :echo
183
+ @middle.property :echo, :default => 123
184
+ @bottom.property :echo
185
+ @bottom.properties.to_a.should == [:echo]
186
+ @bottom.new.echo.should be_nil
187
+ end
188
+
189
+ it 'should allow nil defaults' do
190
+ @bottom.property :echo, :default => nil
191
+ @bottom.new.should have_key('echo')
192
+ end
193
+
194
+ end
195
+
196
+ describe Subclassed do
197
+
198
+ subject { Subclassed.new(:first_name => 'Bob', :last_name => 'McNob', :email => 'bob@example.com') }
199
+
200
+ its(:count) { should be_zero }
201
+
202
+ it { should respond_to(:first_name) }
203
+ it { should respond_to(:first_name=) }
204
+ it { should respond_to(:last_name) }
205
+ it { should respond_to(:last_name=) }
206
+
207
+ it 'has one additional property' do
208
+ described_class.property?(:last_name).should be_true
209
+ end
210
+
211
+ it "didn't override superclass inheritance logic" do
212
+ described_class.instance_variable_get('@inheritance_test').should be_true
213
+ end
214
+
215
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hashie::Extensions::Coercion do
4
+ class Initializable
5
+ def initialize(obj, coerced = false)
6
+ @coerced = coerced
7
+ @value = obj.class.to_s
8
+ end
9
+ def coerced?; @coerced end
10
+ attr_reader :value
11
+ end
12
+
13
+ class Coercable < Initializable
14
+ def self.coerce(obj)
15
+ new(obj, true)
16
+ end
17
+ end
18
+
19
+ before(:each) do
20
+ class ExampleCoercableHash < Hash; include Hashie::Extensions::Coercion end
21
+ end
22
+ subject { ExampleCoercableHash }
23
+ let(:instance){ subject.new }
24
+
25
+ describe '.coerce_key' do
26
+ it { subject.should be_respond_to(:coerce_key) }
27
+
28
+ it 'should run through coerce on a specified key' do
29
+ subject.coerce_key :foo, Coercable
30
+
31
+ instance[:foo] = "bar"
32
+ instance[:foo].should be_coerced
33
+ end
34
+
35
+ it 'should just call #new if no coerce method is available' do
36
+ subject.coerce_key :foo, Initializable
37
+
38
+ instance[:foo] = "bar"
39
+ instance[:foo].value.should == "String"
40
+ instance[:foo].should_not be_coerced
41
+ end
42
+ end
43
+
44
+ describe '.coerce_value' do
45
+ context 'with :strict => true' do
46
+ it 'should coerce any value of the exact right class' do
47
+ subject.coerce_value String, Coercable
48
+
49
+ instance[:foo] = "bar"
50
+ instance[:bar] = "bax"
51
+ instance[:foo].should be_coerced
52
+ instance[:bar].should be_coerced
53
+ end
54
+
55
+ it 'should not coerce superclasses' do
56
+ klass = Class.new(String)
57
+ subject.coerce_value klass, Coercable
58
+
59
+ instance[:foo] = "bar"
60
+ instance[:foo].should_not be_kind_of(Coercable)
61
+ instance[:foo] = klass.new
62
+ instance[:foo].should be_kind_of(Coercable)
63
+ end
64
+ end
65
+ end
66
+
67
+ after(:each) do
68
+ Object.send(:remove_const, :ExampleCoercableHash)
69
+ end
70
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hashie::Extensions::IndifferentAccess do
4
+ class IndifferentHash < Hash
5
+ include Hashie::Extensions::MergeInitializer
6
+ include Hashie::Extensions::IndifferentAccess
7
+ end
8
+ subject{ IndifferentHash }
9
+
10
+ it 'should be able to access via string or symbol' do
11
+ h = subject.new(:abc => 123)
12
+ h[:abc].should == 123
13
+ h['abc'].should == 123
14
+ end
15
+
16
+ describe '#values_at' do
17
+ it 'should indifferently find values' do
18
+ h = subject.new(:foo => 'bar', 'baz' => 'qux')
19
+ h.values_at('foo', :baz).should == %w(bar qux)
20
+ end
21
+ end
22
+
23
+ describe '#fetch' do
24
+ it 'should work like normal fetch, but indifferent' do
25
+ h = subject.new(:foo => 'bar')
26
+ h.fetch(:foo).should == h.fetch('foo')
27
+ h.fetch(:foo).should == 'bar'
28
+ end
29
+ end
30
+
31
+ describe '#delete' do
32
+ it 'should delete indifferently' do
33
+ h = subject.new(:foo => 'bar', 'baz' => 'qux')
34
+ h.delete('foo')
35
+ h.delete(:baz)
36
+ h.should be_empty
37
+ end
38
+ end
39
+
40
+ describe '#key?' do
41
+ it 'should find it indifferently' do
42
+ h = subject.new(:foo => 'bar')
43
+ h.should be_key(:foo)
44
+ h.should be_key('foo')
45
+ end
46
+ end
47
+
48
+ describe '#update' do
49
+ subject{ IndifferentHash.new(:foo => 'bar') }
50
+ it 'should allow keys to be indifferent still' do
51
+ subject.update(:baz => 'qux')
52
+ subject['foo'].should == 'bar'
53
+ subject['baz'].should == 'qux'
54
+ end
55
+
56
+ it 'should recursively inject indifference into sub-hashes' do
57
+ subject.update(:baz => {:qux => 'abc'})
58
+ subject['baz']['qux'].should == 'abc'
59
+ end
60
+
61
+ it 'should not change the ancestors of the injected object class' do
62
+ subject.update(:baz => {:qux => 'abc'})
63
+ Hash.new.should_not be_respond_to(:indifferent_access?)
64
+ end
65
+ end
66
+ end