rpbertp13-dm-core 0.9.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +52 -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 +32 -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 +138 -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 +221 -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 +252 -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 +60 -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 +469 -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