sam-dm-core 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. data/.autotest +26 -0
  2. data/CONTRIBUTING +51 -0
  3. data/FAQ +92 -0
  4. data/History.txt +145 -0
  5. data/MIT-LICENSE +22 -0
  6. data/Manifest.txt +125 -0
  7. data/QUICKLINKS +12 -0
  8. data/README.txt +143 -0
  9. data/Rakefile +30 -0
  10. data/SPECS +63 -0
  11. data/TODO +1 -0
  12. data/lib/dm-core.rb +224 -0
  13. data/lib/dm-core/adapters.rb +4 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
  15. data/lib/dm-core/adapters/data_objects_adapter.rb +707 -0
  16. data/lib/dm-core/adapters/mysql_adapter.rb +136 -0
  17. data/lib/dm-core/adapters/postgres_adapter.rb +188 -0
  18. data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  19. data/lib/dm-core/associations.rb +199 -0
  20. data/lib/dm-core/associations/many_to_many.rb +147 -0
  21. data/lib/dm-core/associations/many_to_one.rb +107 -0
  22. data/lib/dm-core/associations/one_to_many.rb +309 -0
  23. data/lib/dm-core/associations/one_to_one.rb +61 -0
  24. data/lib/dm-core/associations/relationship.rb +218 -0
  25. data/lib/dm-core/associations/relationship_chain.rb +81 -0
  26. data/lib/dm-core/auto_migrations.rb +113 -0
  27. data/lib/dm-core/collection.rb +638 -0
  28. data/lib/dm-core/dependency_queue.rb +31 -0
  29. data/lib/dm-core/hook.rb +11 -0
  30. data/lib/dm-core/identity_map.rb +45 -0
  31. data/lib/dm-core/is.rb +16 -0
  32. data/lib/dm-core/logger.rb +232 -0
  33. data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  34. data/lib/dm-core/migrator.rb +29 -0
  35. data/lib/dm-core/model.rb +471 -0
  36. data/lib/dm-core/naming_conventions.rb +84 -0
  37. data/lib/dm-core/property.rb +673 -0
  38. data/lib/dm-core/property_set.rb +162 -0
  39. data/lib/dm-core/query.rb +625 -0
  40. data/lib/dm-core/repository.rb +159 -0
  41. data/lib/dm-core/resource.rb +637 -0
  42. data/lib/dm-core/scope.rb +58 -0
  43. data/lib/dm-core/support.rb +7 -0
  44. data/lib/dm-core/support/array.rb +13 -0
  45. data/lib/dm-core/support/assertions.rb +8 -0
  46. data/lib/dm-core/support/errors.rb +23 -0
  47. data/lib/dm-core/support/kernel.rb +7 -0
  48. data/lib/dm-core/support/symbol.rb +41 -0
  49. data/lib/dm-core/transaction.rb +267 -0
  50. data/lib/dm-core/type.rb +160 -0
  51. data/lib/dm-core/type_map.rb +80 -0
  52. data/lib/dm-core/types.rb +19 -0
  53. data/lib/dm-core/types/boolean.rb +7 -0
  54. data/lib/dm-core/types/discriminator.rb +34 -0
  55. data/lib/dm-core/types/object.rb +24 -0
  56. data/lib/dm-core/types/paranoid_boolean.rb +34 -0
  57. data/lib/dm-core/types/paranoid_datetime.rb +33 -0
  58. data/lib/dm-core/types/serial.rb +9 -0
  59. data/lib/dm-core/types/text.rb +10 -0
  60. data/lib/dm-core/version.rb +3 -0
  61. data/script/all +5 -0
  62. data/script/performance.rb +203 -0
  63. data/script/profile.rb +87 -0
  64. data/spec/integration/association_spec.rb +1371 -0
  65. data/spec/integration/association_through_spec.rb +203 -0
  66. data/spec/integration/associations/many_to_many_spec.rb +449 -0
  67. data/spec/integration/associations/many_to_one_spec.rb +163 -0
  68. data/spec/integration/associations/one_to_many_spec.rb +151 -0
  69. data/spec/integration/auto_migrations_spec.rb +398 -0
  70. data/spec/integration/collection_spec.rb +1069 -0
  71. data/spec/integration/data_objects_adapter_spec.rb +32 -0
  72. data/spec/integration/dependency_queue_spec.rb +58 -0
  73. data/spec/integration/model_spec.rb +127 -0
  74. data/spec/integration/mysql_adapter_spec.rb +85 -0
  75. data/spec/integration/postgres_adapter_spec.rb +731 -0
  76. data/spec/integration/property_spec.rb +233 -0
  77. data/spec/integration/query_spec.rb +506 -0
  78. data/spec/integration/repository_spec.rb +57 -0
  79. data/spec/integration/resource_spec.rb +475 -0
  80. data/spec/integration/sqlite3_adapter_spec.rb +352 -0
  81. data/spec/integration/sti_spec.rb +208 -0
  82. data/spec/integration/strategic_eager_loading_spec.rb +138 -0
  83. data/spec/integration/transaction_spec.rb +75 -0
  84. data/spec/integration/type_spec.rb +271 -0
  85. data/spec/lib/logging_helper.rb +18 -0
  86. data/spec/lib/mock_adapter.rb +27 -0
  87. data/spec/lib/model_loader.rb +91 -0
  88. data/spec/lib/publicize_methods.rb +28 -0
  89. data/spec/models/vehicles.rb +34 -0
  90. data/spec/models/zoo.rb +47 -0
  91. data/spec/spec.opts +3 -0
  92. data/spec/spec_helper.rb +86 -0
  93. data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
  94. data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
  95. data/spec/unit/adapters/data_objects_adapter_spec.rb +628 -0
  96. data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
  97. data/spec/unit/associations/many_to_many_spec.rb +17 -0
  98. data/spec/unit/associations/many_to_one_spec.rb +152 -0
  99. data/spec/unit/associations/one_to_many_spec.rb +393 -0
  100. data/spec/unit/associations/one_to_one_spec.rb +7 -0
  101. data/spec/unit/associations/relationship_spec.rb +71 -0
  102. data/spec/unit/associations_spec.rb +242 -0
  103. data/spec/unit/auto_migrations_spec.rb +111 -0
  104. data/spec/unit/collection_spec.rb +182 -0
  105. data/spec/unit/data_mapper_spec.rb +35 -0
  106. data/spec/unit/identity_map_spec.rb +126 -0
  107. data/spec/unit/is_spec.rb +80 -0
  108. data/spec/unit/migrator_spec.rb +33 -0
  109. data/spec/unit/model_spec.rb +339 -0
  110. data/spec/unit/naming_conventions_spec.rb +36 -0
  111. data/spec/unit/property_set_spec.rb +83 -0
  112. data/spec/unit/property_spec.rb +753 -0
  113. data/spec/unit/query_spec.rb +530 -0
  114. data/spec/unit/repository_spec.rb +93 -0
  115. data/spec/unit/resource_spec.rb +626 -0
  116. data/spec/unit/scope_spec.rb +142 -0
  117. data/spec/unit/transaction_spec.rb +493 -0
  118. data/spec/unit/type_map_spec.rb +114 -0
  119. data/spec/unit/type_spec.rb +119 -0
  120. data/tasks/ci.rb +68 -0
  121. data/tasks/dm.rb +63 -0
  122. data/tasks/doc.rb +20 -0
  123. data/tasks/gemspec.rb +23 -0
  124. data/tasks/hoe.rb +46 -0
  125. data/tasks/install.rb +20 -0
  126. metadata +216 -0
@@ -0,0 +1,36 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ describe "DataMapper::NamingConventions" do
4
+ describe "Resource" do
5
+ it "should coerce a string into the Underscored convention" do
6
+ DataMapper::NamingConventions::Resource::Underscored.call('User').should == 'user'
7
+ DataMapper::NamingConventions::Resource::Underscored.call('UserAccountSetting').should == 'user_account_setting'
8
+ end
9
+
10
+ it "should coerce a string into the UnderscoredAndPluralized convention" do
11
+ DataMapper::NamingConventions::Resource::UnderscoredAndPluralized.call('User').should == 'users'
12
+ DataMapper::NamingConventions::Resource::UnderscoredAndPluralized.call('UserAccountSetting').should == 'user_account_settings'
13
+ end
14
+
15
+ it "should coerce a string into the UnderscoredAndPluralized convention joining namespace with underscore" do
16
+ DataMapper::NamingConventions::Resource::UnderscoredAndPluralized.call('Model::User').should == 'model_users'
17
+ DataMapper::NamingConventions::Resource::UnderscoredAndPluralized.call('Model::UserAccountSetting').should == 'model_user_account_settings'
18
+ end
19
+
20
+ it "should coerce a string into the UnderscoredAndPluralizedWithoutModule convention" do
21
+ DataMapper::NamingConventions::Resource::UnderscoredAndPluralizedWithoutModule.call('Model::User').should == 'users'
22
+ DataMapper::NamingConventions::Resource::UnderscoredAndPluralizedWithoutModule.call('Model::UserAccountSetting').should == 'user_account_settings'
23
+ end
24
+
25
+ it "should coerce a string into the Yaml convention" do
26
+ DataMapper::NamingConventions::Resource::Yaml.call('UserSetting').should == 'user_settings.yaml'
27
+ DataMapper::NamingConventions::Resource::Yaml.call('User').should == 'users.yaml'
28
+ end
29
+ end
30
+
31
+ describe "Field" do
32
+ it "should accept a property as input" do
33
+ DataMapper::NamingConventions::Field::Underscored.call(Article.blog_id).should == 'blog_id'
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,83 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ class Icon
4
+ include DataMapper::Resource
5
+
6
+ property :id, Serial
7
+ property :name, String
8
+ property :width, Integer, :lazy => true
9
+ property :height, Integer, :lazy => true
10
+ end
11
+
12
+ class Boat
13
+ include DataMapper::Resource
14
+ property :name, String #not lazy
15
+ property :text, DataMapper::Types::Text #Lazy by default
16
+ property :notes, String, :lazy => true
17
+ property :a1, String, :lazy => [:ctx_a,:ctx_c]
18
+ property :a2, String, :lazy => [:ctx_a,:ctx_b]
19
+ property :a3, String, :lazy => [:ctx_a]
20
+ property :b1, String, :lazy => [:ctx_b]
21
+ property :b2, String, :lazy => [:ctx_b]
22
+ property :b3, String, :lazy => [:ctx_b]
23
+ end
24
+
25
+ describe DataMapper::PropertySet do
26
+ before :each do
27
+ @properties = Icon.properties(:default)
28
+ end
29
+
30
+ it "#slice should find properties" do
31
+ @properties.slice(:name, 'width').should have(2).entries
32
+ end
33
+
34
+ it "#select should find properties" do
35
+ @properties.select { |property| property.primitive == Integer }.should have(3).entries
36
+ end
37
+
38
+ it "#[] should find properties by name (Symbol or String)" do
39
+ default_properties = [ :id, 'name', :width, 'height' ]
40
+ @properties.each_with_index do |property,i|
41
+ property.should == @properties[default_properties[i]]
42
+ end
43
+ end
44
+
45
+ it "should provide defaults" do
46
+ @properties.defaults.should have(2).entries
47
+ @properties.should have(4).entries
48
+ end
49
+
50
+ it 'should add a property for lazy loading to the :default context if a context is not supplied' do
51
+ Boat.properties(:default).lazy_context(:default).length.should == 2 # text & notes
52
+ end
53
+
54
+ it 'should return a list of contexts that a given field is in' do
55
+ props = Boat.properties(:default)
56
+ set = props.property_contexts(:a1)
57
+ set.include?(:ctx_a).should == true
58
+ set.include?(:ctx_c).should == true
59
+ set.include?(:ctx_b).should == false
60
+ end
61
+
62
+ it 'should return a list of expanded fields that should be loaded with a given field' do
63
+ props = Boat.properties(:default)
64
+ set = props.lazy_load_context(:a2)
65
+ expect = [:a1,:a2,:a3,:b1,:b2,:b3]
66
+ expect.should == set.sort! {|a,b| a.to_s <=> b.to_s}
67
+ end
68
+
69
+ describe 'when dup\'ed' do
70
+ it 'should duplicate the @entries ivar' do
71
+ @properties.dup.entries.should_not equal(@properties.entries)
72
+ end
73
+
74
+ it 'should reinitialize @properties_for' do
75
+ # force @properties_for to hold a property
76
+ Icon.properties(:default)[:name].should_not be_nil
77
+ @properties = Icon.properties(:default)
78
+
79
+ @properties.instance_variable_get("@property_for").should_not be_empty
80
+ @properties.dup.instance_variable_get("@property_for").should be_empty
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,753 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ describe DataMapper::Property do
4
+ before :each do
5
+ Object.send(:remove_const, :Zoo) if defined?(Zoo)
6
+ class Zoo
7
+ include DataMapper::Resource
8
+
9
+ property :id, DataMapper::Types::Serial
10
+ end
11
+
12
+ Object.send(:remove_const, :Name) if defined?(Name)
13
+ class Name < DataMapper::Type
14
+ primitive String
15
+ track :hash
16
+
17
+ def self.load(value, property)
18
+ value.split(", ").reverse
19
+ end
20
+
21
+ def self.dump(value, property)
22
+ value && value.reverse.join(", ")
23
+ end
24
+
25
+ def self.typecast(value, property)
26
+ value
27
+ end
28
+ end
29
+
30
+ Object.send(:remove_const, :Tomato) if defined?(Tomato)
31
+ class Tomato
32
+ include DataMapper::Resource
33
+ end
34
+ end
35
+
36
+ describe '.new' do
37
+ [ Float, BigDecimal ].each do |primitive|
38
+ describe "with a #{primitive} primitive" do
39
+ it 'should raise an ArgumentError if precision is 0' do
40
+ lambda {
41
+ Zoo.class_eval <<-RUBY
42
+ property :test, #{primitive}, :precision => 0
43
+ RUBY
44
+ }.should raise_error(ArgumentError)
45
+ end
46
+
47
+ it "raises an ArgumentError if precision is less than 0" do
48
+ lambda {
49
+ Zoo.class_eval <<-RUBY
50
+ property :test, #{primitive}, :precision => -1
51
+ RUBY
52
+ }.should raise_error(ArgumentError)
53
+ end
54
+
55
+ it 'should raise an ArgumentError if scale is less than 0' do
56
+ lambda {
57
+ Zoo.class_eval <<-RUBY
58
+ property :test, #{primitive}, :scale => -1
59
+ RUBY
60
+ }.should raise_error(ArgumentError)
61
+ end
62
+
63
+ it 'should raise an ArgumentError if precision is less than scale' do
64
+ lambda {
65
+ Zoo.class_eval <<-RUBY
66
+ property :test, #{primitive}, :precision => 1, :scale => 2
67
+ RUBY
68
+ }.should raise_error(ArgumentError)
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ describe '#field' do
75
+ before(:each) do
76
+ Zoo.class_eval do
77
+ property :location, String, :field => "City"
78
+
79
+ repository(:mock) do
80
+ property :location, String, :field => "MockCity"
81
+ end
82
+ end
83
+ end
84
+
85
+ it 'should accept a custom field' do
86
+ Zoo.properties[:location].field.should == 'City'
87
+ end
88
+
89
+ # How is this supposed to work?
90
+ it 'should use repository name if passed in' do
91
+ pending
92
+ Zoo.properties[:location].field(:mock).should == 'MockCity'
93
+ end
94
+ end
95
+
96
+ describe '#get' do
97
+ before do
98
+ Zoo.class_eval do
99
+ property :name, String, :default => "San Diego"
100
+ property :address, String
101
+ end
102
+ @resource = Zoo.new
103
+ end
104
+
105
+ describe 'when setting the default on initial access' do
106
+ it 'should set the ivar to the default' do
107
+ @resource.name.should == 'San Diego'
108
+ end
109
+
110
+ it 'should set the original value to nil' do
111
+ @resource.original_values[:name].should == nil
112
+ end
113
+ end
114
+
115
+ it "should not reload the default if you set the property to nil" do
116
+ @resource.name = nil
117
+ @resource.name.should == nil
118
+ end
119
+ end
120
+
121
+ describe '#get, when tracking via :hash' do
122
+ before do
123
+ Zoo.class_eval do
124
+ property :name, String, :lazy => true, :track => :hash
125
+ end
126
+ Zoo.auto_migrate!
127
+ @resource = Zoo.create(:name => "San Diego")
128
+ end
129
+
130
+ describe 'when setting the default on initial access' do
131
+ it 'should set the ivar to the default' do
132
+ @resource.name.should == "San Diego"
133
+ end
134
+
135
+ it 'should set the original value to nil' do
136
+ @resource.name
137
+ @resource.original_values[:name].should == "San Diego".hash
138
+ end
139
+
140
+ it "should know it's dirty if a change was made to the object" do
141
+ @resource.name.upcase!
142
+ @resource.should be_dirty
143
+ end
144
+ end
145
+ end
146
+
147
+ describe '#get, when tracking via :get' do
148
+ before do
149
+ Zoo.class_eval do
150
+ property :name, String
151
+ end
152
+ Zoo.auto_migrate!
153
+ @resource = Zoo.create(:name => "San Diego")
154
+ end
155
+
156
+ describe 'when setting the default on initial access' do
157
+ it 'should set the ivar to the default' do
158
+ @resource.name.should == "San Diego"
159
+ end
160
+
161
+ it 'should set the original value to "San Diego"' do
162
+ @resource.name
163
+ @resource.original_values[:name].should == "San Diego"
164
+ end
165
+ end
166
+
167
+ it "should know it's dirty if a change was made to the object" do
168
+ @resource.name.upcase!
169
+ @resource.name
170
+ @resource.should be_dirty
171
+ @resource.original_values[:name].should == "San Diego"
172
+ end
173
+ end
174
+
175
+ describe 'with Proc defaults' do
176
+ it "calls the proc" do
177
+ Zoo.class_eval do
178
+ property :name, String, :default => proc {|r,p| "San Diego"}
179
+ property :address, String
180
+ end
181
+
182
+ Zoo.new.name.should == "San Diego"
183
+ end
184
+
185
+ it "provides the resource to the proc" do
186
+ Zoo.class_eval do
187
+ property :name, String, :default => proc {|r,p| r.address}
188
+ property :address, String
189
+ end
190
+
191
+ zoo = Zoo.new
192
+ zoo.address = "San Diego"
193
+ zoo.name.should == "San Diego"
194
+ end
195
+
196
+ it "provides the property to the proc" do
197
+ Zoo.class_eval do
198
+ property :name, String, :default => proc {|r,p| p.name.to_s}
199
+ end
200
+
201
+ zoo = Zoo.new
202
+ zoo.name.should == "name"
203
+ end
204
+ end
205
+
206
+
207
+ describe '#get!' do
208
+ it 'should get the resource' do
209
+ Zoo.class_eval do
210
+ property :name, String
211
+ end
212
+
213
+ resource = Zoo.new(:name => "Portland Zoo")
214
+ resource.name.should == "Portland Zoo"
215
+ end
216
+ end
217
+
218
+ describe '#set' do
219
+ before(:each) do
220
+ Zoo.class_eval do
221
+ property :name, String
222
+ property :age, Integer
223
+ property :description, String, :lazy => true
224
+ end
225
+ Zoo.auto_migrate!
226
+ Zoo.create(:name => "San Diego Zoo", :age => 888,
227
+ :description => "Great Zoo")
228
+ @resource = Zoo.new
229
+ end
230
+
231
+ it 'should typecast the value' do
232
+ @resource.age = "888"
233
+ @resource.age.should == 888
234
+ end
235
+
236
+ it "should lazy load itself first" do
237
+ resource = Zoo.first
238
+ resource.description = "Still a Great Zoo"
239
+ resource.original_values[:description].should == "Great Zoo"
240
+ end
241
+
242
+ it "should only set original_values once" do
243
+ resource = Zoo.first
244
+ resource.description = "Still a Great Zoo"
245
+ resource.description = "What can I say. This is one great Zoo"
246
+ resource.original_values[:description].should == "Great Zoo"
247
+ end
248
+ end
249
+
250
+ describe '#set!' do
251
+ before do
252
+ Zoo.class_eval do
253
+ property :name, String
254
+ property :age, Integer
255
+ end
256
+ end
257
+
258
+ it 'should set the resource' do
259
+ resource = Zoo.new
260
+ resource.name = "Seattle Zoo"
261
+ resource.name.should == "Seattle Zoo"
262
+ end
263
+ end
264
+
265
+ # What is this for?
266
+ # ---
267
+ # it "should evaluate two similar properties as equal" do
268
+ # p1 = DataMapper::Property.new(Zoo, :name, String, { :size => 30 })
269
+ # p2 = DataMapper::Property.new(Zoo, :name, String, { :size => 30 })
270
+ # p3 = DataMapper::Property.new(Zoo, :title, String, { :size => 30 })
271
+ # p1.eql?(p2).should == true
272
+ # p1.hash.should == p2.hash
273
+ # p1.eql?(p3).should == false
274
+ # p1.hash.should_not == p3.hash
275
+ # end
276
+
277
+ it "should create a String property" do
278
+ Zoo.class_eval do
279
+ property :name, String, :size => 30
280
+ end
281
+
282
+ resource = Zoo.new
283
+ resource.name = 100
284
+ resource.name.should == "100"
285
+ end
286
+
287
+ it "should not have key that is lazy" do
288
+ Zoo.class_eval do
289
+ property :id, DataMapper::Types::Text, :key => true
290
+ property :name, String, :lazy => true
291
+ end
292
+ Zoo.auto_migrate!
293
+
294
+ Zoo.create(:id => "100", :name => "San Diego Zoo")
295
+ zoo = Zoo.first
296
+
297
+ # Do we mean for attribute_loaded? to be public?
298
+ zoo.attribute_loaded?(:id).should == true
299
+ end
300
+
301
+ it "should lazily load other non-loaded, non-lazy fields" do
302
+ # This somewhat contorted setup is to successfully test that
303
+ # the list of eager properties to be loaded when it's initially
304
+ # missing is, in fact, repository-scoped
305
+ Zoo.class_eval do
306
+ property :id, DataMapper::Types::Serial
307
+ property :name, String, :lazy => true
308
+ property :address, String, :lazy => true
309
+
310
+ repository(:default2) do
311
+ property :name, String
312
+ property :address, String
313
+ end
314
+ end
315
+
316
+ repository(:default2) do
317
+ Zoo.auto_migrate!
318
+ Zoo.create(:name => "San Diego Zoo", :address => "San Diego")
319
+ end
320
+ repository(:default2) do
321
+ zoo = Zoo.first(:fields => [:id])
322
+
323
+ zoo.attribute_loaded?(:name).should == false
324
+ zoo.attribute_loaded?(:address).should == false
325
+ zoo.name
326
+ zoo.attribute_loaded?(:name).should == true
327
+ zoo.attribute_loaded?(:address).should == true
328
+ end
329
+ end
330
+
331
+ it "should use a custom type Name property" do
332
+ Zoo.class_eval do
333
+ property :name, Name
334
+ end
335
+
336
+ Zoo.auto_migrate!
337
+
338
+ zoo = Zoo.create(:name => %w(Zoo San\ Diego))
339
+ Zoo.first.name.should == %w(Zoo San\ Diego)
340
+ end
341
+
342
+ it "should override type options with property options" do
343
+ Zoo.class_eval do
344
+ property :name, Name, :track => :get
345
+ end
346
+
347
+ Zoo.auto_migrate!
348
+
349
+ Zoo.create(:name => %w(Awesome Person\ Dude))
350
+ zoo = Zoo.first
351
+ zoo.name = %w(Awesome Person\ Dude)
352
+
353
+ # If we were tracking by hash, this would cause zoo to be dirty,
354
+ # as its hash would not match the original. Since we've overridden
355
+ # and are tracking by :get, it won't be dirty
356
+ zoo.name.stub!(:hash).and_return(1)
357
+ zoo.should_not be_dirty
358
+ end
359
+
360
+ describe "public details" do
361
+ before do
362
+ Zoo.class_eval do
363
+ property :botanical_name, String, :nullable => true, :lazy => true
364
+ property :colloquial_name, DataMapper::Types::Text, :default => "Tomato"
365
+ end
366
+ Zoo.auto_migrate!
367
+ end
368
+
369
+ it "should determine nullness" do
370
+ Zoo.properties[:botanical_name].options[:nullable].should be_true
371
+ end
372
+
373
+ it "should determine its name" do
374
+ Zoo.properties[:botanical_name].name.should == :botanical_name
375
+ end
376
+
377
+ # lazy? is not exposed to or used by the adapters, so it should be tested indirectly
378
+ it "should determine laziness" do
379
+ Zoo.create(:botanical_name => "Calystegia sepium")
380
+ Zoo.first.attribute_loaded?(:botanical_name).should be_false
381
+ end
382
+
383
+ it "should automatically set laziness to true on text fields" do
384
+ Zoo.create(:colloquial_name => "American hedge bindweed")
385
+ Zoo.first.attribute_loaded?(:colloquial_name).should be_false
386
+ end
387
+
388
+ it "should determine whether it is a key" do
389
+ zoo = Zoo.create(:botanical_name => "Calystegia sepium")
390
+ id = zoo.id
391
+ Zoo.first.id.should == id
392
+ end
393
+
394
+ it "should determine whether it is serial" do
395
+ zoo = Zoo.create(:botanical_name => "Calystegia sepium")
396
+ zoo.id.should_not be_nil
397
+ end
398
+
399
+ it "should determine a default value" do
400
+ zoo = Zoo.new
401
+ zoo.colloquial_name.should == "Tomato"
402
+ end
403
+ end
404
+
405
+ describe "reader and writer visibility" do
406
+ # parameter passed to Property.new # reader | writer visibility
407
+ {
408
+ {} => [:public, :public],
409
+ { :accessor => :public } => [:public, :public],
410
+ { :accessor => :protected } => [:protected, :protected],
411
+ { :accessor => :private } => [:private, :private],
412
+ { :reader => :public } => [:public, :public],
413
+ { :reader => :protected } => [:protected, :public],
414
+ { :reader => :private } => [:private, :public],
415
+ { :writer => :public } => [:public, :public],
416
+ { :writer => :protected } => [:public, :protected],
417
+ { :writer => :private } => [:public, :private],
418
+ { :reader => :public, :writer => :public } => [:public, :public],
419
+ { :reader => :public, :writer => :protected } => [:public, :protected],
420
+ { :reader => :public, :writer => :private } => [:public, :private],
421
+ { :reader => :protected, :writer => :public } => [:protected, :public],
422
+ { :reader => :protected, :writer => :protected } => [:protected, :protected],
423
+ { :reader => :protected, :writer => :private } => [:protected, :private],
424
+ { :reader => :private, :writer => :public } => [:private, :public],
425
+ { :reader => :private, :writer => :protected } => [:private, :protected],
426
+ { :reader => :private, :writer => :private } => [:private, :private],
427
+ }.each do |input, output|
428
+ it "#{input.inspect} should make reader #{output[0]} and writer #{output[1]}" do
429
+ Tomato.class_eval <<-RUBY
430
+ property :botanical_name, String, #{input.inspect}
431
+ RUBY
432
+ Tomato.send("#{output[0]}_instance_methods").should include("botanical_name")
433
+ Tomato.send("#{output[1]}_instance_methods").should include("botanical_name=")
434
+ end
435
+ end
436
+
437
+ [
438
+ { :accessor => :junk },
439
+ { :reader => :junk },
440
+ { :writer => :junk },
441
+ { :reader => :public, :writer => :junk },
442
+ { :reader => :protected, :writer => :junk },
443
+ { :reader => :private, :writer => :junk },
444
+ { :reader => :junk, :writer => :public },
445
+ { :reader => :junk, :writer => :protected },
446
+ { :reader => :junk, :writer => :private },
447
+ { :reader => :junk, :writer => :junk },
448
+ { :reader => :junk, :writer => :junk },
449
+ { :reader => :junk, :writer => :junk },
450
+ ].each do |input|
451
+ it "#{input.inspect} should raise ArgumentError" do
452
+ lambda {
453
+ Tomato.class_eval <<-RUBY
454
+ property :family, String, #{input.inspect}
455
+ RUBY
456
+ }.should raise_error(ArgumentError)
457
+ end
458
+ end
459
+ end
460
+
461
+ # This is handled by get!
462
+ # ---
463
+ # it "should return an instance variable name" do
464
+ # DataMapper::Property.new(Tomato, :flavor, String, {}).instance_variable_name.should == '@flavor'
465
+ # DataMapper::Property.new(Tomato, :ripe, TrueClass, {}).instance_variable_name.should == '@ripe' #not @ripe?
466
+ # end
467
+
468
+ it "should append ? to TrueClass property reader methods" do
469
+ class Potato
470
+ include DataMapper::Resource
471
+ property :id, Integer, :key => true
472
+ property :fresh, TrueClass
473
+ property :public, TrueClass
474
+ end
475
+
476
+ Potato.new(:fresh => true).should be_fresh
477
+ end
478
+
479
+ it "should move unknown options into Property#extra_options" do
480
+ Tomato.class_eval do
481
+ property :botanical_name, String, :foo => :bar
482
+ end
483
+ Tomato.properties[:botanical_name].extra_options.should == {:foo => :bar}
484
+ end
485
+
486
+ it 'should provide #custom?' do
487
+ Zoo.class_eval do
488
+ property :name, Name, :size => 50
489
+ property :state, String, :size => 2
490
+ end
491
+ Zoo.properties[:name].should be_custom
492
+ Zoo.properties[:state].should_not be_custom
493
+ end
494
+
495
+ it "should set the field to the correct field_naming_convention" do
496
+ Zoo.class_eval { property :species, String }
497
+ Tomato.class_eval { property :genetic_history, DataMapper::Types::Text }
498
+
499
+ Zoo.properties[:species].field.should == "species"
500
+ Tomato.properties[:genetic_history].field.should == "genetic_history"
501
+ end
502
+
503
+ it "should provide the primitive mapping" do
504
+ Zoo.class_eval do
505
+ property :poverty, String
506
+ property :fortune, DataMapper::Types::Text
507
+ end
508
+
509
+ Zoo.properties[:poverty].primitive.should == String
510
+ Zoo.properties[:fortune].primitive.should == String
511
+ end
512
+
513
+ it "should make it possible to define an integer size" do
514
+ Zoo.class_eval { property :cleanliness, String, :size => 100 }
515
+ Zoo.properties[:cleanliness].size.should == 100
516
+ end
517
+
518
+ it "should make it possible to define an integer length (which defines size)" do
519
+ Zoo.class_eval { property :cleanliness, String, :length => 100 }
520
+ Zoo.properties[:cleanliness].size.should == 100
521
+ end
522
+
523
+ it "should make it possible to define a range size" do
524
+ Zoo.class_eval { property :cleanliness, String, :size => 0..100 }
525
+ Zoo.properties[:cleanliness].size.should == 100
526
+ end
527
+
528
+ it "should make it possible to define a range length (which defines size)" do
529
+ Zoo.class_eval { property :cleanliness, String, :length => 0..100 }
530
+ Zoo.properties[:cleanliness].size.should == 100
531
+ end
532
+
533
+ describe '#typecast' do
534
+ def self.format(value)
535
+ case value
536
+ when BigDecimal then "BigDecimal(#{value.to_s('F').inspect})"
537
+ when Float, Integer, String then "#{value.class}(#{value.inspect})"
538
+ else value.inspect
539
+ end
540
+ end
541
+
542
+ it 'should pass through the value if it is the same type when typecasting' do
543
+ Zoo.class_eval do
544
+ property :name, String
545
+ end
546
+ zoo = Zoo.new
547
+ value = "San Diego"
548
+ def value.to_s() "San Francisco" end
549
+ zoo.name = value
550
+ zoo.name.should == "San Diego"
551
+ end
552
+
553
+ it 'should pass through the value nil when typecasting' do
554
+ Zoo.class_eval do
555
+ property :name, String
556
+ end
557
+
558
+ zoo = Zoo.new
559
+ zoo.name = nil
560
+ zoo.name.should == nil
561
+ end
562
+
563
+ it 'should pass through the value for an Object property' do
564
+ value = Object.new
565
+ Zoo.class_eval do
566
+ property :object, Object
567
+ end
568
+
569
+ zoo = Zoo.new
570
+ zoo.object = value
571
+ zoo.object.object_id.should == value.object_id
572
+ end
573
+
574
+ [ true, 'true', 'TRUE', 1, '1', 't', 'T' ].each do |value|
575
+ it "should typecast #{value.inspect} to true for a TrueClass property" do
576
+ Zoo.class_eval do
577
+ property :boolean, TrueClass
578
+ end
579
+
580
+ zoo = Zoo.new
581
+ zoo.boolean = value
582
+ zoo.boolean.should == true
583
+ end
584
+ end
585
+
586
+ [ false, 'false', 'FALSE', 0, '0', 'f', 'F' ].each do |value|
587
+ it "should typecast #{value.inspect} to false for a Boolean property" do
588
+ Zoo.class_eval do
589
+ property :boolean, TrueClass
590
+ end
591
+
592
+ zoo = Zoo.new
593
+ zoo.boolean = value
594
+ zoo.boolean.should == false
595
+ end
596
+ end
597
+
598
+ it 'should typecast nil to nil for a Boolean property' do
599
+ Zoo.class_eval do
600
+ property :boolean, TrueClass
601
+ end
602
+
603
+ zoo = Zoo.new
604
+ zoo.boolean = nil
605
+ zoo.boolean.should == nil
606
+ end
607
+
608
+ it 'should typecast "0" to "0" for a String property' do
609
+ Zoo.class_eval do
610
+ property :string, String
611
+ end
612
+
613
+ zoo = Zoo.new
614
+ zoo.string = "0"
615
+ zoo.string.should == "0"
616
+ end
617
+
618
+ { '0' => 0.0, '0.0' => 0.0, 0 => 0.0, 0.0 => 0.0, BigDecimal('0.0') => 0.0 }.each do |value,expected|
619
+ it "should typecast #{format(value)} to #{format(expected)} for a Float property" do
620
+ Zoo.class_eval do
621
+ property :float, Float
622
+ end
623
+
624
+ zoo = Zoo.new
625
+ zoo.float = value
626
+ zoo.float.should == expected
627
+ end
628
+ end
629
+
630
+ { '-8' => -8, '-8.0' => -8, -8 => -8, -8.0 => -8, BigDecimal('8.0') => 8,
631
+ '0' => 0, '0.0' => 0, 0 => 0, 0.0 => 0, BigDecimal('0.0') => 0,
632
+ '5' => 5, '5.0' => 5, 5 => 5, 5.0 => 5, BigDecimal('5.0') => 5,
633
+ 'none' => nil, 'almost 5' => nil, '-3 change' => -3, '9 items' => 9}.each do |value,expected|
634
+ it "should typecast #{format(value)} to #{format(expected)} for an Integer property" do
635
+ Zoo.class_eval do
636
+ property :int, Integer
637
+ end
638
+
639
+ zoo = Zoo.new
640
+ zoo.int = value
641
+ zoo.int.should == expected
642
+ end
643
+ end
644
+
645
+ { '0' => BigDecimal('0'), '0.0' => BigDecimal('0.0'), 0.0 => BigDecimal('0.0'), BigDecimal('0.0') => BigDecimal('0.0') }.each do |value,expected|
646
+ it "should typecast #{format(value)} to #{format(expected)} for a BigDecimal property" do
647
+ Zoo.class_eval do
648
+ property :big_decimal, BigDecimal
649
+ end
650
+
651
+ zoo = Zoo.new
652
+ zoo.big_decimal = value
653
+ zoo.big_decimal.should == expected
654
+ end
655
+ end
656
+
657
+ it 'should typecast value for a DateTime property' do
658
+ Zoo.class_eval { property :date_time, DateTime }
659
+ zoo = Zoo.new
660
+ zoo.date_time = '2000-01-01 00:00:00'
661
+ zoo.date_time.should == DateTime.new(2000, 1, 1, 0, 0, 0)
662
+ end
663
+
664
+ it 'should typecast value for a Date property' do
665
+ Zoo.class_eval { property :date, Date }
666
+ zoo = Zoo.new
667
+ zoo.date = '2000-01-01'
668
+ zoo.date.should == Date.new(2000, 1, 1)
669
+ end
670
+
671
+ it 'should typecast value for a Time property' do
672
+ Zoo.class_eval { property :time, Time }
673
+ zoo = Zoo.new
674
+ zoo.time = '2000-01-01 01:01:01.123456'
675
+ zoo.time.should == Time.local(2000, 1, 1, 1, 1, 1, 123456)
676
+ end
677
+
678
+ it 'should typecast Hash for a Time property' do
679
+ Zoo.class_eval { property :time, Time }
680
+ zoo = Zoo.new
681
+ zoo.time = {:year => 2002, "month" => 1, :day => 1, "hour" => 12, :min => 0, :sec => 0}
682
+ zoo.time.should == Time.local(2002, 1, 1, 12, 0, 0)
683
+ end
684
+
685
+ it 'should typecast Hash for a Date property' do
686
+ Zoo.class_eval { property :date, Date }
687
+ zoo = Zoo.new
688
+ zoo.date = {:year => 2002, "month" => 1, :day => 1}
689
+ zoo.date.should == Date.new(2002, 1, 1)
690
+ end
691
+
692
+ it 'should typecast Hash for a DateTime property' do
693
+ Zoo.class_eval { property :date_time, DateTime }
694
+ zoo = Zoo.new
695
+ zoo.date_time = {:year => 2002, :month => 1, :day => 1, "hour" => 12, :min => 0, "sec" => 0}
696
+ zoo.date_time.should == DateTime.new(2002, 1, 1, 12, 0, 0)
697
+ end
698
+
699
+ it 'should use now as defaults for missing parts of a Hash to Time typecast' do
700
+ now = Time.now
701
+ Zoo.class_eval { property :time, Time }
702
+ zoo = Zoo.new
703
+ zoo.time = {:month => 1, :day => 1}
704
+ zoo.time.should == Time.local(now.year, 1, 1, now.hour, now.min, now.sec)
705
+ end
706
+
707
+ it 'should use now as defaults for missing parts of a Hash to Date typecast' do
708
+ now = Time.now
709
+ Zoo.class_eval { property :date, Date }
710
+ zoo = Zoo.new
711
+ zoo.date = {:month => 1, :day => 1}
712
+ zoo.date.should == Date.new(now.year, 1, 1)
713
+ end
714
+
715
+ it 'should use now as defaults for missing parts of a Hash to DateTime typecast' do
716
+ now = Time.now
717
+ Zoo.class_eval { property :date_time, DateTime }
718
+ zoo = Zoo.new
719
+ zoo.date_time = {:month => 1, :day => 1}
720
+ zoo.date_time.should == DateTime.new(now.year, 1, 1, now.hour, now.min, now.sec)
721
+ end
722
+
723
+ it 'should rescue after trying to typecast an invalid Date value from a hash' do
724
+ now = Time.now
725
+ Zoo.class_eval { property :date, Date }
726
+ zoo = Zoo.new
727
+ zoo.date = {:year => 2002, :month => 2, :day => 31}
728
+ zoo.date.should == Date.new(2002, 3, 3)
729
+ end
730
+
731
+ it 'should rescue after trying to typecast an invalid DateTime value from a hash' do
732
+ now = Time.now
733
+ Zoo.class_eval { property :date_time, DateTime }
734
+ zoo = Zoo.new
735
+ zoo.date_time = {
736
+ :year => 2002, :month => 2, :day => 31, :hour => 12, :min => 0, :sec => 0
737
+ }
738
+ zoo.date_time.should == DateTime.new(2002, 3, 3, 12, 0, 0)
739
+ end
740
+
741
+ it 'should typecast value for a Class property' do
742
+ Zoo.class_eval { property :klass, Class }
743
+ zoo = Zoo.new
744
+ zoo.klass = "Zoo"
745
+ zoo.klass.should == Zoo
746
+ end
747
+ end
748
+
749
+ it 'should return an abbreviated representation of the property when inspected' do
750
+ Zoo.class_eval { property :name, String }
751
+ Zoo.properties[:name].inspect.should == '#<Property:Zoo:name>'
752
+ end
753
+ end