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.
- data/CHANGELOG +144 -0
- data/FAQ +74 -0
- data/MIT-LICENSE +22 -0
- data/QUICKLINKS +12 -0
- data/README +143 -0
- data/lib/dm-core.rb +213 -0
- data/lib/dm-core/adapters.rb +4 -0
- data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
- data/lib/dm-core/adapters/data_objects_adapter.rb +701 -0
- data/lib/dm-core/adapters/mysql_adapter.rb +132 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +179 -0
- data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
- data/lib/dm-core/associations.rb +172 -0
- data/lib/dm-core/associations/many_to_many.rb +138 -0
- data/lib/dm-core/associations/many_to_one.rb +101 -0
- data/lib/dm-core/associations/one_to_many.rb +275 -0
- data/lib/dm-core/associations/one_to_one.rb +61 -0
- data/lib/dm-core/associations/relationship.rb +116 -0
- data/lib/dm-core/associations/relationship_chain.rb +74 -0
- data/lib/dm-core/auto_migrations.rb +64 -0
- data/lib/dm-core/collection.rb +604 -0
- data/lib/dm-core/hook.rb +11 -0
- data/lib/dm-core/identity_map.rb +45 -0
- data/lib/dm-core/is.rb +16 -0
- data/lib/dm-core/logger.rb +233 -0
- data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
- data/lib/dm-core/migrator.rb +29 -0
- data/lib/dm-core/model.rb +399 -0
- data/lib/dm-core/naming_conventions.rb +52 -0
- data/lib/dm-core/property.rb +611 -0
- data/lib/dm-core/property_set.rb +158 -0
- data/lib/dm-core/query.rb +590 -0
- data/lib/dm-core/repository.rb +159 -0
- data/lib/dm-core/resource.rb +618 -0
- data/lib/dm-core/scope.rb +35 -0
- data/lib/dm-core/support.rb +7 -0
- data/lib/dm-core/support/array.rb +13 -0
- data/lib/dm-core/support/assertions.rb +8 -0
- data/lib/dm-core/support/errors.rb +23 -0
- data/lib/dm-core/support/kernel.rb +7 -0
- data/lib/dm-core/support/symbol.rb +41 -0
- data/lib/dm-core/transaction.rb +267 -0
- data/lib/dm-core/type.rb +160 -0
- data/lib/dm-core/type_map.rb +80 -0
- data/lib/dm-core/types.rb +19 -0
- data/lib/dm-core/types/boolean.rb +7 -0
- data/lib/dm-core/types/discriminator.rb +32 -0
- data/lib/dm-core/types/object.rb +20 -0
- data/lib/dm-core/types/paranoid_boolean.rb +23 -0
- data/lib/dm-core/types/paranoid_datetime.rb +22 -0
- data/lib/dm-core/types/serial.rb +9 -0
- data/lib/dm-core/types/text.rb +10 -0
- data/spec/integration/association_spec.rb +1215 -0
- data/spec/integration/association_through_spec.rb +150 -0
- data/spec/integration/associations/many_to_many_spec.rb +171 -0
- data/spec/integration/associations/many_to_one_spec.rb +123 -0
- data/spec/integration/associations/one_to_many_spec.rb +66 -0
- data/spec/integration/auto_migrations_spec.rb +398 -0
- data/spec/integration/collection_spec.rb +1015 -0
- data/spec/integration/data_objects_adapter_spec.rb +32 -0
- data/spec/integration/model_spec.rb +68 -0
- data/spec/integration/mysql_adapter_spec.rb +85 -0
- data/spec/integration/postgres_adapter_spec.rb +732 -0
- data/spec/integration/property_spec.rb +224 -0
- data/spec/integration/query_spec.rb +376 -0
- data/spec/integration/repository_spec.rb +57 -0
- data/spec/integration/resource_spec.rb +324 -0
- data/spec/integration/sqlite3_adapter_spec.rb +352 -0
- data/spec/integration/sti_spec.rb +185 -0
- data/spec/integration/transaction_spec.rb +75 -0
- data/spec/integration/type_spec.rb +149 -0
- data/spec/lib/mock_adapter.rb +27 -0
- data/spec/spec_helper.rb +112 -0
- data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
- data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
- data/spec/unit/adapters/data_objects_adapter_spec.rb +627 -0
- data/spec/unit/adapters/postgres_adapter_spec.rb +125 -0
- data/spec/unit/associations/many_to_many_spec.rb +14 -0
- data/spec/unit/associations/many_to_one_spec.rb +138 -0
- data/spec/unit/associations/one_to_many_spec.rb +385 -0
- data/spec/unit/associations/one_to_one_spec.rb +7 -0
- data/spec/unit/associations/relationship_spec.rb +67 -0
- data/spec/unit/associations_spec.rb +205 -0
- data/spec/unit/auto_migrations_spec.rb +110 -0
- data/spec/unit/collection_spec.rb +174 -0
- data/spec/unit/data_mapper_spec.rb +21 -0
- data/spec/unit/identity_map_spec.rb +126 -0
- data/spec/unit/is_spec.rb +80 -0
- data/spec/unit/migrator_spec.rb +33 -0
- data/spec/unit/model_spec.rb +339 -0
- data/spec/unit/naming_conventions_spec.rb +28 -0
- data/spec/unit/property_set_spec.rb +96 -0
- data/spec/unit/property_spec.rb +447 -0
- data/spec/unit/query_spec.rb +485 -0
- data/spec/unit/repository_spec.rb +93 -0
- data/spec/unit/resource_spec.rb +557 -0
- data/spec/unit/scope_spec.rb +131 -0
- data/spec/unit/transaction_spec.rb +493 -0
- data/spec/unit/type_map_spec.rb +114 -0
- data/spec/unit/type_spec.rb +119 -0
- 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
|