sam-dm-core 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. data/.autotest +26 -0
  2. data/CONTRIBUTING +51 -0
  3. data/FAQ +92 -0
  4. data/History.txt +145 -0
  5. data/MIT-LICENSE +22 -0
  6. data/Manifest.txt +125 -0
  7. data/QUICKLINKS +12 -0
  8. data/README.txt +143 -0
  9. data/Rakefile +30 -0
  10. data/SPECS +63 -0
  11. data/TODO +1 -0
  12. data/lib/dm-core.rb +224 -0
  13. data/lib/dm-core/adapters.rb +4 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
  15. data/lib/dm-core/adapters/data_objects_adapter.rb +707 -0
  16. data/lib/dm-core/adapters/mysql_adapter.rb +136 -0
  17. data/lib/dm-core/adapters/postgres_adapter.rb +188 -0
  18. data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  19. data/lib/dm-core/associations.rb +199 -0
  20. data/lib/dm-core/associations/many_to_many.rb +147 -0
  21. data/lib/dm-core/associations/many_to_one.rb +107 -0
  22. data/lib/dm-core/associations/one_to_many.rb +309 -0
  23. data/lib/dm-core/associations/one_to_one.rb +61 -0
  24. data/lib/dm-core/associations/relationship.rb +218 -0
  25. data/lib/dm-core/associations/relationship_chain.rb +81 -0
  26. data/lib/dm-core/auto_migrations.rb +113 -0
  27. data/lib/dm-core/collection.rb +638 -0
  28. data/lib/dm-core/dependency_queue.rb +31 -0
  29. data/lib/dm-core/hook.rb +11 -0
  30. data/lib/dm-core/identity_map.rb +45 -0
  31. data/lib/dm-core/is.rb +16 -0
  32. data/lib/dm-core/logger.rb +232 -0
  33. data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  34. data/lib/dm-core/migrator.rb +29 -0
  35. data/lib/dm-core/model.rb +471 -0
  36. data/lib/dm-core/naming_conventions.rb +84 -0
  37. data/lib/dm-core/property.rb +673 -0
  38. data/lib/dm-core/property_set.rb +162 -0
  39. data/lib/dm-core/query.rb +625 -0
  40. data/lib/dm-core/repository.rb +159 -0
  41. data/lib/dm-core/resource.rb +637 -0
  42. data/lib/dm-core/scope.rb +58 -0
  43. data/lib/dm-core/support.rb +7 -0
  44. data/lib/dm-core/support/array.rb +13 -0
  45. data/lib/dm-core/support/assertions.rb +8 -0
  46. data/lib/dm-core/support/errors.rb +23 -0
  47. data/lib/dm-core/support/kernel.rb +7 -0
  48. data/lib/dm-core/support/symbol.rb +41 -0
  49. data/lib/dm-core/transaction.rb +267 -0
  50. data/lib/dm-core/type.rb +160 -0
  51. data/lib/dm-core/type_map.rb +80 -0
  52. data/lib/dm-core/types.rb +19 -0
  53. data/lib/dm-core/types/boolean.rb +7 -0
  54. data/lib/dm-core/types/discriminator.rb +34 -0
  55. data/lib/dm-core/types/object.rb +24 -0
  56. data/lib/dm-core/types/paranoid_boolean.rb +34 -0
  57. data/lib/dm-core/types/paranoid_datetime.rb +33 -0
  58. data/lib/dm-core/types/serial.rb +9 -0
  59. data/lib/dm-core/types/text.rb +10 -0
  60. data/lib/dm-core/version.rb +3 -0
  61. data/script/all +5 -0
  62. data/script/performance.rb +203 -0
  63. data/script/profile.rb +87 -0
  64. data/spec/integration/association_spec.rb +1371 -0
  65. data/spec/integration/association_through_spec.rb +203 -0
  66. data/spec/integration/associations/many_to_many_spec.rb +449 -0
  67. data/spec/integration/associations/many_to_one_spec.rb +163 -0
  68. data/spec/integration/associations/one_to_many_spec.rb +151 -0
  69. data/spec/integration/auto_migrations_spec.rb +398 -0
  70. data/spec/integration/collection_spec.rb +1069 -0
  71. data/spec/integration/data_objects_adapter_spec.rb +32 -0
  72. data/spec/integration/dependency_queue_spec.rb +58 -0
  73. data/spec/integration/model_spec.rb +127 -0
  74. data/spec/integration/mysql_adapter_spec.rb +85 -0
  75. data/spec/integration/postgres_adapter_spec.rb +731 -0
  76. data/spec/integration/property_spec.rb +233 -0
  77. data/spec/integration/query_spec.rb +506 -0
  78. data/spec/integration/repository_spec.rb +57 -0
  79. data/spec/integration/resource_spec.rb +475 -0
  80. data/spec/integration/sqlite3_adapter_spec.rb +352 -0
  81. data/spec/integration/sti_spec.rb +208 -0
  82. data/spec/integration/strategic_eager_loading_spec.rb +138 -0
  83. data/spec/integration/transaction_spec.rb +75 -0
  84. data/spec/integration/type_spec.rb +271 -0
  85. data/spec/lib/logging_helper.rb +18 -0
  86. data/spec/lib/mock_adapter.rb +27 -0
  87. data/spec/lib/model_loader.rb +91 -0
  88. data/spec/lib/publicize_methods.rb +28 -0
  89. data/spec/models/vehicles.rb +34 -0
  90. data/spec/models/zoo.rb +47 -0
  91. data/spec/spec.opts +3 -0
  92. data/spec/spec_helper.rb +86 -0
  93. data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
  94. data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
  95. data/spec/unit/adapters/data_objects_adapter_spec.rb +628 -0
  96. data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
  97. data/spec/unit/associations/many_to_many_spec.rb +17 -0
  98. data/spec/unit/associations/many_to_one_spec.rb +152 -0
  99. data/spec/unit/associations/one_to_many_spec.rb +393 -0
  100. data/spec/unit/associations/one_to_one_spec.rb +7 -0
  101. data/spec/unit/associations/relationship_spec.rb +71 -0
  102. data/spec/unit/associations_spec.rb +242 -0
  103. data/spec/unit/auto_migrations_spec.rb +111 -0
  104. data/spec/unit/collection_spec.rb +182 -0
  105. data/spec/unit/data_mapper_spec.rb +35 -0
  106. data/spec/unit/identity_map_spec.rb +126 -0
  107. data/spec/unit/is_spec.rb +80 -0
  108. data/spec/unit/migrator_spec.rb +33 -0
  109. data/spec/unit/model_spec.rb +339 -0
  110. data/spec/unit/naming_conventions_spec.rb +36 -0
  111. data/spec/unit/property_set_spec.rb +83 -0
  112. data/spec/unit/property_spec.rb +753 -0
  113. data/spec/unit/query_spec.rb +530 -0
  114. data/spec/unit/repository_spec.rb +93 -0
  115. data/spec/unit/resource_spec.rb +626 -0
  116. data/spec/unit/scope_spec.rb +142 -0
  117. data/spec/unit/transaction_spec.rb +493 -0
  118. data/spec/unit/type_map_spec.rb +114 -0
  119. data/spec/unit/type_spec.rb +119 -0
  120. data/tasks/ci.rb +68 -0
  121. data/tasks/dm.rb +63 -0
  122. data/tasks/doc.rb +20 -0
  123. data/tasks/gemspec.rb +23 -0
  124. data/tasks/hoe.rb +46 -0
  125. data/tasks/install.rb +20 -0
  126. metadata +216 -0
@@ -0,0 +1,15 @@
1
+
2
+ describe "a DataMapper Adapter", :shared => true do
3
+
4
+ it "should initialize the connection uri" do
5
+ new_adapter = @adapter.class.new(:default, Addressable::URI.parse('some://uri/string'))
6
+ new_adapter.instance_variable_get('@uri').to_s.should == Addressable::URI.parse('some://uri/string').to_s
7
+ end
8
+
9
+ %w{create read_many read_one update delete create_model_storage alter_model_storage destroy_model_storage create_property_storage alter_property_storage destroy_property_storage} .each do |meth|
10
+ it "should have a #{meth} method" do
11
+ @adapter.should respond_to(meth.intern)
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,628 @@
1
+ require 'monitor'
2
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
3
+
4
+ require DataMapper.root / 'spec' / 'unit' / 'adapters' / 'adapter_shared_spec'
5
+
6
+ # TODO: make a shared adapter spec for all the DAO objects to adhere to
7
+
8
+ describe DataMapper::Adapters::DataObjectsAdapter do
9
+ before :all do
10
+ class Cheese
11
+ include DataMapper::Resource
12
+ property :id, Serial
13
+ property :name, String, :nullable => false
14
+ property :color, String, :default => 'yellow'
15
+ property :notes, String, :length => 100, :lazy => true
16
+ end
17
+ end
18
+
19
+ before do
20
+ @uri = Addressable::URI.parse('mock://localhost')
21
+ @adapter = DataMapper::Adapters::DataObjectsAdapter.new(:default, @uri)
22
+ end
23
+
24
+ it_should_behave_like 'a DataMapper Adapter'
25
+
26
+ describe "#find_by_sql" do
27
+
28
+ before do
29
+ class Plupp
30
+ include DataMapper::Resource
31
+ property :id, Integer, :key => true
32
+ property :name, String
33
+ end
34
+ end
35
+
36
+ it "should be added to DataMapper::Model" do
37
+ DataMapper::Model.instance_methods.include?("find_by_sql").should == true
38
+ Plupp.should respond_to(:find_by_sql)
39
+ end
40
+
41
+ describe "when called" do
42
+
43
+ before do
44
+ @reader = mock("reader")
45
+ @reader.stub!(:next!).and_return(false)
46
+ @reader.stub!(:close)
47
+ @connection = mock("connection")
48
+ @connection.stub!(:close)
49
+ @command = mock("command")
50
+ @adapter = Plupp.repository.adapter
51
+ @repository = Plupp.repository
52
+ @repository.stub!(:adapter).and_return(@adapter)
53
+ @adapter.stub!(:create_connection).and_return(@connection)
54
+ @adapter.should_receive(:is_a?).any_number_of_times.with(DataMapper::Adapters::DataObjectsAdapter).and_return(true)
55
+ end
56
+
57
+ it "should accept a single String argument with or without options hash" do
58
+ @connection.should_receive(:create_command).twice.with("SELECT * FROM plupps").and_return(@command)
59
+ @command.should_receive(:execute_reader).twice.and_return(@reader)
60
+ Plupp.should_receive(:repository).any_number_of_times.and_return(@repository)
61
+ Plupp.should_receive(:repository).any_number_of_times.with(:plupp_repo).and_return(@repository)
62
+ Plupp.find_by_sql("SELECT * FROM plupps").to_a
63
+ Plupp.find_by_sql("SELECT * FROM plupps", :repository => :plupp_repo).to_a
64
+ end
65
+
66
+ it "should accept an Array argument with or without options hash" do
67
+ @connection.should_receive(:create_command).twice.with("SELECT * FROM plupps WHERE plur = ?").and_return(@command)
68
+ @command.should_receive(:execute_reader).twice.with("my pretty plur").and_return(@reader)
69
+ Plupp.should_receive(:repository).any_number_of_times.and_return(@repository)
70
+ Plupp.should_receive(:repository).any_number_of_times.with(:plupp_repo).and_return(@repository)
71
+ Plupp.find_by_sql(["SELECT * FROM plupps WHERE plur = ?", "my pretty plur"]).to_a
72
+ Plupp.find_by_sql(["SELECT * FROM plupps WHERE plur = ?", "my pretty plur"], :repository => :plupp_repo).to_a
73
+ end
74
+
75
+ it "should accept a Query argument with or without options hash" do
76
+ @connection.should_receive(:create_command).twice.with('SELECT "name" FROM "plupps" WHERE ("name" = ?) ORDER BY "id"').and_return(@command)
77
+ @command.should_receive(:execute_reader).twice.with('my pretty plur').and_return(@reader)
78
+ Plupp.should_receive(:repository).any_number_of_times.and_return(@repository)
79
+ Plupp.should_receive(:repository).any_number_of_times.with(:plupp_repo).and_return(@repository)
80
+ Plupp.find_by_sql(DataMapper::Query.new(@repository, Plupp, "name" => "my pretty plur", :fields => ["name"])).to_a
81
+ Plupp.find_by_sql(DataMapper::Query.new(@repository, Plupp, "name" => "my pretty plur", :fields => ["name"]), :repository => :plupp_repo).to_a
82
+ end
83
+
84
+ it "requires a Repository that is a DataObjectsRepository to work" do
85
+ non_do_adapter = mock("non do adapter")
86
+ non_do_repo = mock("non do repo")
87
+ non_do_repo.stub!(:adapter).and_return(non_do_adapter)
88
+ Plupp.should_receive(:repository).any_number_of_times.with(:plupp_repo).and_return(non_do_repo)
89
+ Proc.new do
90
+ Plupp.find_by_sql(:repository => :plupp_repo)
91
+ end.should raise_error(Exception, /DataObjectsAdapter/)
92
+ end
93
+
94
+ it "requires some kind of query to work at all" do
95
+ Plupp.should_receive(:repository).any_number_of_times.with(:plupp_repo).and_return(@repository)
96
+ Proc.new do
97
+ Plupp.find_by_sql(:repository => :plupp_repo)
98
+ end.should raise_error(Exception, /requires a query/)
99
+ end
100
+
101
+ end
102
+
103
+ end
104
+
105
+ describe '#uri options' do
106
+ it 'should transform a fully specified option hash into a URI' do
107
+ options = {
108
+ :adapter => 'mysql',
109
+ :host => 'davidleal.com',
110
+ :username => 'me',
111
+ :password => 'mypass',
112
+ :port => 5000,
113
+ :database => 'you_can_call_me_al',
114
+ :socket => 'nosock'
115
+ }
116
+
117
+ adapter = DataMapper::Adapters::DataObjectsAdapter.new(:spec, options)
118
+ adapter.uri.should ==
119
+ Addressable::URI.parse("mysql://me:mypass@davidleal.com:5000/you_can_call_me_al?socket=nosock")
120
+ end
121
+
122
+ it 'should transform a minimal options hash into a URI' do
123
+ options = {
124
+ :adapter => 'mysql',
125
+ :database => 'you_can_call_me_al'
126
+ }
127
+
128
+ adapter = DataMapper::Adapters::DataObjectsAdapter.new(:spec, options)
129
+ adapter.uri.should == Addressable::URI.parse("mysql:you_can_call_me_al")
130
+ end
131
+
132
+ it 'should accept the uri when no overrides exist' do
133
+ uri = Addressable::URI.parse("protocol:///")
134
+ DataMapper::Adapters::DataObjectsAdapter.new(:spec, uri).uri.should == uri
135
+ end
136
+ end
137
+
138
+ describe '#create' do
139
+ before do
140
+ @result = mock('result', :to_i => 1, :insert_id => 1)
141
+
142
+ @adapter.stub!(:execute).and_return(@result)
143
+ @adapter.stub!(:supports_returning?).and_return(false)
144
+
145
+ @property = mock('property', :kind_of? => true, :serial? => true, :name => :property, :field => 'property', :custom? => false, :typecast => 'bind value')
146
+ @properties = [ @property ]
147
+ @bind_values = [ 'bind value' ]
148
+ @attributes = mock('attributes', :keys => @properties, :values => @bind_values)
149
+ @model = mock('model', :kind_of? => true, :key => [ @property ], :storage_name => 'models')
150
+ @resource = mock('resource', :model => @model, :dirty_attributes => @attributes)
151
+
152
+ @property.stub!(:set!).and_return(@resource)
153
+
154
+ @statement = 'INSERT INTO "models" ("property") VALUES (?)'
155
+ end
156
+
157
+ def do_create
158
+ @adapter.create([ @resource ])
159
+ end
160
+
161
+ it 'should use only dirty properties' do
162
+ @resource.should_receive(:dirty_attributes).with(no_args).and_return(@attributes)
163
+ do_create.should == 1
164
+ end
165
+
166
+ it 'should use the bind values' do
167
+ @attributes.should_receive(:values).with(no_args).and_return(@bind_values)
168
+
169
+ @adapter.should_receive(:execute).with(@statement, *@bind_values).and_return(@result)
170
+
171
+ do_create.should == 1
172
+ end
173
+
174
+ it 'should generate an SQL statement when supports_returning? is true' do
175
+ @property.should_receive(:serial?).with(no_args).and_return(true)
176
+ @adapter.should_receive(:supports_returning?).with(no_args).and_return(true)
177
+
178
+ @statement = 'INSERT INTO "models" ("property") VALUES (?) RETURNING "property"'
179
+ @adapter.should_receive(:execute).with(@statement, 'bind value').and_return(@result)
180
+
181
+ do_create.should == 1
182
+ end
183
+
184
+ it 'should generate an SQL statement when supports_default_values? is true' do
185
+ @bind_values.clear
186
+ @properties.clear
187
+ @adapter.should_receive(:supports_default_values?).with(no_args).and_return(true)
188
+
189
+ @statement = 'INSERT INTO "models" DEFAULT VALUES'
190
+ @adapter.should_receive(:execute).with(@statement).and_return(@result)
191
+
192
+ do_create.should == 1
193
+ end
194
+
195
+ it 'should generate an SQL statement when supports_default_values? is false' do
196
+ @bind_values.clear
197
+ @properties.clear
198
+ @adapter.should_receive(:supports_default_values?).with(no_args).and_return(false)
199
+
200
+ @statement = 'INSERT INTO "models" () VALUES ()'
201
+ @adapter.should_receive(:execute).with(@statement).and_return(@result)
202
+
203
+ do_create.should == 1
204
+ end
205
+
206
+ it 'should return 0 if no rows created' do
207
+ @result.should_receive(:to_i).with(no_args).and_return(0)
208
+ do_create.should == 0
209
+ end
210
+
211
+ it 'should return 1 if number of rows created is 1' do
212
+ @result.should_receive(:to_i).with(no_args).and_return(1)
213
+ do_create.should == 1
214
+ end
215
+
216
+ it 'should set the resource primary key if the model key size is 1 and the key is serial' do
217
+ @model.key.size.should == 1
218
+ @property.should_receive(:serial?).and_return(true)
219
+ @result.should_receive(:insert_id).and_return(777)
220
+ @property.should_receive(:set!).with(@resource, 777)
221
+ do_create.should == 1
222
+ end
223
+ end
224
+
225
+ [ :read_many, :read_one ].each do |method|
226
+ describe "##{method}" do
227
+ before do
228
+ @key = mock('key')
229
+ @model = mock('model', :key => @key, :storage_name => 'models', :relationships => {})
230
+ @primitive = mock('primitive')
231
+ @property = mock('property', :kind_of? => true, :model => @model, :field => 'property', :primitive => @primitive)
232
+
233
+ @child_model = @model
234
+ @parent_model = mock('parent model', :storage_name => 'parents')
235
+ @parent_property = mock('parent id', :kind_of? => true, :model => @parent_model, :field => 'id')
236
+
237
+ @child_key = [ @property ]
238
+ @parent_key = [ @parent_property ]
239
+ @relationship = mock('relationship', :child_model => @child_model, :parent_model => @parent_model, :child_key => @child_key, :parent_key => @parent_key)
240
+ @links = [ @relationship ]
241
+
242
+ @fields = [ @property ]
243
+ @bind_values = [ 'bind value' ]
244
+ @conditions = [ [ :eql, @property, @bind_values[0] ] ]
245
+
246
+ @direction = mock('direction', :property => @property, :direction => :desc)
247
+ @order = [ @direction ]
248
+
249
+ @query = mock('query', :model => @model, :kind_of? => true, :links => @links, :fields => @fields, :conditions => @conditions, :order => @order, :limit => 111, :offset => 222, :bind_values => @bind_values)
250
+ @query.should_receive(:unique?).with(no_args).and_return(false)
251
+
252
+ @reader = mock('reader', :close => true, :next! => false)
253
+ @command = mock('command', :set_types => nil, :execute_reader => @reader)
254
+ @connection = mock('connection', :close => true, :create_command => @command)
255
+
256
+ DataObjects::Connection.stub!(:new).and_return(@connection)
257
+ DataMapper::Query::Direction.stub!(:===).and_return(true)
258
+ end
259
+
260
+ if method == :read_one
261
+ before do
262
+ @query.should_receive(:limit).with(no_args).twice.and_return(1)
263
+
264
+ @values = @bind_values.dup
265
+
266
+ @reader.should_receive(:next!).with(no_args).and_return(true)
267
+ @reader.should_receive(:values).with(no_args).and_return(@values)
268
+
269
+ @resource = mock('resource')
270
+ @resource.should_receive(:kind_of?).with(DataMapper::Resource).any_number_of_times.and_return(true)
271
+
272
+ @model.should_receive(:load).with(@values, @query).and_return(@resource)
273
+
274
+ @statement = 'SELECT "models"."property" FROM "models" INNER JOIN "parents" ON ("parents"."id" = "models"."property") WHERE ("models"."property" = ?) ORDER BY "models"."property" DESC LIMIT 1 OFFSET 222'
275
+ end
276
+
277
+ define_method(:do_read) do
278
+ resource = @adapter.read_one(@query)
279
+ resource.should == @resource
280
+ resource
281
+ end
282
+ elsif method == :read_many
283
+ before do
284
+ @statement = 'SELECT "models"."property" FROM "models" INNER JOIN "parents" ON ("parents"."id" = "models"."property") WHERE ("models"."property" = ?) ORDER BY "models"."property" DESC LIMIT 111 OFFSET 222'
285
+ end
286
+
287
+ define_method(:do_read) do
288
+ collection = @adapter.read_many(@query)
289
+ collection.to_a
290
+ collection
291
+ end
292
+ end
293
+
294
+ it 'should use the bind values' do
295
+ @command.should_receive(:execute_reader).with(*@bind_values).and_return(@reader)
296
+ do_read
297
+ end
298
+
299
+ it 'should generate an SQL statement' do
300
+ @connection.should_receive(:create_command).with(@statement).and_return(@command)
301
+ do_read
302
+ end
303
+
304
+ it 'should generate an SQL statement with composite keys' do
305
+ other_property = mock('other property', :kind_of? => true)
306
+ other_property.should_receive(:field).with(:default).and_return('other')
307
+ other_property.should_receive(:model).with(no_args).and_return(@model)
308
+
309
+ other_value = 'other value'
310
+ @bind_values << other_value
311
+ @conditions << [ :eql, other_property, other_value ]
312
+
313
+ @statement = %[SELECT "models"."property" FROM "models" INNER JOIN "parents" ON ("parents"."id" = "models"."property") WHERE ("models"."property" = ?) AND ("models"."other" = ?) ORDER BY "models"."property" DESC LIMIT #{method == :read_one ? '1' : '111'} OFFSET 222]
314
+ @query.should_receive(:conditions).with(no_args).twice.and_return(@conditions)
315
+
316
+ @connection.should_receive(:create_command).with(@statement).and_return(@command)
317
+
318
+ do_read
319
+ end
320
+
321
+ it 'should set the return types to the property primitives' do
322
+ @command.should_receive(:set_types).with([ @primitive ])
323
+ do_read
324
+ end
325
+
326
+ it 'should close the reader' do
327
+ @reader.should_receive(:close).with(no_args)
328
+ do_read
329
+ end
330
+
331
+ it 'should close the connection' do
332
+ @connection.should_receive(:close).with(no_args)
333
+ do_read
334
+ end
335
+
336
+ if method == :read_one
337
+ it 'should return a DataMapper::Resource' do
338
+ do_read.should == be_kind_of(DataMapper::Resource)
339
+ end
340
+ else
341
+ it 'should return a DataMapper::Collection' do
342
+ do_read.should be_kind_of(DataMapper::Collection)
343
+ end
344
+ end
345
+ end
346
+ end
347
+
348
+ describe '#update' do
349
+ before do
350
+ @result = mock('result', :to_i => 1)
351
+
352
+ @adapter.stub!(:execute).and_return(@result)
353
+
354
+ @values = %w[ new ]
355
+ @model = mock('model', :storage_name => 'models')
356
+ @property = mock('property', :kind_of? => true, :field => 'property')
357
+ @bind_values = [ 'bind value' ]
358
+ @conditions = [ [ :eql, @property, @bind_values[0] ] ]
359
+ @attributes = mock('attributes', :kind_of? => true, :empty? => false, :keys => [ @property ], :values => @values)
360
+ @query = mock('query', :kind_of? => true, :model => @model, :links => [], :conditions => @conditions, :bind_values => @bind_values)
361
+ @statement = 'UPDATE "models" SET "property" = ? WHERE ("property" = ?)'
362
+ end
363
+
364
+ def do_update
365
+ @adapter.update(@attributes, @query)
366
+ end
367
+
368
+ it 'should use the bind values' do
369
+ @attributes.should_receive(:values).with(no_args).and_return(@values)
370
+ @query.should_receive(:bind_values).with(no_args).and_return(@bind_values)
371
+
372
+ @adapter.should_receive(:execute).with(@statement, *@values + @bind_values).and_return(@result)
373
+
374
+ do_update.should == 1
375
+ end
376
+
377
+ it 'should generate an SQL statement' do
378
+ other_property = mock('other property', :kind_of? => true)
379
+ other_property.should_receive(:field).with(:default).and_return('other')
380
+ other_property.should_receive(:model).with(no_args).and_return(@model)
381
+
382
+ other_value = 'other value'
383
+ @bind_values << other_value
384
+ @conditions << [ :eql, other_property, other_value ]
385
+
386
+ @query.should_receive(:conditions).with(no_args).twice.and_return(@conditions)
387
+
388
+ @statement = 'UPDATE "models" SET "property" = ? WHERE ("property" = ?) AND ("other" = ?)'
389
+ @adapter.should_receive(:execute).with(@statement, *%w[ new ] + @bind_values).and_return(@result)
390
+
391
+ do_update.should == 1
392
+ end
393
+
394
+ it 'should return 0 if no rows updated' do
395
+ @result.should_receive(:to_i).with(no_args).and_return(0)
396
+ do_update.should == 0
397
+ end
398
+
399
+ it 'should return 1 if number of rows updated is 1' do
400
+ @result.should_receive(:to_i).with(no_args).and_return(1)
401
+ do_update.should == 1
402
+ end
403
+ end
404
+
405
+ describe '#delete' do
406
+ before do
407
+ @result = mock('result', :to_i => 1)
408
+
409
+ @adapter.stub!(:execute).and_return(@result)
410
+
411
+ @model = mock('model', :storage_name => 'models')
412
+ @property = mock('property', :kind_of? => true, :field => 'property')
413
+ @bind_values = [ 'bind value' ]
414
+ @conditions = [ [ :eql, @property, @bind_values[0] ] ]
415
+ @query = mock('query', :kind_of? => true, :model => @model, :links => [], :conditions => @conditions, :bind_values => @bind_values)
416
+ @resource = mock('resource', :to_query => @query)
417
+ @statement = 'DELETE FROM "models" WHERE ("property" = ?)'
418
+ end
419
+
420
+ def do_delete
421
+ @adapter.delete(@resource.to_query(@repository))
422
+ end
423
+
424
+ it 'should use the bind values' do
425
+ @query.should_receive(:bind_values).with(no_args).and_return(@bind_values)
426
+
427
+ @adapter.should_receive(:execute).with(@statement, *@bind_values).and_return(@result)
428
+
429
+ do_delete.should == 1
430
+ end
431
+
432
+ it 'should generate an SQL statement' do
433
+ other_property = mock('other property', :kind_of? => true)
434
+ other_property.should_receive(:field).with(:default).and_return('other')
435
+ other_property.should_receive(:model).with(no_args).and_return(@model)
436
+
437
+ other_value = 'other value'
438
+ @bind_values << other_value
439
+ @conditions << [ :eql, other_property, other_value ]
440
+
441
+ @query.should_receive(:conditions).with(no_args).twice.and_return(@conditions)
442
+
443
+ @statement = 'DELETE FROM "models" WHERE ("property" = ?) AND ("other" = ?)'
444
+ @adapter.should_receive(:execute).with(@statement, *@bind_values).and_return(@result)
445
+
446
+ do_delete.should == 1
447
+ end
448
+
449
+ it 'should return 0 if no rows deleted' do
450
+ @result.should_receive(:to_i).with(no_args).and_return(0)
451
+ do_delete.should == 0
452
+ end
453
+
454
+ it 'should return 1 if number of rows deleted is 1' do
455
+ @result.should_receive(:to_i).with(no_args).and_return(1)
456
+ do_delete.should == 1
457
+ end
458
+ end
459
+
460
+ describe "when upgrading tables" do
461
+ it "should raise NotImplementedError when #storage_exists? is called" do
462
+ lambda { @adapter.storage_exists?("cheeses") }.should raise_error(NotImplementedError)
463
+ end
464
+
465
+ describe "#upgrade_model_storage" do
466
+ it "should call #create_model_storage" do
467
+ @adapter.should_receive(:create_model_storage).with(repository, Cheese).and_return(true)
468
+ @adapter.upgrade_model_storage(repository, Cheese).should == Cheese.properties
469
+ end
470
+
471
+ it "should check if all properties of the model have columns if the table exists" do
472
+ @adapter.should_receive(:field_exists?).with("cheeses", "id").and_return(true)
473
+ @adapter.should_receive(:field_exists?).with("cheeses", "name").and_return(true)
474
+ @adapter.should_receive(:field_exists?).with("cheeses", "color").and_return(true)
475
+ @adapter.should_receive(:field_exists?).with("cheeses", "notes").and_return(true)
476
+ @adapter.should_receive(:storage_exists?).with("cheeses").and_return(true)
477
+ @adapter.upgrade_model_storage(repository, Cheese).should == []
478
+ end
479
+
480
+ it "should create and execute add column statements for columns that dont exist" do
481
+ @adapter.should_receive(:field_exists?).with("cheeses", "id").and_return(true)
482
+ @adapter.should_receive(:field_exists?).with("cheeses", "name").and_return(true)
483
+ @adapter.should_receive(:field_exists?).with("cheeses", "color").and_return(true)
484
+ @adapter.should_receive(:field_exists?).with("cheeses", "notes").and_return(false)
485
+ @adapter.should_receive(:storage_exists?).with("cheeses").and_return(true)
486
+ connection = mock("connection")
487
+ connection.should_receive(:close)
488
+ @adapter.should_receive(:create_connection).and_return(connection)
489
+ statement = mock("statement")
490
+ command = mock("command")
491
+ result = mock("result")
492
+ command.should_receive(:execute_non_query).and_return(result)
493
+ connection.should_receive(:create_command).with(statement).and_return(command)
494
+ @adapter.should_receive(:alter_table_add_column_statement).with("cheeses",
495
+ {
496
+ :nullable? => true,
497
+ :name => "notes",
498
+ :serial? => false,
499
+ :primitive => "VARCHAR",
500
+ :size => 100
501
+ }).and_return(statement)
502
+ @adapter.upgrade_model_storage(repository, Cheese).should == [Cheese.notes]
503
+ end
504
+ end
505
+ end
506
+
507
+ describe '#execute' do
508
+ before do
509
+ @mock_command = mock('Command', :execute_non_query => nil)
510
+ @mock_db = mock('DB Connection', :create_command => @mock_command, :close => true)
511
+
512
+ @adapter.stub!(:create_connection).and_return(@mock_db)
513
+ end
514
+
515
+ it 'should #create_command from the sql passed' do
516
+ @mock_db.should_receive(:create_command).with('SQL STRING').and_return(@mock_command)
517
+ @adapter.execute('SQL STRING')
518
+ end
519
+
520
+ it 'should pass any additional args to #execute_non_query' do
521
+ @mock_command.should_receive(:execute_non_query).with(:args)
522
+ @adapter.execute('SQL STRING', :args)
523
+ end
524
+
525
+ it 'should return the result of #execute_non_query' do
526
+ @mock_command.should_receive(:execute_non_query).and_return(:result_set)
527
+
528
+ @adapter.execute('SQL STRING').should == :result_set
529
+ end
530
+
531
+ it 'should log any errors, then re-raise them' do
532
+ @mock_command.should_receive(:execute_non_query).and_raise("Oh Noes!")
533
+ DataMapper.logger.should_receive(:error)
534
+
535
+ lambda { @adapter.execute('SQL STRING') }.should raise_error("Oh Noes!")
536
+ end
537
+
538
+ it 'should always close the db connection' do
539
+ @mock_command.should_receive(:execute_non_query).and_raise("Oh Noes!")
540
+ @mock_db.should_receive(:close)
541
+
542
+ lambda { @adapter.execute('SQL STRING') }.should raise_error("Oh Noes!")
543
+ end
544
+ end
545
+
546
+ describe '#query' do
547
+ before do
548
+ @mock_reader = mock('Reader', :fields => ['id', 'UserName', 'AGE'],
549
+ :values => [1, 'rando', 27],
550
+ :close => true)
551
+ @mock_command = mock('Command', :execute_reader => @mock_reader)
552
+ @mock_db = mock('DB Connection', :create_command => @mock_command, :close => true)
553
+
554
+ #make the while loop run exactly once
555
+ @mock_reader.stub!(:next!).and_return(true, nil)
556
+ @adapter.stub!(:create_connection).and_return(@mock_db)
557
+ end
558
+
559
+ it 'should #create_command from the sql passed' do
560
+ @mock_db.should_receive(:create_command).with('SQL STRING').and_return(@mock_command)
561
+ @adapter.query('SQL STRING')
562
+ end
563
+
564
+ it 'should pass any additional args to #execute_reader' do
565
+ @mock_command.should_receive(:execute_reader).with(:args).and_return(@mock_reader)
566
+ @adapter.query('SQL STRING', :args)
567
+ end
568
+
569
+ describe 'returning multiple fields' do
570
+
571
+ it 'should underscore the field names as members of the result struct' do
572
+ @mock_reader.should_receive(:fields).and_return(['id', 'UserName', 'AGE'])
573
+
574
+ result = @adapter.query('SQL STRING')
575
+
576
+ result.first.members.should == %w{id user_name age}
577
+ end
578
+
579
+ it 'should convert each row into the struct' do
580
+ @mock_reader.should_receive(:values).and_return([1, 'rando', 27])
581
+
582
+ @adapter.query('SQL STRING')
583
+ end
584
+
585
+ it 'should add the row structs into the results array' do
586
+ results = @adapter.query('SQL STRING')
587
+
588
+ results.should be_kind_of(Array)
589
+
590
+ row = results.first
591
+ row.should be_kind_of(Struct)
592
+
593
+ row.id.should == 1
594
+ row.user_name.should == 'rando'
595
+ row.age.should == 27
596
+ end
597
+
598
+ end
599
+
600
+ describe 'returning a single field' do
601
+
602
+ it 'should add the value to the results array' do
603
+ @mock_reader.should_receive(:fields).and_return(['username'])
604
+ @mock_reader.should_receive(:values).and_return(['rando'])
605
+
606
+ results = @adapter.query('SQL STRING')
607
+
608
+ results.should be_kind_of(Array)
609
+ results.first.should == 'rando'
610
+ end
611
+
612
+ end
613
+
614
+ it 'should log any errors, then re-raise them' do
615
+ @mock_command.should_receive(:execute_non_query).and_raise("Oh Noes!")
616
+ DataMapper.logger.should_receive(:error)
617
+
618
+ lambda { @adapter.execute('SQL STRING') }.should raise_error("Oh Noes!")
619
+ end
620
+
621
+ it 'should always close the db connection' do
622
+ @mock_command.should_receive(:execute_non_query).and_raise("Oh Noes!")
623
+ @mock_db.should_receive(:close)
624
+
625
+ lambda { @adapter.execute('SQL STRING') }.should raise_error("Oh Noes!")
626
+ end
627
+ end
628
+ end