sam-dm-core 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +26 -0
- data/CONTRIBUTING +51 -0
- data/FAQ +92 -0
- data/History.txt +145 -0
- data/MIT-LICENSE +22 -0
- data/Manifest.txt +125 -0
- data/QUICKLINKS +12 -0
- data/README.txt +143 -0
- data/Rakefile +30 -0
- data/SPECS +63 -0
- data/TODO +1 -0
- data/lib/dm-core.rb +224 -0
- data/lib/dm-core/adapters.rb +4 -0
- data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
- data/lib/dm-core/adapters/data_objects_adapter.rb +707 -0
- data/lib/dm-core/adapters/mysql_adapter.rb +136 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +188 -0
- data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
- data/lib/dm-core/associations.rb +199 -0
- data/lib/dm-core/associations/many_to_many.rb +147 -0
- data/lib/dm-core/associations/many_to_one.rb +107 -0
- data/lib/dm-core/associations/one_to_many.rb +309 -0
- data/lib/dm-core/associations/one_to_one.rb +61 -0
- data/lib/dm-core/associations/relationship.rb +218 -0
- data/lib/dm-core/associations/relationship_chain.rb +81 -0
- data/lib/dm-core/auto_migrations.rb +113 -0
- data/lib/dm-core/collection.rb +638 -0
- data/lib/dm-core/dependency_queue.rb +31 -0
- data/lib/dm-core/hook.rb +11 -0
- data/lib/dm-core/identity_map.rb +45 -0
- data/lib/dm-core/is.rb +16 -0
- data/lib/dm-core/logger.rb +232 -0
- data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
- data/lib/dm-core/migrator.rb +29 -0
- data/lib/dm-core/model.rb +471 -0
- data/lib/dm-core/naming_conventions.rb +84 -0
- data/lib/dm-core/property.rb +673 -0
- data/lib/dm-core/property_set.rb +162 -0
- data/lib/dm-core/query.rb +625 -0
- data/lib/dm-core/repository.rb +159 -0
- data/lib/dm-core/resource.rb +637 -0
- data/lib/dm-core/scope.rb +58 -0
- data/lib/dm-core/support.rb +7 -0
- data/lib/dm-core/support/array.rb +13 -0
- data/lib/dm-core/support/assertions.rb +8 -0
- data/lib/dm-core/support/errors.rb +23 -0
- data/lib/dm-core/support/kernel.rb +7 -0
- data/lib/dm-core/support/symbol.rb +41 -0
- data/lib/dm-core/transaction.rb +267 -0
- data/lib/dm-core/type.rb +160 -0
- data/lib/dm-core/type_map.rb +80 -0
- data/lib/dm-core/types.rb +19 -0
- data/lib/dm-core/types/boolean.rb +7 -0
- data/lib/dm-core/types/discriminator.rb +34 -0
- data/lib/dm-core/types/object.rb +24 -0
- data/lib/dm-core/types/paranoid_boolean.rb +34 -0
- data/lib/dm-core/types/paranoid_datetime.rb +33 -0
- data/lib/dm-core/types/serial.rb +9 -0
- data/lib/dm-core/types/text.rb +10 -0
- data/lib/dm-core/version.rb +3 -0
- data/script/all +5 -0
- data/script/performance.rb +203 -0
- data/script/profile.rb +87 -0
- data/spec/integration/association_spec.rb +1371 -0
- data/spec/integration/association_through_spec.rb +203 -0
- data/spec/integration/associations/many_to_many_spec.rb +449 -0
- data/spec/integration/associations/many_to_one_spec.rb +163 -0
- data/spec/integration/associations/one_to_many_spec.rb +151 -0
- data/spec/integration/auto_migrations_spec.rb +398 -0
- data/spec/integration/collection_spec.rb +1069 -0
- data/spec/integration/data_objects_adapter_spec.rb +32 -0
- data/spec/integration/dependency_queue_spec.rb +58 -0
- data/spec/integration/model_spec.rb +127 -0
- data/spec/integration/mysql_adapter_spec.rb +85 -0
- data/spec/integration/postgres_adapter_spec.rb +731 -0
- data/spec/integration/property_spec.rb +233 -0
- data/spec/integration/query_spec.rb +506 -0
- data/spec/integration/repository_spec.rb +57 -0
- data/spec/integration/resource_spec.rb +475 -0
- data/spec/integration/sqlite3_adapter_spec.rb +352 -0
- data/spec/integration/sti_spec.rb +208 -0
- data/spec/integration/strategic_eager_loading_spec.rb +138 -0
- data/spec/integration/transaction_spec.rb +75 -0
- data/spec/integration/type_spec.rb +271 -0
- data/spec/lib/logging_helper.rb +18 -0
- data/spec/lib/mock_adapter.rb +27 -0
- data/spec/lib/model_loader.rb +91 -0
- data/spec/lib/publicize_methods.rb +28 -0
- data/spec/models/vehicles.rb +34 -0
- data/spec/models/zoo.rb +47 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +86 -0
- data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
- data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
- data/spec/unit/adapters/data_objects_adapter_spec.rb +628 -0
- data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
- data/spec/unit/associations/many_to_many_spec.rb +17 -0
- data/spec/unit/associations/many_to_one_spec.rb +152 -0
- data/spec/unit/associations/one_to_many_spec.rb +393 -0
- data/spec/unit/associations/one_to_one_spec.rb +7 -0
- data/spec/unit/associations/relationship_spec.rb +71 -0
- data/spec/unit/associations_spec.rb +242 -0
- data/spec/unit/auto_migrations_spec.rb +111 -0
- data/spec/unit/collection_spec.rb +182 -0
- data/spec/unit/data_mapper_spec.rb +35 -0
- data/spec/unit/identity_map_spec.rb +126 -0
- data/spec/unit/is_spec.rb +80 -0
- data/spec/unit/migrator_spec.rb +33 -0
- data/spec/unit/model_spec.rb +339 -0
- data/spec/unit/naming_conventions_spec.rb +36 -0
- data/spec/unit/property_set_spec.rb +83 -0
- data/spec/unit/property_spec.rb +753 -0
- data/spec/unit/query_spec.rb +530 -0
- data/spec/unit/repository_spec.rb +93 -0
- data/spec/unit/resource_spec.rb +626 -0
- data/spec/unit/scope_spec.rb +142 -0
- data/spec/unit/transaction_spec.rb +493 -0
- data/spec/unit/type_map_spec.rb +114 -0
- data/spec/unit/type_spec.rb +119 -0
- data/tasks/ci.rb +68 -0
- data/tasks/dm.rb +63 -0
- data/tasks/doc.rb +20 -0
- data/tasks/gemspec.rb +23 -0
- data/tasks/hoe.rb +46 -0
- data/tasks/install.rb +20 -0
- 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
|