dm-core 0.9.2

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