dm-core 0.10.2 → 1.0.0.rc1
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/.gitignore +10 -1
- data/Gemfile +143 -0
- data/Rakefile +9 -5
- data/VERSION +1 -1
- data/dm-core.gemspec +160 -57
- data/lib/dm-core.rb +131 -56
- data/lib/dm-core/adapters.rb +98 -14
- data/lib/dm-core/adapters/abstract_adapter.rb +24 -4
- data/lib/dm-core/adapters/in_memory_adapter.rb +7 -2
- data/lib/dm-core/associations/many_to_many.rb +19 -30
- data/lib/dm-core/associations/many_to_one.rb +58 -42
- data/lib/dm-core/associations/one_to_many.rb +33 -23
- data/lib/dm-core/associations/one_to_one.rb +27 -11
- data/lib/dm-core/associations/relationship.rb +4 -4
- data/lib/dm-core/collection.rb +23 -16
- data/lib/dm-core/core_ext/array.rb +36 -0
- data/lib/dm-core/core_ext/hash.rb +30 -0
- data/lib/dm-core/core_ext/module.rb +46 -0
- data/lib/dm-core/core_ext/object.rb +31 -0
- data/lib/dm-core/core_ext/pathname.rb +20 -0
- data/lib/dm-core/core_ext/string.rb +22 -0
- data/lib/dm-core/core_ext/try_dup.rb +44 -0
- data/lib/dm-core/model.rb +88 -27
- data/lib/dm-core/model/hook.rb +75 -18
- data/lib/dm-core/model/property.rb +50 -9
- data/lib/dm-core/model/relationship.rb +31 -31
- data/lib/dm-core/model/scope.rb +3 -3
- data/lib/dm-core/property.rb +196 -516
- data/lib/dm-core/property/binary.rb +7 -0
- data/lib/dm-core/property/boolean.rb +35 -0
- data/lib/dm-core/property/class.rb +24 -0
- data/lib/dm-core/property/date.rb +47 -0
- data/lib/dm-core/property/date_time.rb +48 -0
- data/lib/dm-core/property/decimal.rb +43 -0
- data/lib/dm-core/property/discriminator.rb +48 -0
- data/lib/dm-core/property/float.rb +24 -0
- data/lib/dm-core/property/integer.rb +32 -0
- data/lib/dm-core/property/numeric.rb +43 -0
- data/lib/dm-core/property/object.rb +32 -0
- data/lib/dm-core/property/serial.rb +8 -0
- data/lib/dm-core/property/string.rb +49 -0
- data/lib/dm-core/property/text.rb +12 -0
- data/lib/dm-core/property/time.rb +48 -0
- data/lib/dm-core/property/typecast/numeric.rb +32 -0
- data/lib/dm-core/property/typecast/time.rb +28 -0
- data/lib/dm-core/property_set.rb +10 -4
- data/lib/dm-core/query.rb +14 -37
- data/lib/dm-core/query/conditions/comparison.rb +8 -6
- data/lib/dm-core/query/conditions/operation.rb +33 -2
- data/lib/dm-core/query/operator.rb +2 -5
- data/lib/dm-core/query/path.rb +4 -6
- data/lib/dm-core/repository.rb +21 -6
- data/lib/dm-core/resource.rb +316 -133
- data/lib/dm-core/resource/state.rb +79 -0
- data/lib/dm-core/resource/state/clean.rb +40 -0
- data/lib/dm-core/resource/state/deleted.rb +30 -0
- data/lib/dm-core/resource/state/dirty.rb +86 -0
- data/lib/dm-core/resource/state/immutable.rb +34 -0
- data/lib/dm-core/resource/state/persisted.rb +29 -0
- data/lib/dm-core/resource/state/transient.rb +70 -0
- data/lib/dm-core/spec/lib/adapter_helpers.rb +52 -0
- data/lib/dm-core/spec/lib/collection_helpers.rb +20 -0
- data/{spec → lib/dm-core/spec}/lib/counter_adapter.rb +5 -1
- data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
- data/lib/dm-core/spec/lib/spec_helper.rb +68 -0
- data/lib/dm-core/spec/setup.rb +165 -0
- data/lib/dm-core/spec/{adapter_shared_spec.rb → shared/adapter_spec.rb} +21 -7
- data/{spec/public/shared/resource_shared_spec.rb → lib/dm-core/spec/shared/resource_spec.rb} +120 -83
- data/{spec/public/shared/sel_shared_spec.rb → lib/dm-core/spec/shared/sel_spec.rb} +5 -6
- data/lib/dm-core/support/assertions.rb +8 -0
- data/lib/dm-core/support/equalizer.rb +1 -0
- data/lib/dm-core/support/hook.rb +420 -0
- data/lib/dm-core/support/lazy_array.rb +453 -0
- data/lib/dm-core/support/local_object_space.rb +12 -0
- data/lib/dm-core/support/logger.rb +193 -6
- data/lib/dm-core/support/naming_conventions.rb +8 -8
- data/lib/dm-core/support/subject.rb +33 -0
- data/lib/dm-core/type.rb +4 -0
- data/lib/dm-core/types/boolean.rb +2 -0
- data/lib/dm-core/types/decimal.rb +9 -0
- data/lib/dm-core/types/discriminator.rb +2 -0
- data/lib/dm-core/types/object.rb +3 -0
- data/lib/dm-core/types/serial.rb +2 -0
- data/lib/dm-core/types/text.rb +2 -0
- data/lib/dm-core/version.rb +1 -1
- data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +67 -0
- data/spec/public/model/hook_spec.rb +209 -0
- data/spec/public/model/property_spec.rb +35 -0
- data/spec/public/model/relationship_spec.rb +33 -20
- data/spec/public/model_spec.rb +142 -10
- data/spec/public/property/binary_spec.rb +14 -0
- data/spec/public/property/boolean_spec.rb +14 -0
- data/spec/public/property/class_spec.rb +20 -0
- data/spec/public/property/date_spec.rb +14 -0
- data/spec/public/property/date_time_spec.rb +14 -0
- data/spec/public/property/decimal_spec.rb +14 -0
- data/spec/public/{types → property}/discriminator_spec.rb +2 -12
- data/spec/public/property/float_spec.rb +14 -0
- data/spec/public/property/integer_spec.rb +14 -0
- data/spec/public/property/object_spec.rb +9 -17
- data/spec/public/property/serial_spec.rb +14 -0
- data/spec/public/property/string_spec.rb +14 -0
- data/spec/public/property/text_spec.rb +52 -0
- data/spec/public/property/time_spec.rb +14 -0
- data/spec/public/property_spec.rb +28 -87
- data/spec/public/resource_spec.rb +101 -0
- data/spec/public/sel_spec.rb +5 -15
- data/spec/public/shared/collection_shared_spec.rb +16 -30
- data/spec/public/shared/finder_shared_spec.rb +2 -4
- data/spec/public/shared/property_shared_spec.rb +176 -0
- data/spec/semipublic/adapters/abstract_adapter_spec.rb +1 -1
- data/spec/semipublic/adapters/in_memory_adapter_spec.rb +2 -2
- data/spec/semipublic/associations/many_to_many_spec.rb +89 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +24 -1
- data/spec/semipublic/associations/one_to_many_spec.rb +51 -0
- data/spec/semipublic/associations/one_to_one_spec.rb +49 -0
- data/spec/semipublic/associations/relationship_spec.rb +3 -3
- data/spec/semipublic/associations_spec.rb +1 -1
- data/spec/semipublic/property/binary_spec.rb +13 -0
- data/spec/semipublic/property/boolean_spec.rb +65 -0
- data/spec/semipublic/property/class_spec.rb +33 -0
- data/spec/semipublic/property/date_spec.rb +43 -0
- data/spec/semipublic/property/date_time_spec.rb +46 -0
- data/spec/semipublic/property/decimal_spec.rb +82 -0
- data/spec/semipublic/property/discriminator_spec.rb +19 -0
- data/spec/semipublic/property/float_spec.rb +82 -0
- data/spec/semipublic/property/integer_spec.rb +82 -0
- data/spec/semipublic/property/serial_spec.rb +13 -0
- data/spec/semipublic/property/string_spec.rb +13 -0
- data/spec/semipublic/property/text_spec.rb +31 -0
- data/spec/semipublic/property/time_spec.rb +50 -0
- data/spec/semipublic/property_spec.rb +2 -532
- data/spec/semipublic/query/conditions/comparison_spec.rb +171 -169
- data/spec/semipublic/query/conditions/operation_spec.rb +53 -51
- data/spec/semipublic/query/path_spec.rb +17 -17
- data/spec/semipublic/query_spec.rb +47 -78
- data/spec/semipublic/resource/state/clean_spec.rb +88 -0
- data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
- data/spec/semipublic/resource/state/dirty_spec.rb +133 -0
- data/spec/semipublic/resource/state/immutable_spec.rb +99 -0
- data/spec/semipublic/resource/state/transient_spec.rb +128 -0
- data/spec/semipublic/resource/state_spec.rb +226 -0
- data/spec/semipublic/shared/property_shared_spec.rb +143 -0
- data/spec/semipublic/shared/resource_shared_spec.rb +16 -15
- data/spec/semipublic/shared/resource_state_shared_spec.rb +78 -0
- data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
- data/spec/spec_helper.rb +21 -97
- data/spec/support/types/huge_integer.rb +17 -0
- data/spec/unit/array_spec.rb +48 -0
- data/spec/unit/hash_spec.rb +35 -0
- data/spec/unit/hook_spec.rb +1234 -0
- data/spec/unit/lazy_array_spec.rb +1959 -0
- data/spec/unit/module_spec.rb +70 -0
- data/spec/unit/object_spec.rb +37 -0
- data/spec/unit/try_dup_spec.rb +45 -0
- data/tasks/local_gemfile.rake +18 -0
- data/tasks/spec.rake +0 -3
- metadata +197 -71
- data/deps.rip +0 -2
- data/lib/dm-core/adapters/data_objects_adapter.rb +0 -712
- data/lib/dm-core/adapters/mysql_adapter.rb +0 -42
- data/lib/dm-core/adapters/oracle_adapter.rb +0 -229
- data/lib/dm-core/adapters/postgres_adapter.rb +0 -22
- data/lib/dm-core/adapters/sqlite3_adapter.rb +0 -17
- data/lib/dm-core/adapters/sqlserver_adapter.rb +0 -114
- data/lib/dm-core/adapters/yaml_adapter.rb +0 -111
- data/lib/dm-core/core_ext/enumerable.rb +0 -28
- data/lib/dm-core/migrations.rb +0 -1427
- data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +0 -366
- data/lib/dm-core/transaction.rb +0 -508
- data/lib/dm-core/types/paranoid_boolean.rb +0 -42
- data/lib/dm-core/types/paranoid_datetime.rb +0 -41
- data/spec/lib/adapter_helpers.rb +0 -105
- data/spec/lib/collection_helpers.rb +0 -18
- data/spec/lib/pending_helpers.rb +0 -46
- data/spec/public/migrations_spec.rb +0 -503
- data/spec/public/transaction_spec.rb +0 -153
- data/spec/semipublic/adapters/mysql_adapter_spec.rb +0 -17
- data/spec/semipublic/adapters/oracle_adapter_spec.rb +0 -194
- data/spec/semipublic/adapters/postgres_adapter_spec.rb +0 -17
- data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +0 -17
- data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +0 -17
- data/spec/semipublic/adapters/yaml_adapter_spec.rb +0 -12
@@ -1,366 +0,0 @@
|
|
1
|
-
share_examples_for 'A DataObjects Adapter' do
|
2
|
-
before :all do
|
3
|
-
raise '+@adapter+ should be defined in before block' unless instance_variable_get('@adapter')
|
4
|
-
|
5
|
-
@log = StringIO.new
|
6
|
-
|
7
|
-
@original_logger = DataMapper.logger
|
8
|
-
DataMapper.logger = DataMapper::Logger.new(@log, :debug)
|
9
|
-
|
10
|
-
# set up the adapter after switching the logger so queries can be captured
|
11
|
-
@adapter = DataMapper.setup(@adapter.name, @adapter.options)
|
12
|
-
|
13
|
-
@jruby = !!(RUBY_PLATFORM =~ /java/)
|
14
|
-
|
15
|
-
@postgres = defined?(DataMapper::Adapters::PostgresAdapter) && @adapter.kind_of?(DataMapper::Adapters::PostgresAdapter)
|
16
|
-
@mysql = defined?(DataMapper::Adapters::MysqlAdapter) && @adapter.kind_of?(DataMapper::Adapters::MysqlAdapter)
|
17
|
-
@sql_server = defined?(DataMapper::Adapters::SqlserverAdapter) && @adapter.kind_of?(DataMapper::Adapters::SqlserverAdapter)
|
18
|
-
@oracle = defined?(DataMapper::Adapters::OracleAdapter) && @adapter.kind_of?(DataMapper::Adapters::OracleAdapter)
|
19
|
-
end
|
20
|
-
|
21
|
-
after :all do
|
22
|
-
DataMapper.logger = @original_logger
|
23
|
-
end
|
24
|
-
|
25
|
-
def reset_log
|
26
|
-
@log.truncate(0)
|
27
|
-
@log.rewind
|
28
|
-
end
|
29
|
-
|
30
|
-
def log_output
|
31
|
-
@log.rewind
|
32
|
-
@log.read.chomp.gsub(/^\s+~ \(\d+\.?\d*\)\s+/, '').split("\n")
|
33
|
-
end
|
34
|
-
|
35
|
-
def supports_default_values?
|
36
|
-
@adapter.send(:supports_default_values?)
|
37
|
-
end
|
38
|
-
|
39
|
-
def supports_returning?
|
40
|
-
@adapter.send(:supports_returning?)
|
41
|
-
end
|
42
|
-
|
43
|
-
describe '#create' do
|
44
|
-
describe 'serial properties' do
|
45
|
-
before :all do
|
46
|
-
class ::Article
|
47
|
-
include DataMapper::Resource
|
48
|
-
|
49
|
-
property :id, Serial
|
50
|
-
|
51
|
-
auto_migrate!
|
52
|
-
end
|
53
|
-
|
54
|
-
reset_log
|
55
|
-
|
56
|
-
Article.create
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'should not send NULL values' do
|
60
|
-
statement = if @mysql
|
61
|
-
/\AINSERT INTO `articles` \(\) VALUES \(\)\z/
|
62
|
-
elsif @oracle
|
63
|
-
/\AINSERT INTO "ARTICLES" \("ID"\) VALUES \(DEFAULT\) RETURNING "ID"/
|
64
|
-
elsif supports_default_values? && supports_returning?
|
65
|
-
/\AINSERT INTO "articles" DEFAULT VALUES RETURNING \"id\"\z/
|
66
|
-
elsif supports_default_values?
|
67
|
-
/\AINSERT INTO "articles" DEFAULT VALUES\z/
|
68
|
-
else
|
69
|
-
/\AINSERT INTO "articles" \(\) VALUES \(\)\z/
|
70
|
-
end
|
71
|
-
|
72
|
-
log_output.first.should =~ statement
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
describe 'properties without a default' do
|
77
|
-
before :all do
|
78
|
-
class ::Article
|
79
|
-
include DataMapper::Resource
|
80
|
-
|
81
|
-
property :id, Serial
|
82
|
-
property :title, String
|
83
|
-
|
84
|
-
auto_migrate!
|
85
|
-
end
|
86
|
-
|
87
|
-
reset_log
|
88
|
-
|
89
|
-
Article.create(:id => 1)
|
90
|
-
end
|
91
|
-
|
92
|
-
it 'should not send NULL values' do
|
93
|
-
regexp = if @mysql
|
94
|
-
/^INSERT INTO `articles` \(`id`\) VALUES \(.{1,2}\)$/i
|
95
|
-
elsif @sql_server
|
96
|
-
/^SET IDENTITY_INSERT \"articles\" ON INSERT INTO "articles" \("id"\) VALUES \(.{1,2}\) SET IDENTITY_INSERT \"articles\" OFF $/i
|
97
|
-
else
|
98
|
-
/^INSERT INTO "articles" \("id"\) VALUES \(.{1,2}\)$/i
|
99
|
-
end
|
100
|
-
|
101
|
-
log_output.first.should =~ regexp
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
describe '#select' do
|
107
|
-
before :all do
|
108
|
-
class ::Article
|
109
|
-
include DataMapper::Resource
|
110
|
-
|
111
|
-
property :name, String, :key => true
|
112
|
-
property :author, String, :required => true
|
113
|
-
|
114
|
-
auto_migrate!
|
115
|
-
end
|
116
|
-
|
117
|
-
@article_model = Article
|
118
|
-
|
119
|
-
@article_model.create(:name => 'Learning DataMapper', :author => 'Dan Kubb')
|
120
|
-
end
|
121
|
-
|
122
|
-
describe 'when one field specified in SELECT statement' do
|
123
|
-
before :all do
|
124
|
-
@return = @adapter.select('SELECT name FROM articles')
|
125
|
-
end
|
126
|
-
|
127
|
-
it 'should return an Array' do
|
128
|
-
@return.should be_kind_of(Array)
|
129
|
-
end
|
130
|
-
|
131
|
-
it 'should have a single result' do
|
132
|
-
@return.size.should == 1
|
133
|
-
end
|
134
|
-
|
135
|
-
it 'should return an Array of values' do
|
136
|
-
@return.should == [ 'Learning DataMapper' ]
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
describe 'when more than one field specified in SELECT statement' do
|
141
|
-
before :all do
|
142
|
-
@return = @adapter.select('SELECT name, author FROM articles')
|
143
|
-
end
|
144
|
-
|
145
|
-
it 'should return an Array' do
|
146
|
-
@return.should be_kind_of(Array)
|
147
|
-
end
|
148
|
-
|
149
|
-
it 'should have a single result' do
|
150
|
-
@return.size.should == 1
|
151
|
-
end
|
152
|
-
|
153
|
-
it 'should return an Array of Struct objects' do
|
154
|
-
@return.first.should be_kind_of(Struct)
|
155
|
-
end
|
156
|
-
|
157
|
-
it 'should return expected values' do
|
158
|
-
@return.first.values.should == [ 'Learning DataMapper', 'Dan Kubb' ]
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
describe '#execute' do
|
164
|
-
before :all do
|
165
|
-
class ::Article
|
166
|
-
include DataMapper::Resource
|
167
|
-
|
168
|
-
property :name, String, :key => true
|
169
|
-
property :author, String, :required => true
|
170
|
-
|
171
|
-
auto_migrate!
|
172
|
-
end
|
173
|
-
|
174
|
-
@article_model = Article
|
175
|
-
end
|
176
|
-
|
177
|
-
before :all do
|
178
|
-
@result = @adapter.execute('INSERT INTO articles (name, author) VALUES(?, ?)', 'Learning DataMapper', 'Dan Kubb')
|
179
|
-
end
|
180
|
-
|
181
|
-
it 'should return a DataObjects::Result' do
|
182
|
-
@result.should be_kind_of(DataObjects::Result)
|
183
|
-
end
|
184
|
-
|
185
|
-
it 'should affect 1 row' do
|
186
|
-
@result.affected_rows.should == 1
|
187
|
-
end
|
188
|
-
|
189
|
-
it 'should not have an insert_id' do
|
190
|
-
pending_if 'Inconsistent insert_id results', !(@postgres || @oracle || (@jruby && @mysql)) do
|
191
|
-
@result.insert_id.should be_nil
|
192
|
-
end
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
describe '#read' do
|
197
|
-
before :all do
|
198
|
-
class ::Article
|
199
|
-
include DataMapper::Resource
|
200
|
-
|
201
|
-
property :name, String, :key => true
|
202
|
-
|
203
|
-
belongs_to :parent, self, :required => false
|
204
|
-
has n, :children, self, :inverse => :parent
|
205
|
-
|
206
|
-
auto_migrate!
|
207
|
-
end
|
208
|
-
|
209
|
-
@article_model = Article
|
210
|
-
end
|
211
|
-
|
212
|
-
describe 'with a raw query' do
|
213
|
-
before :all do
|
214
|
-
@article_model.create(:name => 'Test').should be_saved
|
215
|
-
|
216
|
-
@query = DataMapper::Query.new(@repository, @article_model, :conditions => [ 'name IS NOT NULL' ])
|
217
|
-
|
218
|
-
@return = @adapter.read(@query)
|
219
|
-
end
|
220
|
-
|
221
|
-
it 'should return an Array of Hashes' do
|
222
|
-
@return.should be_kind_of(Array)
|
223
|
-
@return.all? { |entry| entry.should be_kind_of(Hash) }
|
224
|
-
end
|
225
|
-
|
226
|
-
it 'should return expected values' do
|
227
|
-
@return.should == [ { @article_model.properties[:name] => 'Test', @article_model.properties[:parent_name] => nil } ]
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
describe 'with a raw query with a bind value mismatch' do
|
232
|
-
before :all do
|
233
|
-
@article_model.create(:name => 'Test').should be_saved
|
234
|
-
|
235
|
-
@query = DataMapper::Query.new(@repository, @article_model, :conditions => [ 'name IS NOT NULL', nil ])
|
236
|
-
end
|
237
|
-
|
238
|
-
it 'should raise an error' do
|
239
|
-
lambda {
|
240
|
-
@adapter.read(@query)
|
241
|
-
}.should raise_error(ArgumentError, 'Binding mismatch: 1 for 0')
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
describe 'with a Collection bind value' do
|
246
|
-
describe 'with an inclusion comparison' do
|
247
|
-
before :all do
|
248
|
-
5.times do |index|
|
249
|
-
@article_model.create(:name => "Test #{index}", :parent => @article_model.last).should be_saved
|
250
|
-
end
|
251
|
-
|
252
|
-
@parents = @article_model.all
|
253
|
-
@query = DataMapper::Query.new(@repository, @article_model, :parent => @parents)
|
254
|
-
|
255
|
-
@expected = @article_model.all[1, 4].map { |article| article.attributes(:property) }
|
256
|
-
end
|
257
|
-
|
258
|
-
describe 'that is not loaded' do
|
259
|
-
before :all do
|
260
|
-
reset_log
|
261
|
-
@return = @adapter.read(@query)
|
262
|
-
end
|
263
|
-
|
264
|
-
it 'should return an Array of Hashes' do
|
265
|
-
@return.should be_kind_of(Array)
|
266
|
-
@return.all? { |entry| entry.should be_kind_of(Hash) }
|
267
|
-
end
|
268
|
-
|
269
|
-
it 'should return expected values' do
|
270
|
-
@return.should == @expected
|
271
|
-
end
|
272
|
-
|
273
|
-
it 'should execute one subquery' do
|
274
|
-
pending_if @mysql do
|
275
|
-
log_output.size.should == 1
|
276
|
-
end
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
describe 'that is loaded' do
|
281
|
-
before :all do
|
282
|
-
@parents.to_a # lazy load the collection
|
283
|
-
end
|
284
|
-
|
285
|
-
before :all do
|
286
|
-
reset_log
|
287
|
-
@return = @adapter.read(@query)
|
288
|
-
end
|
289
|
-
|
290
|
-
it 'should return an Array of Hashes' do
|
291
|
-
@return.should be_kind_of(Array)
|
292
|
-
@return.all? { |entry| entry.should be_kind_of(Hash) }
|
293
|
-
end
|
294
|
-
|
295
|
-
it 'should return expected values' do
|
296
|
-
@return.should == @expected
|
297
|
-
end
|
298
|
-
|
299
|
-
it 'should execute one query' do
|
300
|
-
log_output.size.should == 1
|
301
|
-
end
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
describe 'with an negated inclusion comparison' do
|
306
|
-
before :all do
|
307
|
-
5.times do |index|
|
308
|
-
@article_model.create(:name => "Test #{index}", :parent => @article_model.last).should be_saved
|
309
|
-
end
|
310
|
-
|
311
|
-
@parents = @article_model.all
|
312
|
-
@query = DataMapper::Query.new(@repository, @article_model, :parent.not => @parents)
|
313
|
-
|
314
|
-
@expected = []
|
315
|
-
end
|
316
|
-
|
317
|
-
describe 'that is not loaded' do
|
318
|
-
before :all do
|
319
|
-
reset_log
|
320
|
-
@return = @adapter.read(@query)
|
321
|
-
end
|
322
|
-
|
323
|
-
it 'should return an Array of Hashes' do
|
324
|
-
@return.should be_kind_of(Array)
|
325
|
-
@return.all? { |entry| entry.should be_kind_of(Hash) }
|
326
|
-
end
|
327
|
-
|
328
|
-
it 'should return expected values' do
|
329
|
-
@return.should == @expected
|
330
|
-
end
|
331
|
-
|
332
|
-
it 'should execute one subquery' do
|
333
|
-
pending_if @mysql do
|
334
|
-
log_output.size.should == 1
|
335
|
-
end
|
336
|
-
end
|
337
|
-
end
|
338
|
-
|
339
|
-
describe 'that is loaded' do
|
340
|
-
before :all do
|
341
|
-
@parents.to_a # lazy load the collection
|
342
|
-
end
|
343
|
-
|
344
|
-
before :all do
|
345
|
-
reset_log
|
346
|
-
@return = @adapter.read(@query)
|
347
|
-
end
|
348
|
-
|
349
|
-
it 'should return an Array of Hashes' do
|
350
|
-
@return.should be_kind_of(Array)
|
351
|
-
@return.all? { |entry| entry.should be_kind_of(Hash) }
|
352
|
-
end
|
353
|
-
|
354
|
-
it 'should return expected values' do
|
355
|
-
@return.should == @expected
|
356
|
-
end
|
357
|
-
|
358
|
-
it 'should execute one query' do
|
359
|
-
log_output.size.should == 1
|
360
|
-
end
|
361
|
-
end
|
362
|
-
end
|
363
|
-
|
364
|
-
end
|
365
|
-
end
|
366
|
-
end
|
data/lib/dm-core/transaction.rb
DELETED
@@ -1,508 +0,0 @@
|
|
1
|
-
# TODO: move to dm-more/dm-transaction
|
2
|
-
|
3
|
-
module DataMapper
|
4
|
-
class Transaction
|
5
|
-
extend Chainable
|
6
|
-
|
7
|
-
# @api private
|
8
|
-
attr_accessor :state
|
9
|
-
|
10
|
-
# @api private
|
11
|
-
def none?
|
12
|
-
state == :none
|
13
|
-
end
|
14
|
-
|
15
|
-
# @api private
|
16
|
-
def begin?
|
17
|
-
state == :begin
|
18
|
-
end
|
19
|
-
|
20
|
-
# @api private
|
21
|
-
def rollback?
|
22
|
-
state == :rollback
|
23
|
-
end
|
24
|
-
|
25
|
-
# @api private
|
26
|
-
def commit?
|
27
|
-
state == :commit
|
28
|
-
end
|
29
|
-
|
30
|
-
# Create a new Transaction
|
31
|
-
#
|
32
|
-
# @see Transaction#link
|
33
|
-
#
|
34
|
-
# In fact, it just calls #link with the given arguments at the end of the
|
35
|
-
# constructor.
|
36
|
-
#
|
37
|
-
# @api public
|
38
|
-
def initialize(*things)
|
39
|
-
@transaction_primitives = {}
|
40
|
-
self.state = :none
|
41
|
-
@adapters = {}
|
42
|
-
link(*things)
|
43
|
-
if block_given?
|
44
|
-
warn "Passing block to #{self.class.name}.new is deprecated (#{caller[0]})"
|
45
|
-
commit { |*block_args| yield(*block_args) }
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
# Associate this Transaction with some things.
|
50
|
-
#
|
51
|
-
# @param [Object] things
|
52
|
-
# the things you want this Transaction associated with:
|
53
|
-
#
|
54
|
-
# Adapters::AbstractAdapter subclasses will be added as
|
55
|
-
# adapters as is.
|
56
|
-
# Arrays will have their elements added.
|
57
|
-
# Repository will have it's own @adapters added.
|
58
|
-
# Resource subclasses will have all the repositories of all
|
59
|
-
# their properties added.
|
60
|
-
# Resource instances will have all repositories of all their
|
61
|
-
# properties added.
|
62
|
-
#
|
63
|
-
# @param [Proc] block
|
64
|
-
# a block (taking one argument, the Transaction) to execute within
|
65
|
-
# this transaction. The transaction will begin and commit around
|
66
|
-
# the block, and rollback if an exception is raised.
|
67
|
-
#
|
68
|
-
# @api private
|
69
|
-
def link(*things)
|
70
|
-
unless none?
|
71
|
-
raise "Illegal state for link: #{state}"
|
72
|
-
end
|
73
|
-
|
74
|
-
things.each do |thing|
|
75
|
-
case thing
|
76
|
-
when DataMapper::Adapters::AbstractAdapter
|
77
|
-
@adapters[thing] = :none
|
78
|
-
when DataMapper::Repository
|
79
|
-
link(thing.adapter)
|
80
|
-
when DataMapper::Model
|
81
|
-
link(*thing.repositories)
|
82
|
-
when DataMapper::Resource
|
83
|
-
link(thing.model)
|
84
|
-
when Array
|
85
|
-
link(*thing)
|
86
|
-
else
|
87
|
-
raise "Unknown argument to #{self.class}#link: #{thing.inspect} (#{thing.class})"
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
if block_given?
|
92
|
-
commit { |*block_args| yield(*block_args) }
|
93
|
-
else
|
94
|
-
self
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# Begin the transaction
|
99
|
-
#
|
100
|
-
# Before #begin is called, the transaction is not valid and can not be used.
|
101
|
-
#
|
102
|
-
# @api private
|
103
|
-
def begin
|
104
|
-
unless none?
|
105
|
-
raise "Illegal state for begin: #{state}"
|
106
|
-
end
|
107
|
-
|
108
|
-
each_adapter(:connect_adapter, [:log_fatal_transaction_breakage])
|
109
|
-
each_adapter(:begin_adapter, [:rollback_and_close_adapter_if_begin, :close_adapter_if_none])
|
110
|
-
self.state = :begin
|
111
|
-
end
|
112
|
-
|
113
|
-
# Commit the transaction
|
114
|
-
#
|
115
|
-
# If no block is given, it will simply commit any changes made since the
|
116
|
-
# Transaction did #begin.
|
117
|
-
#
|
118
|
-
# @param block<Block> a block (taking the one argument, the Transaction) to
|
119
|
-
# execute within this transaction. The transaction will begin and commit
|
120
|
-
# around the block, and roll back if an exception is raised.
|
121
|
-
#
|
122
|
-
# @api private
|
123
|
-
def commit
|
124
|
-
if block_given?
|
125
|
-
unless none?
|
126
|
-
raise "Illegal state for commit with block: #{state}"
|
127
|
-
end
|
128
|
-
|
129
|
-
begin
|
130
|
-
self.begin
|
131
|
-
rval = within { |*block_args| yield(*block_args) }
|
132
|
-
rescue Exception => exception
|
133
|
-
if begin?
|
134
|
-
rollback
|
135
|
-
end
|
136
|
-
raise exception
|
137
|
-
ensure
|
138
|
-
unless exception
|
139
|
-
if begin?
|
140
|
-
commit
|
141
|
-
end
|
142
|
-
return rval
|
143
|
-
end
|
144
|
-
end
|
145
|
-
else
|
146
|
-
unless begin?
|
147
|
-
raise "Illegal state for commit without block: #{state}"
|
148
|
-
end
|
149
|
-
each_adapter(:commit_adapter, [:log_fatal_transaction_breakage])
|
150
|
-
each_adapter(:close_adapter, [:log_fatal_transaction_breakage])
|
151
|
-
self.state = :commit
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
# Rollback the transaction
|
156
|
-
#
|
157
|
-
# Will undo all changes made during the transaction.
|
158
|
-
#
|
159
|
-
# @api private
|
160
|
-
def rollback
|
161
|
-
unless begin?
|
162
|
-
raise "Illegal state for rollback: #{state}"
|
163
|
-
end
|
164
|
-
each_adapter(:rollback_adapter_if_begin, [:rollback_and_close_adapter_if_begin, :close_adapter_if_none])
|
165
|
-
each_adapter(:close_adapter_if_open, [:log_fatal_transaction_breakage])
|
166
|
-
self.state = :rollback
|
167
|
-
end
|
168
|
-
|
169
|
-
# Execute a block within this Transaction.
|
170
|
-
#
|
171
|
-
# No #begin, #commit or #rollback is performed in #within, but this
|
172
|
-
# Transaction will pushed on the per thread stack of transactions for each
|
173
|
-
# adapter it is associated with, and it will ensures that it will pop the
|
174
|
-
# Transaction away again after the block is finished.
|
175
|
-
#
|
176
|
-
# @param block<Block> the block of code to execute.
|
177
|
-
#
|
178
|
-
# @api private
|
179
|
-
def within
|
180
|
-
unless block_given?
|
181
|
-
raise 'No block provided'
|
182
|
-
end
|
183
|
-
|
184
|
-
unless begin?
|
185
|
-
raise "Illegal state for within: #{state}"
|
186
|
-
end
|
187
|
-
|
188
|
-
adapters = @adapters
|
189
|
-
|
190
|
-
adapters.each_key do |adapter|
|
191
|
-
adapter.push_transaction(self)
|
192
|
-
end
|
193
|
-
|
194
|
-
begin
|
195
|
-
yield self
|
196
|
-
ensure
|
197
|
-
adapters.each_key do |adapter|
|
198
|
-
adapter.pop_transaction
|
199
|
-
end
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
# @api private
|
204
|
-
def method_missing(method, *args, &block)
|
205
|
-
first_arg = args.first
|
206
|
-
|
207
|
-
return super unless args.size == 1 && first_arg.kind_of?(Adapters::AbstractAdapter)
|
208
|
-
return super unless match = method.to_s.match(/\A(.*)_(if|unless)_(none|begin|rollback|commit)\z/)
|
209
|
-
|
210
|
-
action, condition, expected_state = match.captures
|
211
|
-
return super unless respond_to?(action, true)
|
212
|
-
|
213
|
-
state = state_for(first_arg).to_s
|
214
|
-
execute = (condition == 'if') == (state == expected_state)
|
215
|
-
|
216
|
-
send(action, first_arg) if execute
|
217
|
-
end
|
218
|
-
|
219
|
-
# @api private
|
220
|
-
def primitive_for(adapter)
|
221
|
-
unless @adapters.include?(adapter)
|
222
|
-
raise "Unknown adapter #{adapter}"
|
223
|
-
end
|
224
|
-
|
225
|
-
unless @transaction_primitives.include?(adapter)
|
226
|
-
raise "No primitive for #{adapter}"
|
227
|
-
end
|
228
|
-
|
229
|
-
@transaction_primitives[adapter]
|
230
|
-
end
|
231
|
-
|
232
|
-
private
|
233
|
-
|
234
|
-
# @api private
|
235
|
-
def validate_primitive(primitive)
|
236
|
-
[:close, :begin, :rollback, :commit].each do |meth|
|
237
|
-
unless primitive.respond_to?(meth)
|
238
|
-
raise "Invalid primitive #{primitive}: doesnt respond_to?(#{meth.inspect})"
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
primitive
|
243
|
-
end
|
244
|
-
|
245
|
-
# @api private
|
246
|
-
def each_adapter(method, on_fail)
|
247
|
-
adapters = @adapters
|
248
|
-
begin
|
249
|
-
adapters.each_key do |adapter|
|
250
|
-
send(method, adapter)
|
251
|
-
end
|
252
|
-
rescue Exception => exception
|
253
|
-
adapters.each_key do |adapter|
|
254
|
-
on_fail.each do |fail_handler|
|
255
|
-
begin
|
256
|
-
send(fail_handler, adapter)
|
257
|
-
rescue Exception => inner_exception
|
258
|
-
DataMapper.logger.fatal("#{self}#each_adapter(#{method.inspect}, #{on_fail.inspect}) failed with #{exception.inspect}: #{exception.backtrace.join("\n")} - and when sending #{fail_handler} to #{adapter} we failed again with #{inner_exception.inspect}: #{inner_exception.backtrace.join("\n")}")
|
259
|
-
end
|
260
|
-
end
|
261
|
-
end
|
262
|
-
raise exception
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
# @api private
|
267
|
-
def state_for(adapter)
|
268
|
-
unless @adapters.include?(adapter)
|
269
|
-
raise "Unknown adapter #{adapter}"
|
270
|
-
end
|
271
|
-
|
272
|
-
@adapters[adapter]
|
273
|
-
end
|
274
|
-
|
275
|
-
# @api private
|
276
|
-
def do_adapter(adapter, what, prerequisite)
|
277
|
-
unless @transaction_primitives.include?(adapter)
|
278
|
-
raise "No primitive for #{adapter}"
|
279
|
-
end
|
280
|
-
|
281
|
-
state = state_for(adapter)
|
282
|
-
|
283
|
-
unless state == prerequisite
|
284
|
-
raise "Illegal state for #{what}: #{state}"
|
285
|
-
end
|
286
|
-
|
287
|
-
DataMapper.logger.debug("#{adapter.name}: #{what}")
|
288
|
-
@transaction_primitives[adapter].send(what)
|
289
|
-
@adapters[adapter] = what
|
290
|
-
end
|
291
|
-
|
292
|
-
# @api private
|
293
|
-
def log_fatal_transaction_breakage(adapter)
|
294
|
-
DataMapper.logger.fatal("#{self} experienced a totally broken transaction execution. Presenting member #{adapter.inspect}.")
|
295
|
-
end
|
296
|
-
|
297
|
-
# @api private
|
298
|
-
def connect_adapter(adapter)
|
299
|
-
if @transaction_primitives.key?(adapter)
|
300
|
-
raise "Already a primitive for adapter #{adapter}"
|
301
|
-
end
|
302
|
-
|
303
|
-
@transaction_primitives[adapter] = validate_primitive(adapter.transaction_primitive)
|
304
|
-
end
|
305
|
-
|
306
|
-
# @api private
|
307
|
-
def close_adapter_if_open(adapter)
|
308
|
-
if @transaction_primitives.include?(adapter)
|
309
|
-
close_adapter(adapter)
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
# @api private
|
314
|
-
def close_adapter(adapter)
|
315
|
-
unless @transaction_primitives.include?(adapter)
|
316
|
-
raise 'No primitive for adapter'
|
317
|
-
end
|
318
|
-
|
319
|
-
@transaction_primitives[adapter].close
|
320
|
-
@transaction_primitives.delete(adapter)
|
321
|
-
end
|
322
|
-
|
323
|
-
# @api private
|
324
|
-
def begin_adapter(adapter)
|
325
|
-
do_adapter(adapter, :begin, :none)
|
326
|
-
end
|
327
|
-
|
328
|
-
# @api private
|
329
|
-
def commit_adapter(adapter)
|
330
|
-
do_adapter(adapter, :commit, :begin)
|
331
|
-
end
|
332
|
-
|
333
|
-
# @api private
|
334
|
-
def rollback_adapter(adapter)
|
335
|
-
do_adapter(adapter, :rollback, :begin)
|
336
|
-
end
|
337
|
-
|
338
|
-
# @api private
|
339
|
-
def rollback_and_close_adapter(adapter)
|
340
|
-
rollback_adapter(adapter)
|
341
|
-
close_adapter(adapter)
|
342
|
-
end
|
343
|
-
|
344
|
-
module Adapter
|
345
|
-
extend Chainable
|
346
|
-
|
347
|
-
# @api private
|
348
|
-
def self.included(base)
|
349
|
-
[ :Repository, :Model, :Resource ].each do |name|
|
350
|
-
DataMapper.const_get(name).send(:include, Transaction.const_get(name))
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
|
-
# Produces a fresh transaction primitive for this Adapter
|
355
|
-
#
|
356
|
-
# Used by Transaction to perform its various tasks.
|
357
|
-
#
|
358
|
-
# @return [Object]
|
359
|
-
# a new Object that responds to :close, :begin, :commit,
|
360
|
-
# and :rollback,
|
361
|
-
#
|
362
|
-
# @api private
|
363
|
-
def transaction_primitive
|
364
|
-
DataObjects::Transaction.create_for_uri(normalized_uri)
|
365
|
-
end
|
366
|
-
|
367
|
-
# Pushes the given Transaction onto the per thread Transaction stack so
|
368
|
-
# that everything done by this Adapter is done within the context of said
|
369
|
-
# Transaction.
|
370
|
-
#
|
371
|
-
# @param [Transaction] transaction
|
372
|
-
# a Transaction to be the 'current' transaction until popped.
|
373
|
-
#
|
374
|
-
# @return [Array(Transaction)]
|
375
|
-
# the stack of active transactions for the current thread
|
376
|
-
#
|
377
|
-
# @api private
|
378
|
-
def push_transaction(transaction)
|
379
|
-
transactions << transaction
|
380
|
-
end
|
381
|
-
|
382
|
-
# Pop the 'current' Transaction from the per thread Transaction stack so
|
383
|
-
# that everything done by this Adapter is no longer necessarily within the
|
384
|
-
# context of said Transaction.
|
385
|
-
#
|
386
|
-
# @return [Transaction]
|
387
|
-
# the former 'current' transaction.
|
388
|
-
#
|
389
|
-
# @api private
|
390
|
-
def pop_transaction
|
391
|
-
transactions.pop
|
392
|
-
end
|
393
|
-
|
394
|
-
# Retrieve the current transaction for this Adapter.
|
395
|
-
#
|
396
|
-
# Everything done by this Adapter is done within the context of this
|
397
|
-
# Transaction.
|
398
|
-
#
|
399
|
-
# @return [Transaction]
|
400
|
-
# the 'current' transaction for this Adapter.
|
401
|
-
#
|
402
|
-
# @api private
|
403
|
-
def current_transaction
|
404
|
-
transactions.last
|
405
|
-
end
|
406
|
-
|
407
|
-
chainable do
|
408
|
-
protected
|
409
|
-
|
410
|
-
# @api semipublic
|
411
|
-
def open_connection
|
412
|
-
current_connection || super
|
413
|
-
end
|
414
|
-
|
415
|
-
# @api semipublic
|
416
|
-
def close_connection(connection)
|
417
|
-
unless current_connection == connection
|
418
|
-
super
|
419
|
-
end
|
420
|
-
end
|
421
|
-
end
|
422
|
-
|
423
|
-
private
|
424
|
-
|
425
|
-
# @api private
|
426
|
-
def transactions
|
427
|
-
Thread.current[:dm_transactions] ||= []
|
428
|
-
end
|
429
|
-
|
430
|
-
# Retrieve the current connection for this Adapter.
|
431
|
-
#
|
432
|
-
# @return [Transaction]
|
433
|
-
# the 'current' connection for this Adapter.
|
434
|
-
#
|
435
|
-
# @api private
|
436
|
-
def current_connection
|
437
|
-
if transaction = current_transaction
|
438
|
-
transaction.primitive_for(self).connection
|
439
|
-
end
|
440
|
-
end
|
441
|
-
end # module Adapter
|
442
|
-
|
443
|
-
# alias the MySQL, PostgreSQL, Sqlite3 and Oracle adapters to use transactions
|
444
|
-
MysqlAdapter = PostgresAdapter = Sqlite3Adapter = OracleAdapter = SqlserverAdapter = Adapter
|
445
|
-
|
446
|
-
module Repository
|
447
|
-
|
448
|
-
# Produce a new Transaction for this Repository
|
449
|
-
#
|
450
|
-
# @return [Adapters::Transaction]
|
451
|
-
# a new Transaction (in state :none) that can be used
|
452
|
-
# to execute code #with_transaction
|
453
|
-
#
|
454
|
-
# @api public
|
455
|
-
def transaction
|
456
|
-
Transaction.new(self)
|
457
|
-
end
|
458
|
-
end # module Repository
|
459
|
-
|
460
|
-
module Model
|
461
|
-
# @api private
|
462
|
-
def self.included(mod)
|
463
|
-
mod.descendants.each { |model| model.extend self }
|
464
|
-
end
|
465
|
-
|
466
|
-
# Produce a new Transaction for this Resource class
|
467
|
-
#
|
468
|
-
# @return <Adapters::Transaction
|
469
|
-
# a new Adapters::Transaction with all Repositories
|
470
|
-
# of the class of this Resource added.
|
471
|
-
#
|
472
|
-
# @api public
|
473
|
-
def transaction
|
474
|
-
transaction = Transaction.new(self)
|
475
|
-
transaction.commit { |block_args| yield(*block_args) }
|
476
|
-
end
|
477
|
-
end # module Model
|
478
|
-
|
479
|
-
module Resource
|
480
|
-
|
481
|
-
# Produce a new Transaction for the class of this Resource
|
482
|
-
#
|
483
|
-
# @return [Adapters::Transaction]
|
484
|
-
# a new Adapters::Transaction for the Repository
|
485
|
-
# of the class of this Resource added.
|
486
|
-
#
|
487
|
-
# @api public
|
488
|
-
def transaction
|
489
|
-
model.transaction { |*block_args| yield(*block_args) }
|
490
|
-
end
|
491
|
-
end # module Resource
|
492
|
-
end # class Transaction
|
493
|
-
|
494
|
-
module Adapters
|
495
|
-
extendable do
|
496
|
-
|
497
|
-
# @api private
|
498
|
-
def const_added(const_name)
|
499
|
-
if Transaction.const_defined?(const_name)
|
500
|
-
adapter = const_get(const_name)
|
501
|
-
adapter.send(:include, Transaction.const_get(const_name))
|
502
|
-
end
|
503
|
-
|
504
|
-
super
|
505
|
-
end
|
506
|
-
end
|
507
|
-
end # module Adapters
|
508
|
-
end # module DataMapper
|