dm-core 0.9.2

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 (101) hide show
  1. data/CHANGELOG +144 -0
  2. data/FAQ +74 -0
  3. data/MIT-LICENSE +22 -0
  4. data/QUICKLINKS +12 -0
  5. data/README +143 -0
  6. data/lib/dm-core.rb +213 -0
  7. data/lib/dm-core/adapters.rb +4 -0
  8. data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
  9. data/lib/dm-core/adapters/data_objects_adapter.rb +701 -0
  10. data/lib/dm-core/adapters/mysql_adapter.rb +132 -0
  11. data/lib/dm-core/adapters/postgres_adapter.rb +179 -0
  12. data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  13. data/lib/dm-core/associations.rb +172 -0
  14. data/lib/dm-core/associations/many_to_many.rb +138 -0
  15. data/lib/dm-core/associations/many_to_one.rb +101 -0
  16. data/lib/dm-core/associations/one_to_many.rb +275 -0
  17. data/lib/dm-core/associations/one_to_one.rb +61 -0
  18. data/lib/dm-core/associations/relationship.rb +116 -0
  19. data/lib/dm-core/associations/relationship_chain.rb +74 -0
  20. data/lib/dm-core/auto_migrations.rb +64 -0
  21. data/lib/dm-core/collection.rb +604 -0
  22. data/lib/dm-core/hook.rb +11 -0
  23. data/lib/dm-core/identity_map.rb +45 -0
  24. data/lib/dm-core/is.rb +16 -0
  25. data/lib/dm-core/logger.rb +233 -0
  26. data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  27. data/lib/dm-core/migrator.rb +29 -0
  28. data/lib/dm-core/model.rb +399 -0
  29. data/lib/dm-core/naming_conventions.rb +52 -0
  30. data/lib/dm-core/property.rb +611 -0
  31. data/lib/dm-core/property_set.rb +158 -0
  32. data/lib/dm-core/query.rb +590 -0
  33. data/lib/dm-core/repository.rb +159 -0
  34. data/lib/dm-core/resource.rb +618 -0
  35. data/lib/dm-core/scope.rb +35 -0
  36. data/lib/dm-core/support.rb +7 -0
  37. data/lib/dm-core/support/array.rb +13 -0
  38. data/lib/dm-core/support/assertions.rb +8 -0
  39. data/lib/dm-core/support/errors.rb +23 -0
  40. data/lib/dm-core/support/kernel.rb +7 -0
  41. data/lib/dm-core/support/symbol.rb +41 -0
  42. data/lib/dm-core/transaction.rb +267 -0
  43. data/lib/dm-core/type.rb +160 -0
  44. data/lib/dm-core/type_map.rb +80 -0
  45. data/lib/dm-core/types.rb +19 -0
  46. data/lib/dm-core/types/boolean.rb +7 -0
  47. data/lib/dm-core/types/discriminator.rb +32 -0
  48. data/lib/dm-core/types/object.rb +20 -0
  49. data/lib/dm-core/types/paranoid_boolean.rb +23 -0
  50. data/lib/dm-core/types/paranoid_datetime.rb +22 -0
  51. data/lib/dm-core/types/serial.rb +9 -0
  52. data/lib/dm-core/types/text.rb +10 -0
  53. data/spec/integration/association_spec.rb +1215 -0
  54. data/spec/integration/association_through_spec.rb +150 -0
  55. data/spec/integration/associations/many_to_many_spec.rb +171 -0
  56. data/spec/integration/associations/many_to_one_spec.rb +123 -0
  57. data/spec/integration/associations/one_to_many_spec.rb +66 -0
  58. data/spec/integration/auto_migrations_spec.rb +398 -0
  59. data/spec/integration/collection_spec.rb +1015 -0
  60. data/spec/integration/data_objects_adapter_spec.rb +32 -0
  61. data/spec/integration/model_spec.rb +68 -0
  62. data/spec/integration/mysql_adapter_spec.rb +85 -0
  63. data/spec/integration/postgres_adapter_spec.rb +732 -0
  64. data/spec/integration/property_spec.rb +224 -0
  65. data/spec/integration/query_spec.rb +376 -0
  66. data/spec/integration/repository_spec.rb +57 -0
  67. data/spec/integration/resource_spec.rb +324 -0
  68. data/spec/integration/sqlite3_adapter_spec.rb +352 -0
  69. data/spec/integration/sti_spec.rb +185 -0
  70. data/spec/integration/transaction_spec.rb +75 -0
  71. data/spec/integration/type_spec.rb +149 -0
  72. data/spec/lib/mock_adapter.rb +27 -0
  73. data/spec/spec_helper.rb +112 -0
  74. data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
  75. data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
  76. data/spec/unit/adapters/data_objects_adapter_spec.rb +627 -0
  77. data/spec/unit/adapters/postgres_adapter_spec.rb +125 -0
  78. data/spec/unit/associations/many_to_many_spec.rb +14 -0
  79. data/spec/unit/associations/many_to_one_spec.rb +138 -0
  80. data/spec/unit/associations/one_to_many_spec.rb +385 -0
  81. data/spec/unit/associations/one_to_one_spec.rb +7 -0
  82. data/spec/unit/associations/relationship_spec.rb +67 -0
  83. data/spec/unit/associations_spec.rb +205 -0
  84. data/spec/unit/auto_migrations_spec.rb +110 -0
  85. data/spec/unit/collection_spec.rb +174 -0
  86. data/spec/unit/data_mapper_spec.rb +21 -0
  87. data/spec/unit/identity_map_spec.rb +126 -0
  88. data/spec/unit/is_spec.rb +80 -0
  89. data/spec/unit/migrator_spec.rb +33 -0
  90. data/spec/unit/model_spec.rb +339 -0
  91. data/spec/unit/naming_conventions_spec.rb +28 -0
  92. data/spec/unit/property_set_spec.rb +96 -0
  93. data/spec/unit/property_spec.rb +447 -0
  94. data/spec/unit/query_spec.rb +485 -0
  95. data/spec/unit/repository_spec.rb +93 -0
  96. data/spec/unit/resource_spec.rb +557 -0
  97. data/spec/unit/scope_spec.rb +131 -0
  98. data/spec/unit/transaction_spec.rb +493 -0
  99. data/spec/unit/type_map_spec.rb +114 -0
  100. data/spec/unit/type_spec.rb +119 -0
  101. metadata +187 -0
@@ -0,0 +1,57 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ if ADAPTER
4
+ describe DataMapper::Repository, "with #{ADAPTER}" do
5
+ before :all do
6
+ class SerialFinderSpec
7
+ include DataMapper::Resource
8
+
9
+ property :id, Serial
10
+ property :sample, String
11
+
12
+ auto_migrate!(ADAPTER)
13
+ end
14
+
15
+ repository(ADAPTER).create((0...100).map { SerialFinderSpec.new(:sample => rand.to_s) })
16
+ end
17
+
18
+ before do
19
+ @repository = repository(ADAPTER)
20
+ @model = SerialFinderSpec
21
+ @query = DataMapper::Query.new(@repository, @model)
22
+ end
23
+
24
+ it "should throw an exception if the named repository is unknown" do
25
+ r = DataMapper::Repository.new(:completely_bogus)
26
+ lambda { r.adapter }.should raise_error(ArgumentError)
27
+ end
28
+
29
+ it "should return all available rows" do
30
+ @repository.read_many(@query).should have(100).entries
31
+ end
32
+
33
+ it "should allow limit and offset" do
34
+ @repository.read_many(@query.merge(:limit => 50)).should have(50).entries
35
+
36
+ collection = @repository.read_many(@query.merge(:limit => 20, :offset => 40))
37
+ collection.should have(20).entries
38
+ collection.map { |entry| entry.id }.should == @repository.read_many(@query)[40...60].map { |entry| entry.id }
39
+ end
40
+
41
+ it "should lazy-load missing attributes" do
42
+ sfs = @repository.read_one(@query.merge(:fields => [ :id ], :limit => 1))
43
+ sfs.should be_a_kind_of(@model)
44
+ sfs.should_not be_a_new_record
45
+
46
+ sfs.attribute_loaded?(:sample).should be_false
47
+ sfs.sample.should_not be_nil
48
+ end
49
+
50
+ it "should translate an Array to an IN clause" do
51
+ ids = @repository.read_many(@query.merge(:fields => [ :id ], :limit => 10)).map { |entry| entry.id }
52
+ results = @repository.read_many(@query.merge(:id => ids))
53
+
54
+ results.map { |entry| entry.id }.should == ids
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,324 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ if ADAPTER
4
+ class Orange
5
+ include DataMapper::Resource
6
+
7
+ def self.default_repository_name
8
+ ADAPTER
9
+ end
10
+
11
+ property :name, String, :key => true
12
+ property :color, String
13
+ end
14
+
15
+ class Apple
16
+ include DataMapper::Resource
17
+
18
+ def self.default_repository_name
19
+ ADAPTER
20
+ end
21
+
22
+ property :id, Serial
23
+ property :color, String, :default => 'green', :nullable => true
24
+ end
25
+
26
+ class FortunePig
27
+ include DataMapper::Resource
28
+
29
+ def self.default_repository_name
30
+ ADAPTER
31
+ end
32
+
33
+ property :id, Serial
34
+ property :name, String
35
+
36
+ def to_s
37
+ name
38
+ end
39
+
40
+ after :create do
41
+ @created_id = self.id
42
+ end
43
+
44
+ after :save do
45
+ @save_id = self.id
46
+ end
47
+ end
48
+
49
+ class Car
50
+ include DataMapper::Resource
51
+
52
+ def self.default_repository_name
53
+ ADAPTER
54
+ end
55
+
56
+ property :brand, String, :key => true
57
+ property :color, String
58
+ property :created_on, Date
59
+ property :touched_on, Date
60
+ property :updated_on, Date
61
+
62
+ before :save do
63
+ self.touched_on = Date.today
64
+ end
65
+
66
+ before :create do
67
+ self.created_on = Date.today
68
+ end
69
+
70
+ before :update do
71
+ self.updated_on = Date.today
72
+ end
73
+ end
74
+
75
+ class Male
76
+ include DataMapper::Resource
77
+
78
+ def self.default_repository_name
79
+ ADAPTER
80
+ end
81
+
82
+ property :id, Serial
83
+ property :name, String
84
+ property :iq, Integer, :default => 100
85
+ property :type, Discriminator
86
+ property :data, Object
87
+
88
+ def iq=(i)
89
+ attribute_set(:iq, i - 1)
90
+ end
91
+ end
92
+
93
+ class Bully < Male; end
94
+
95
+ class Mugger < Bully; end
96
+
97
+ class Maniac < Bully; end
98
+
99
+ class Psycho < Maniac; end
100
+
101
+ class Geek < Male
102
+ property :awkward, Boolean, :default => true
103
+
104
+ def iq=(i)
105
+ attribute_set(:iq, i + 30)
106
+ end
107
+ end
108
+
109
+ class Flanimal
110
+ include DataMapper::Resource
111
+
112
+ def self.default_repository_name
113
+ ADAPTER
114
+ end
115
+
116
+ property :id, Serial
117
+ property :type, Discriminator
118
+ property :name, String
119
+ end
120
+
121
+ class Sprog < Flanimal; end
122
+
123
+ describe "DataMapper::Resource with #{ADAPTER}" do
124
+ before :all do
125
+ Orange.auto_migrate!(ADAPTER)
126
+ Apple.auto_migrate!(ADAPTER)
127
+ FortunePig.auto_migrate!(ADAPTER)
128
+
129
+ orange = Orange.new(:color => 'orange')
130
+ orange.name = 'Bob' # Keys are protected from mass-assignment by default.
131
+ repository(ADAPTER) { orange.save }
132
+ end
133
+
134
+ it "should be able to overwrite Resource#to_s" do
135
+ repository(ADAPTER) do
136
+ ted = FortunePig.create!(:name => "Ted")
137
+ FortunePig.get!(ted.id).to_s.should == 'Ted'
138
+ end
139
+ end
140
+
141
+ it "should be able to destroy objects" do
142
+ apple = Apple.create!(:color => 'Green')
143
+ lambda do
144
+ apple.destroy.should be_true
145
+ end.should_not raise_error
146
+ end
147
+
148
+ it 'should return false to #destroy if the resource is new' do
149
+ Apple.new.destroy.should be_false
150
+ end
151
+
152
+ it "should be able to reload objects" do
153
+ orange = repository(ADAPTER) { Orange.get!('Bob') }
154
+ orange.color.should == 'orange'
155
+ orange.color = 'blue'
156
+ orange.color.should == 'blue'
157
+ orange.reload
158
+ orange.color.should == 'orange'
159
+ end
160
+
161
+ it "should be able to reload new objects" do
162
+ repository(ADAPTER) do
163
+ Orange.create(:name => 'Tom').reload
164
+ end
165
+ end
166
+
167
+ it "should be able to find first or create objects" do
168
+ repository(ADAPTER) do
169
+ orange = Orange.create(:name => 'Naval')
170
+
171
+ Orange.first_or_create(:name => 'Naval').should == orange
172
+
173
+ purple = Orange.first_or_create(:name => 'Purple', :color => 'Fuschia')
174
+ oranges = Orange.all(:name => 'Purple')
175
+ oranges.size.should == 1
176
+ oranges.first.should == purple
177
+ end
178
+ end
179
+
180
+ it "should be able to override a default with a nil" do
181
+ repository(ADAPTER) do
182
+ apple = Apple.new
183
+ apple.color = nil
184
+ apple.save
185
+ apple.color.should be_nil
186
+
187
+ apple = Apple.create(:color => nil)
188
+ apple.color.should be_nil
189
+ end
190
+ end
191
+
192
+ it "should be able to respond to create hooks" do
193
+ bob = repository(ADAPTER) { FortunePig.create(:name => 'Bob') }
194
+ bob.id.should_not be_nil
195
+ bob.instance_variable_get("@created_id").should == bob.id
196
+
197
+ fred = FortunePig.new(:name => 'Fred')
198
+ repository(ADAPTER) { fred.save }
199
+ fred.id.should_not be_nil
200
+ fred.instance_variable_get("@save_id").should == fred.id
201
+ end
202
+
203
+ it "should be dirty when Object properties are changed" do
204
+ # pending "Awaiting Property#track implementation"
205
+ repository(ADAPTER) do
206
+ Male.auto_migrate!
207
+ end
208
+ repository(ADAPTER) do
209
+ bob = Male.create(:name => "Bob", :data => {})
210
+ bob.dirty?.should be_false
211
+ bob.data.merge!(:name => "Bob")
212
+ bob.dirty?.should be_true
213
+ bob = Male.first
214
+ bob.data[:name] = "Bob"
215
+ bob.dirty?.should be_true
216
+ end
217
+ end
218
+
219
+ describe "hooking" do
220
+ before :all do
221
+ Car.auto_migrate!(ADAPTER)
222
+ end
223
+
224
+ it "should execute hooks before creating/updating objects" do
225
+ repository(ADAPTER) do
226
+ c1 = Car.new(:brand => 'BMW', :color => 'white')
227
+
228
+ c1.new_record?.should == true
229
+ c1.created_on.should == nil
230
+
231
+ c1.save
232
+
233
+ c1.new_record?.should == false
234
+ c1.touched_on.should == Date.today
235
+ c1.created_on.should == Date.today
236
+ c1.updated_on.should == nil
237
+
238
+ c1.color = 'black'
239
+ c1.save
240
+
241
+ c1.updated_on.should == Date.today
242
+ end
243
+
244
+ end
245
+
246
+ end
247
+
248
+ describe "inheritance" do
249
+ before :all do
250
+ Geek.auto_migrate!(ADAPTER)
251
+
252
+ repository(ADAPTER) do
253
+ Male.create!(:name => 'John Dorian')
254
+ Bully.create!(:name => 'Bob', :iq => 69)
255
+ Geek.create!(:name => 'Steve', :awkward => false, :iq => 132)
256
+ Geek.create!(:name => 'Bill', :iq => 150)
257
+ Bully.create!(:name => 'Johnson')
258
+ Mugger.create!(:name => 'Frank')
259
+ Maniac.create!(:name => 'William')
260
+ Psycho.create!(:name => 'Norman')
261
+ end
262
+
263
+ Flanimal.auto_migrate!(ADAPTER)
264
+
265
+ end
266
+
267
+ it "should test bug ticket #302" do
268
+ repository(ADAPTER) do
269
+ Sprog.create(:name => 'Marty')
270
+ Sprog.first(:name => 'Marty').should_not be_nil
271
+ end
272
+ end
273
+
274
+ it "should select appropriate types" do
275
+ repository(ADAPTER) do
276
+ males = Male.all
277
+ males.should have(8).entries
278
+
279
+ males.each do |male|
280
+ male.class.name.should == male.type.name
281
+ end
282
+
283
+ Male.first(:name => 'Steve').should be_a_kind_of(Geek)
284
+ Bully.first(:name => 'Bob').should be_a_kind_of(Bully)
285
+ Geek.first(:name => 'Steve').should be_a_kind_of(Geek)
286
+ Geek.first(:name => 'Bill').should be_a_kind_of(Geek)
287
+ Bully.first(:name => 'Johnson').should be_a_kind_of(Bully)
288
+ Male.first(:name => 'John Dorian').should be_a_kind_of(Male)
289
+ end
290
+ end
291
+
292
+ it "should not select parent type" do
293
+ repository(ADAPTER) do
294
+ Male.first(:name => 'John Dorian').should be_a_kind_of(Male)
295
+ Geek.first(:name => 'John Dorian').should be_nil
296
+ Geek.first.iq.should > Bully.first.iq
297
+ end
298
+ end
299
+
300
+ it "should select objects of all inheriting classes" do
301
+ repository(ADAPTER) do
302
+ Male.all.should have(8).entries
303
+ Geek.all.should have(2).entries
304
+ Bully.all.should have(5).entries
305
+ Mugger.all.should have(1).entries
306
+ Maniac.all.should have(2).entries
307
+ Psycho.all.should have(1).entries
308
+ end
309
+ end
310
+
311
+ it "should inherit setter method from parent" do
312
+ repository(ADAPTER) do
313
+ Bully.first(:name => "Bob").iq.should == 68
314
+ end
315
+ end
316
+
317
+ it "should be able to overwrite a setter in a child class" do
318
+ repository(ADAPTER) do
319
+ Geek.first(:name => "Bill").iq.should == 180
320
+ end
321
+ end
322
+ end
323
+ end
324
+ end
@@ -0,0 +1,352 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ if HAS_SQLITE3
4
+ describe DataMapper::Adapters::Sqlite3Adapter do
5
+ before :all do
6
+ @adapter = repository(:sqlite3).adapter
7
+ end
8
+
9
+ describe "auto migrating" do
10
+ before :all do
11
+ class Sputnik
12
+ include DataMapper::Resource
13
+
14
+ property :id, Serial
15
+ property :name, DM::Text
16
+ end
17
+ end
18
+
19
+ it "#upgrade_model should work" do
20
+ @adapter.destroy_model_storage(repository(:sqlite3), Sputnik)
21
+ @adapter.storage_exists?("sputniks").should == false
22
+ Sputnik.auto_migrate!(:sqlite3)
23
+ @adapter.storage_exists?("sputniks").should == true
24
+ @adapter.field_exists?("sputniks", "new_prop").should == false
25
+ Sputnik.property :new_prop, Integer
26
+ Sputnik.auto_upgrade!(:sqlite3)
27
+ @adapter.field_exists?("sputniks", "new_prop").should == true
28
+ end
29
+ end
30
+
31
+ describe "querying metadata" do
32
+ before :all do
33
+ class Sputnik
34
+ include DataMapper::Resource
35
+
36
+ property :id, Serial
37
+ property :name, DM::Text
38
+ end
39
+ end
40
+
41
+ before do
42
+ Sputnik.auto_migrate!(:sqlite3)
43
+ end
44
+
45
+ it "#storage_exists? should return true for tables that exist" do
46
+ @adapter.storage_exists?("sputniks").should == true
47
+ end
48
+
49
+ it "#storage_exists? should return false for tables that don't exist" do
50
+ @adapter.storage_exists?("space turds").should == false
51
+ end
52
+
53
+ it "#field_exists? should return true for columns that exist" do
54
+ @adapter.field_exists?("sputniks", "name").should == true
55
+ end
56
+
57
+ it "#storage_exists? should return false for tables that don't exist" do
58
+ @adapter.field_exists?("sputniks", "plur").should == false
59
+ end
60
+ end
61
+
62
+ describe "database file handling" do
63
+ it "should preserve the file path for file-based databases" do
64
+ file = 'newfile.db'
65
+ DataMapper.setup(:sqlite3file, "sqlite3:#{file}")
66
+ adapter = repository(:sqlite3file).adapter
67
+ adapter.uri.path.should == file
68
+ end
69
+
70
+ it "should have a path of just :memory: when using memory databases" do
71
+ DataMapper.setup(:sqlite3memory, "sqlite3::memory:")
72
+ adapter = repository(:sqlite3memory).adapter
73
+ adapter.uri.path.should == ':memory:'
74
+ end
75
+ end
76
+
77
+ describe "handling transactions" do
78
+ before :all do
79
+ class Sputnik
80
+ include DataMapper::Resource
81
+
82
+ property :id, Serial
83
+ property :name, DM::Text
84
+ end
85
+ end
86
+
87
+ before do
88
+ Sputnik.auto_migrate!(:sqlite3)
89
+
90
+ @transaction = DataMapper::Transaction.new(@adapter)
91
+ end
92
+
93
+ it "should rollback changes when #rollback_transaction is called" do
94
+ @transaction.commit do |transaction|
95
+ @adapter.execute("INSERT INTO sputniks (name) VALUES ('my pretty sputnik')")
96
+ transaction.rollback
97
+ end
98
+ @adapter.query("SELECT * FROM sputniks WHERE name = 'my pretty sputnik'").empty?.should == true
99
+ end
100
+
101
+ it "should commit changes when #commit_transaction is called" do
102
+ @transaction.commit do
103
+ @adapter.execute("INSERT INTO sputniks (name) VALUES ('my pretty sputnik')")
104
+ end
105
+ @adapter.query("SELECT * FROM sputniks WHERE name = 'my pretty sputnik'").size.should == 1
106
+ end
107
+ end
108
+
109
+ describe "reading & writing a database" do
110
+ before :all do
111
+ class User
112
+ include DataMapper::Resource
113
+
114
+ property :id, Serial
115
+ property :name, DM::Text
116
+ end
117
+ end
118
+
119
+ before do
120
+ User.auto_migrate!(:sqlite3)
121
+
122
+ @adapter.execute("INSERT INTO users (name) VALUES ('Paul')")
123
+ end
124
+
125
+ it 'should be able to #execute an arbitrary query' do
126
+ result = @adapter.execute("INSERT INTO users (name) VALUES ('Sam')")
127
+
128
+ result.affected_rows.should == 1
129
+ end
130
+
131
+ it 'should be able to #query' do
132
+ result = @adapter.query("SELECT * FROM users")
133
+
134
+ result.should be_kind_of(Array)
135
+ row = result.first
136
+ row.should be_kind_of(Struct)
137
+ row.members.should == %w{id name}
138
+
139
+ row.id.should == 1
140
+ row.name.should == 'Paul'
141
+ end
142
+
143
+ it 'should return an empty array if #query found no rows' do
144
+ @adapter.execute("DELETE FROM users")
145
+
146
+ result = nil
147
+ lambda { result = @adapter.query("SELECT * FROM users") }.should_not raise_error
148
+
149
+ result.should be_kind_of(Array)
150
+ result.size.should == 0
151
+ end
152
+ end
153
+
154
+ describe "CRUD for serial Key" do
155
+ before :all do
156
+ class VideoGame
157
+ include DataMapper::Resource
158
+
159
+ property :id, Serial
160
+ property :name, String
161
+ property :object, Object
162
+ property :notes, Text
163
+ end
164
+ end
165
+
166
+ before do
167
+ VideoGame.auto_migrate!(:sqlite3)
168
+ end
169
+
170
+ it 'should be able to create a record' do
171
+ time = Time.now
172
+ game = repository(:sqlite3) do
173
+ game = VideoGame.new(:name => 'System Shock', :object => time, :notes => "Test")
174
+ game.save
175
+ game.should_not be_a_new_record
176
+ game.should_not be_dirty
177
+ game
178
+ end
179
+ repository(:sqlite3) do
180
+ saved = VideoGame.first(:name => 'System Shock')
181
+ saved.id.should == game.id
182
+ saved.notes.should == game.notes
183
+ saved.object.should == time
184
+ end
185
+ end
186
+
187
+ it 'should be able to read a record' do
188
+ name = 'Wing Commander: Privateer'
189
+ id = @adapter.execute('INSERT INTO "video_games" ("name") VALUES (?)', name).insert_id
190
+
191
+ game = repository(:sqlite3) do
192
+ VideoGame.get(id)
193
+ end
194
+
195
+ game.name.should == name
196
+ game.should_not be_dirty
197
+ game.should_not be_a_new_record
198
+ end
199
+
200
+ it 'should be able to update a record' do
201
+ name = 'Resistance: Fall of Mon'
202
+ id = @adapter.execute('INSERT INTO "video_games" ("name") VALUES (?)', name).insert_id
203
+
204
+ game = repository(:sqlite3) do
205
+ VideoGame.get(id)
206
+ end
207
+
208
+ game.name = game.name.sub(/Mon/, 'Man')
209
+
210
+ game.should_not be_a_new_record
211
+ game.should be_dirty
212
+
213
+ repository(:sqlite3) do
214
+ game.save
215
+ end
216
+
217
+ game.should_not be_dirty
218
+
219
+ clone = repository(:sqlite3) do
220
+ VideoGame.get(id)
221
+ end
222
+
223
+ clone.name.should == game.name
224
+ end
225
+
226
+ it 'should be able to delete a record' do
227
+ name = 'Zelda'
228
+ id = @adapter.execute('INSERT INTO "video_games" ("name") VALUES (?)', name).insert_id
229
+
230
+ game = repository(:sqlite3) do
231
+ VideoGame.get(id)
232
+ end
233
+
234
+ game.name.should == name
235
+
236
+ repository(:sqlite3) do
237
+ game.destroy.should be_true
238
+ end
239
+ game.should be_a_new_record
240
+ game.should be_dirty
241
+ end
242
+
243
+ it 'should respond to Resource#get' do
244
+ name = 'Contra'
245
+ id = @adapter.execute('INSERT INTO "video_games" ("name") VALUES (?)', name).insert_id
246
+
247
+ contra = repository(:sqlite3) { VideoGame.get(id) }
248
+
249
+ contra.should_not be_nil
250
+ contra.should_not be_dirty
251
+ contra.should_not be_a_new_record
252
+ contra.id.should == id
253
+ end
254
+ end
255
+
256
+ describe "CRUD for Composite Key" do
257
+ before :all do
258
+ class BankCustomer
259
+ include DataMapper::Resource
260
+
261
+ property :bank, String, :key => true
262
+ property :account_number, String, :key => true
263
+ property :name, String
264
+ end
265
+ end
266
+
267
+ before do
268
+ BankCustomer.auto_migrate!(:sqlite3)
269
+ end
270
+
271
+ it 'should be able to create a record' do
272
+ customer = BankCustomer.new(:bank => 'Community Bank', :account_number => '123456', :name => 'David Hasselhoff')
273
+ repository(:sqlite3) do
274
+ customer.save
275
+ end
276
+
277
+ customer.should_not be_a_new_record
278
+ customer.should_not be_dirty
279
+
280
+ row = @adapter.query('SELECT "bank", "account_number" FROM "bank_customers" WHERE "name" = ?', customer.name).first
281
+ row.bank.should == customer.bank
282
+ row.account_number.should == customer.account_number
283
+ end
284
+
285
+ it 'should be able to read a record' do
286
+ bank, account_number, name = 'Chase', '4321', 'Super Wonderful'
287
+ @adapter.execute('INSERT INTO "bank_customers" ("bank", "account_number", "name") VALUES (?, ?, ?)', bank, account_number, name)
288
+
289
+ repository(:sqlite3) do
290
+ BankCustomer.get(bank, account_number).name.should == name
291
+ end
292
+ end
293
+
294
+ it 'should be able to update a record' do
295
+ bank, account_number, name = 'Wells Fargo', '00101001', 'Spider Pig'
296
+ @adapter.execute('INSERT INTO "bank_customers" ("bank", "account_number", "name") VALUES (?, ?, ?)', bank, account_number, name)
297
+
298
+ customer = repository(:sqlite3) do
299
+ BankCustomer.get(bank, account_number)
300
+ end
301
+
302
+ customer.name = 'Bat-Pig'
303
+
304
+ customer.should_not be_a_new_record
305
+ customer.should be_dirty
306
+
307
+ repository(:sqlite3) do
308
+ customer.save
309
+ end
310
+
311
+ customer.should_not be_dirty
312
+
313
+ clone = repository(:sqlite3) do
314
+ BankCustomer.get(bank, account_number)
315
+ end
316
+
317
+ clone.name.should == customer.name
318
+ end
319
+
320
+ it 'should be able to delete a record' do
321
+ bank, account_number, name = 'Megacorp', 'ABC', 'Flash Gordon'
322
+ @adapter.execute('INSERT INTO "bank_customers" ("bank", "account_number", "name") VALUES (?, ?, ?)', bank, account_number, name)
323
+
324
+ customer = repository(:sqlite3) do
325
+ BankCustomer.get(bank, account_number)
326
+ end
327
+
328
+ customer.name.should == name
329
+
330
+ repository(:sqlite3) do
331
+ customer.destroy.should be_true
332
+ end
333
+
334
+ customer.should be_a_new_record
335
+ customer.should be_dirty
336
+ end
337
+
338
+ it 'should respond to Resource#get' do
339
+ bank, account_number, name = 'Conchords', '1100101', 'Robo Boogie'
340
+ @adapter.execute('INSERT INTO "bank_customers" ("bank", "account_number", "name") VALUES (?, ?, ?)', bank, account_number, name)
341
+
342
+ robots = repository(:sqlite3) { BankCustomer.get(bank, account_number) }
343
+
344
+ robots.should_not be_nil
345
+ robots.should_not be_dirty
346
+ robots.should_not be_a_new_record
347
+ robots.bank.should == bank
348
+ robots.account_number.should == account_number
349
+ end
350
+ end
351
+ end
352
+ end