hashie-pre 2.0.0.beta

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