datamapper-dm-core 0.9.11

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