sam-dm-core 0.9.6

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.
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