activerecord 2.2.3 → 2.3.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +438 -396
- data/Rakefile +4 -2
- data/lib/active_record.rb +46 -43
- data/lib/active_record/association_preload.rb +34 -19
- data/lib/active_record/associations.rb +193 -251
- data/lib/active_record/associations/association_collection.rb +38 -21
- data/lib/active_record/associations/association_proxy.rb +11 -4
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +2 -2
- data/lib/active_record/associations/has_many_association.rb +2 -2
- data/lib/active_record/associations/has_many_through_association.rb +8 -8
- data/lib/active_record/associations/has_one_association.rb +11 -2
- data/lib/active_record/attribute_methods.rb +1 -0
- data/lib/active_record/autosave_association.rb +349 -0
- data/lib/active_record/base.rb +292 -106
- data/lib/active_record/batches.rb +73 -0
- data/lib/active_record/calculations.rb +34 -16
- data/lib/active_record/callbacks.rb +37 -8
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +16 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +103 -15
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -6
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +28 -25
- data/lib/active_record/connection_adapters/abstract_adapter.rb +29 -5
- data/lib/active_record/connection_adapters/mysql_adapter.rb +50 -21
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -41
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +41 -21
- data/lib/active_record/dirty.rb +1 -1
- data/lib/active_record/dynamic_scope_match.rb +25 -0
- data/lib/active_record/fixtures.rb +193 -198
- data/lib/active_record/locale/en.yml +1 -1
- data/lib/active_record/locking/optimistic.rb +33 -0
- data/lib/active_record/migration.rb +8 -2
- data/lib/active_record/named_scope.rb +13 -6
- data/lib/active_record/nested_attributes.rb +329 -0
- data/lib/active_record/query_cache.rb +25 -13
- data/lib/active_record/reflection.rb +6 -1
- data/lib/active_record/schema_dumper.rb +2 -0
- data/lib/active_record/serialization.rb +3 -1
- data/lib/active_record/serializers/json_serializer.rb +19 -0
- data/lib/active_record/serializers/xml_serializer.rb +28 -13
- data/lib/active_record/session_store.rb +318 -0
- data/lib/active_record/test_case.rb +15 -9
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/transactions.rb +58 -8
- data/lib/active_record/validations.rb +29 -24
- data/lib/active_record/version.rb +2 -2
- data/test/cases/ar_schema_test.rb +0 -1
- data/test/cases/associations/belongs_to_associations_test.rb +35 -131
- data/test/cases/associations/cascaded_eager_loading_test.rb +8 -0
- data/test/cases/associations/eager_load_nested_include_test.rb +29 -0
- data/test/cases/associations/eager_test.rb +137 -7
- data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +45 -7
- data/test/cases/associations/has_many_associations_test.rb +110 -149
- data/test/cases/associations/has_many_through_associations_test.rb +39 -7
- data/test/cases/associations/has_one_associations_test.rb +39 -92
- data/test/cases/associations/has_one_through_associations_test.rb +34 -3
- data/test/cases/associations/inner_join_association_test.rb +0 -5
- data/test/cases/associations/join_model_test.rb +5 -7
- data/test/cases/attribute_methods_test.rb +13 -1
- data/test/cases/autosave_association_test.rb +901 -0
- data/test/cases/base_test.rb +41 -21
- data/test/cases/batches_test.rb +61 -0
- data/test/cases/calculations_test.rb +37 -17
- data/test/cases/callbacks_test.rb +43 -5
- data/test/cases/connection_pool_test.rb +25 -0
- data/test/cases/copy_table_test_sqlite.rb +11 -0
- data/test/cases/datatype_test_postgresql.rb +1 -0
- data/test/cases/defaults_test.rb +37 -26
- data/test/cases/dirty_test.rb +26 -2
- data/test/cases/finder_test.rb +79 -44
- data/test/cases/fixtures_test.rb +15 -19
- data/test/cases/helper.rb +26 -19
- data/test/cases/inheritance_test.rb +2 -2
- data/test/cases/json_serialization_test.rb +1 -1
- data/test/cases/locking_test.rb +23 -5
- data/test/cases/method_scoping_test.rb +126 -3
- data/test/cases/migration_test.rb +253 -237
- data/test/cases/named_scope_test.rb +73 -3
- data/test/cases/nested_attributes_test.rb +509 -0
- data/test/cases/query_cache_test.rb +0 -4
- data/test/cases/reflection_test.rb +13 -3
- data/test/cases/reload_models_test.rb +3 -1
- data/test/cases/repair_helper.rb +50 -0
- data/test/cases/schema_dumper_test.rb +0 -1
- data/test/cases/transactions_test.rb +177 -12
- data/test/cases/validations_i18n_test.rb +288 -294
- data/test/cases/validations_test.rb +230 -180
- data/test/cases/xml_serialization_test.rb +19 -1
- data/test/fixtures/fixture_database.sqlite3 +0 -0
- data/test/fixtures/fixture_database_2.sqlite3 +0 -0
- data/test/fixtures/member_types.yml +6 -0
- data/test/fixtures/members.yml +3 -1
- data/test/fixtures/people.yml +10 -1
- data/test/fixtures/toys.yml +4 -0
- data/test/models/author.rb +1 -2
- data/test/models/bird.rb +3 -0
- data/test/models/category.rb +1 -0
- data/test/models/company.rb +3 -0
- data/test/models/developer.rb +12 -0
- data/test/models/event.rb +3 -0
- data/test/models/member.rb +1 -0
- data/test/models/member_detail.rb +1 -0
- data/test/models/member_type.rb +3 -0
- data/test/models/owner.rb +2 -1
- data/test/models/parrot.rb +2 -0
- data/test/models/person.rb +6 -0
- data/test/models/pet.rb +2 -1
- data/test/models/pirate.rb +55 -1
- data/test/models/post.rb +6 -0
- data/test/models/project.rb +1 -0
- data/test/models/reply.rb +6 -0
- data/test/models/ship.rb +8 -1
- data/test/models/ship_part.rb +5 -0
- data/test/models/topic.rb +13 -1
- data/test/models/toy.rb +4 -0
- data/test/schema/schema.rb +35 -2
- metadata +70 -9
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Batches # :nodoc:
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
# When processing large numbers of records, it's often a good idea to do so in batches to prevent memory ballooning.
|
8
|
+
module ClassMethods
|
9
|
+
# Yields each record that was found by the find +options+. The find is performed by find_in_batches
|
10
|
+
# with a batch size of 1000 (or as specified by the +batch_size+ option).
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# Person.find_each(:conditions => "age > 21") do |person|
|
15
|
+
# person.party_all_night!
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# Note: This method is only intended to use for batch processing of large amounts of records that wouldn't fit in
|
19
|
+
# memory all at once. If you just need to loop over less than 1000 records, it's probably better just to use the
|
20
|
+
# regular find methods.
|
21
|
+
def find_each(options = {})
|
22
|
+
find_in_batches(options) do |records|
|
23
|
+
records.each { |record| yield record }
|
24
|
+
end
|
25
|
+
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# Yields each batch of records that was found by the find +options+ as an array. The size of each batch is
|
30
|
+
# set by the +batch_size+ option; the default is 1000.
|
31
|
+
#
|
32
|
+
# You can control the starting point for the batch processing by supplying the +start+ option. This is especially
|
33
|
+
# useful if you want multiple workers dealing with the same processing queue. You can make worker 1 handle all the
|
34
|
+
# records between id 0 and 10,000 and worker 2 handle from 10,000 and beyond (by setting the +start+ option on that
|
35
|
+
# worker).
|
36
|
+
#
|
37
|
+
# It's not possible to set the order. That is automatically set to ascending on the primary key ("id ASC")
|
38
|
+
# to make the batch ordering work. This also mean that this method only works with integer-based primary keys.
|
39
|
+
# You can't set the limit either, that's used to control the the batch sizes.
|
40
|
+
#
|
41
|
+
# Example:
|
42
|
+
#
|
43
|
+
# Person.find_in_batches(:conditions => "age > 21") do |group|
|
44
|
+
# sleep(50) # Make sure it doesn't get too crowded in there!
|
45
|
+
# group.each { |person| person.party_all_night! }
|
46
|
+
# end
|
47
|
+
def find_in_batches(options = {})
|
48
|
+
raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order]
|
49
|
+
raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit]
|
50
|
+
|
51
|
+
start = options.delete(:start).to_i
|
52
|
+
batch_size = options.delete(:batch_size) || 1000
|
53
|
+
|
54
|
+
with_scope(:find => options.merge(:order => batch_order, :limit => batch_size)) do
|
55
|
+
records = find(:all, :conditions => [ "#{table_name}.#{primary_key} >= ?", start ])
|
56
|
+
|
57
|
+
while records.any?
|
58
|
+
yield records
|
59
|
+
|
60
|
+
break if records.size < batch_size
|
61
|
+
records = find(:all, :conditions => [ "#{table_name}.#{primary_key} > ?", records.last.id ])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
private
|
68
|
+
def batch_order
|
69
|
+
"#{table_name}.#{primary_key} ASC"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -48,30 +48,38 @@ module ActiveRecord
|
|
48
48
|
calculate(:count, *construct_count_options_from_args(*args))
|
49
49
|
end
|
50
50
|
|
51
|
-
# Calculates the average value on a given column.
|
51
|
+
# Calculates the average value on a given column. The value is returned as
|
52
|
+
# a float, or +nil+ if there's no row. See +calculate+ for examples with
|
53
|
+
# options.
|
52
54
|
#
|
53
|
-
# Person.average('age')
|
55
|
+
# Person.average('age') # => 35.8
|
54
56
|
def average(column_name, options = {})
|
55
57
|
calculate(:avg, column_name, options)
|
56
58
|
end
|
57
59
|
|
58
|
-
# Calculates the minimum value on a given column. The value is returned
|
60
|
+
# Calculates the minimum value on a given column. The value is returned
|
61
|
+
# with the same data type of the column, or +nil+ if there's no row. See
|
62
|
+
# +calculate+ for examples with options.
|
59
63
|
#
|
60
|
-
# Person.minimum('age')
|
64
|
+
# Person.minimum('age') # => 7
|
61
65
|
def minimum(column_name, options = {})
|
62
66
|
calculate(:min, column_name, options)
|
63
67
|
end
|
64
68
|
|
65
|
-
# Calculates the maximum value on a given column.
|
69
|
+
# Calculates the maximum value on a given column. The value is returned
|
70
|
+
# with the same data type of the column, or +nil+ if there's no row. See
|
71
|
+
# +calculate+ for examples with options.
|
66
72
|
#
|
67
|
-
# Person.maximum('age')
|
73
|
+
# Person.maximum('age') # => 93
|
68
74
|
def maximum(column_name, options = {})
|
69
75
|
calculate(:max, column_name, options)
|
70
76
|
end
|
71
77
|
|
72
|
-
# Calculates the sum of values on a given column.
|
78
|
+
# Calculates the sum of values on a given column. The value is returned
|
79
|
+
# with the same data type of the column, 0 if there's no row. See
|
80
|
+
# +calculate+ for examples with options.
|
73
81
|
#
|
74
|
-
# Person.sum('age')
|
82
|
+
# Person.sum('age') # => 4562
|
75
83
|
def sum(column_name, options = {})
|
76
84
|
calculate(:sum, column_name, options)
|
77
85
|
end
|
@@ -133,22 +141,30 @@ module ActiveRecord
|
|
133
141
|
def construct_count_options_from_args(*args)
|
134
142
|
options = {}
|
135
143
|
column_name = :all
|
136
|
-
|
144
|
+
|
137
145
|
# We need to handle
|
138
146
|
# count()
|
139
147
|
# count(:column_name=:all)
|
140
148
|
# count(options={})
|
141
149
|
# count(column_name=:all, options={})
|
150
|
+
# selects specified by scopes
|
142
151
|
case args.size
|
152
|
+
when 0
|
153
|
+
column_name = scope(:find)[:select] if scope(:find)
|
143
154
|
when 1
|
144
|
-
args[0].is_a?(Hash)
|
155
|
+
if args[0].is_a?(Hash)
|
156
|
+
column_name = scope(:find)[:select] if scope(:find)
|
157
|
+
options = args[0]
|
158
|
+
else
|
159
|
+
column_name = args[0]
|
160
|
+
end
|
145
161
|
when 2
|
146
162
|
column_name, options = args
|
147
163
|
else
|
148
164
|
raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}"
|
149
|
-
end
|
150
|
-
|
151
|
-
[column_name, options]
|
165
|
+
end
|
166
|
+
|
167
|
+
[column_name || :all, options]
|
152
168
|
end
|
153
169
|
|
154
170
|
def construct_calculation_sql(operation, column_name, options) #:nodoc:
|
@@ -206,13 +222,15 @@ module ActiveRecord
|
|
206
222
|
end
|
207
223
|
|
208
224
|
if options[:group] && options[:having]
|
225
|
+
having = sanitize_sql_for_conditions(options[:having])
|
226
|
+
|
209
227
|
# FrontBase requires identifiers in the HAVING clause and chokes on function calls
|
210
228
|
if connection.adapter_name == 'FrontBase'
|
211
|
-
|
212
|
-
|
229
|
+
having.downcase!
|
230
|
+
having.gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias)
|
213
231
|
end
|
214
232
|
|
215
|
-
sql << " HAVING #{
|
233
|
+
sql << " HAVING #{having} "
|
216
234
|
end
|
217
235
|
|
218
236
|
sql << " ORDER BY #{options[:order]} " if options[:order]
|
@@ -77,7 +77,7 @@ module ActiveRecord
|
|
77
77
|
#
|
78
78
|
# In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+. So, use the callback macros when
|
79
79
|
# you want to ensure that a certain callback is called for the entire hierarchy, and use the regular overwriteable methods
|
80
|
-
# when you want to leave it up to each
|
80
|
+
# when you want to leave it up to each descendant to decide whether they want to call +super+ and trigger the inherited callbacks.
|
81
81
|
#
|
82
82
|
# *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the callbacks before specifying the
|
83
83
|
# associations. Otherwise, you might trigger the loading of a child before the parent has registered the callbacks and they won't
|
@@ -104,6 +104,37 @@ module ActiveRecord
|
|
104
104
|
# The callback objects have methods named after the callback called with the record as the only parameter, such as:
|
105
105
|
#
|
106
106
|
# class BankAccount < ActiveRecord::Base
|
107
|
+
# before_save EncryptionWrapper.new
|
108
|
+
# after_save EncryptionWrapper.new
|
109
|
+
# after_initialize EncryptionWrapper.new
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# class EncryptionWrapper
|
113
|
+
# def before_save(record)
|
114
|
+
# record.credit_card_number = encrypt(record.credit_card_number)
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# def after_save(record)
|
118
|
+
# record.credit_card_number = decrypt(record.credit_card_number)
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# alias_method :after_find, :after_save
|
122
|
+
#
|
123
|
+
# private
|
124
|
+
# def encrypt(value)
|
125
|
+
# # Secrecy is committed
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# def decrypt(value)
|
129
|
+
# # Secrecy is unveiled
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
#
|
133
|
+
# So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
|
134
|
+
# a method by the name of the callback messaged. You can make these callbacks more flexible by passing in other
|
135
|
+
# initialization data such as the name of the attribute to work with:
|
136
|
+
#
|
137
|
+
# class BankAccount < ActiveRecord::Base
|
107
138
|
# before_save EncryptionWrapper.new("credit_card_number")
|
108
139
|
# after_save EncryptionWrapper.new("credit_card_number")
|
109
140
|
# after_initialize EncryptionWrapper.new("credit_card_number")
|
@@ -115,11 +146,11 @@ module ActiveRecord
|
|
115
146
|
# end
|
116
147
|
#
|
117
148
|
# def before_save(record)
|
118
|
-
# record.
|
149
|
+
# record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
|
119
150
|
# end
|
120
151
|
#
|
121
152
|
# def after_save(record)
|
122
|
-
# record.
|
153
|
+
# record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
|
123
154
|
# end
|
124
155
|
#
|
125
156
|
# alias_method :after_find, :after_save
|
@@ -134,9 +165,6 @@ module ActiveRecord
|
|
134
165
|
# end
|
135
166
|
# end
|
136
167
|
#
|
137
|
-
# So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
|
138
|
-
# a method by the name of the callback messaged.
|
139
|
-
#
|
140
168
|
# The callback macros usually accept a symbol for the method they're supposed to run, but you can also pass a "method string",
|
141
169
|
# which will then be evaluated within the binding of the callback. Example:
|
142
170
|
#
|
@@ -219,8 +247,9 @@ module ActiveRecord
|
|
219
247
|
def after_save() end
|
220
248
|
def create_or_update_with_callbacks #:nodoc:
|
221
249
|
return false if callback(:before_save) == false
|
222
|
-
result = create_or_update_without_callbacks
|
223
|
-
|
250
|
+
if result = create_or_update_without_callbacks
|
251
|
+
callback(:after_save)
|
252
|
+
end
|
224
253
|
result
|
225
254
|
end
|
226
255
|
private :create_or_update_with_callbacks
|
@@ -351,5 +351,21 @@ module ActiveRecord
|
|
351
351
|
retrieve_connection_pool klass.superclass
|
352
352
|
end
|
353
353
|
end
|
354
|
+
|
355
|
+
class ConnectionManagement
|
356
|
+
def initialize(app)
|
357
|
+
@app = app
|
358
|
+
end
|
359
|
+
|
360
|
+
def call(env)
|
361
|
+
@app.call(env)
|
362
|
+
ensure
|
363
|
+
# Don't return connection (and peform implicit rollback) if
|
364
|
+
# this request is a part of integration test
|
365
|
+
unless env.key?("rack.test")
|
366
|
+
ActiveRecord::Base.clear_active_connections!
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
354
370
|
end
|
355
371
|
end
|
@@ -7,6 +7,8 @@ module ActiveRecord
|
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
+
##
|
11
|
+
# :singleton-method:
|
10
12
|
# The connection handler
|
11
13
|
cattr_accessor :connection_handler, :instance_writer => false
|
12
14
|
@@connection_handler = ConnectionAdapters::ConnectionHandler.new
|
@@ -121,6 +123,7 @@ module ActiveRecord
|
|
121
123
|
connection_handler.retrieve_connection(self)
|
122
124
|
end
|
123
125
|
|
126
|
+
# Returns true if +ActiveRecord+ is connected.
|
124
127
|
def connected?
|
125
128
|
connection_handler.connected?(self)
|
126
129
|
end
|
@@ -31,13 +31,13 @@ module ActiveRecord
|
|
31
31
|
# Returns an array of arrays containing the field values.
|
32
32
|
# Order is the same as that returned by +columns+.
|
33
33
|
def select_rows(sql, name = nil)
|
34
|
-
raise NotImplementedError, "select_rows is an abstract method"
|
35
34
|
end
|
35
|
+
undef_method :select_rows
|
36
36
|
|
37
37
|
# Executes the SQL statement in the context of this connection.
|
38
|
-
def execute(sql, name = nil)
|
39
|
-
raise NotImplementedError, "execute is an abstract method"
|
38
|
+
def execute(sql, name = nil, skip_logging = false)
|
40
39
|
end
|
40
|
+
undef_method :execute
|
41
41
|
|
42
42
|
# Returns the last auto-generated ID from the affected table.
|
43
43
|
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
@@ -53,36 +53,124 @@ module ActiveRecord
|
|
53
53
|
def delete(sql, name = nil)
|
54
54
|
delete_sql(sql, name)
|
55
55
|
end
|
56
|
+
|
57
|
+
# Checks whether there is currently no transaction active. This is done
|
58
|
+
# by querying the database driver, and does not use the transaction
|
59
|
+
# house-keeping information recorded by #increment_open_transactions and
|
60
|
+
# friends.
|
61
|
+
#
|
62
|
+
# Returns true if there is no transaction active, false if there is a
|
63
|
+
# transaction active, and nil if this information is unknown.
|
64
|
+
#
|
65
|
+
# Not all adapters supports transaction state introspection. Currently,
|
66
|
+
# only the PostgreSQL adapter supports this.
|
67
|
+
def outside_transaction?
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# Runs the given block in a database transaction, and returns the result
|
72
|
+
# of the block.
|
73
|
+
#
|
74
|
+
# == Nested transactions support
|
75
|
+
#
|
76
|
+
# Most databases don't support true nested transactions. At the time of
|
77
|
+
# writing, the only database that supports true nested transactions that
|
78
|
+
# we're aware of, is MS-SQL.
|
79
|
+
#
|
80
|
+
# In order to get around this problem, #transaction will emulate the effect
|
81
|
+
# of nested transactions, by using savepoints:
|
82
|
+
# http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
|
83
|
+
# Savepoints are supported by MySQL and PostgreSQL, but not SQLite3.
|
84
|
+
#
|
85
|
+
# It is safe to call this method if a database transaction is already open,
|
86
|
+
# i.e. if #transaction is called within another #transaction block. In case
|
87
|
+
# of a nested call, #transaction will behave as follows:
|
88
|
+
#
|
89
|
+
# - The block will be run without doing anything. All database statements
|
90
|
+
# that happen within the block are effectively appended to the already
|
91
|
+
# open database transaction.
|
92
|
+
# - However, if +:requires_new+ is set, the block will be wrapped in a
|
93
|
+
# database savepoint acting as a sub-transaction.
|
94
|
+
#
|
95
|
+
# === Caveats
|
96
|
+
#
|
97
|
+
# MySQL doesn't support DDL transactions. If you perform a DDL operation,
|
98
|
+
# then any created savepoints will be automatically released. For example,
|
99
|
+
# if you've created a savepoint, then you execute a CREATE TABLE statement,
|
100
|
+
# then the savepoint that was created will be automatically released.
|
101
|
+
#
|
102
|
+
# This means that, on MySQL, you shouldn't execute DDL operations inside
|
103
|
+
# a #transaction call that you know might create a savepoint. Otherwise,
|
104
|
+
# #transaction will raise exceptions when it tries to release the
|
105
|
+
# already-automatically-released savepoints:
|
106
|
+
#
|
107
|
+
# Model.connection.transaction do # BEGIN
|
108
|
+
# Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
|
109
|
+
# Model.connection.create_table(...)
|
110
|
+
# # active_record_1 now automatically released
|
111
|
+
# end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
|
112
|
+
# end
|
113
|
+
def transaction(options = {})
|
114
|
+
options.assert_valid_keys :requires_new, :joinable
|
115
|
+
|
116
|
+
last_transaction_joinable = @transaction_joinable
|
117
|
+
if options.has_key?(:joinable)
|
118
|
+
@transaction_joinable = options[:joinable]
|
119
|
+
else
|
120
|
+
@transaction_joinable = true
|
121
|
+
end
|
122
|
+
requires_new = options[:requires_new] || !last_transaction_joinable
|
56
123
|
|
57
|
-
# Wrap a block in a transaction. Returns result of block.
|
58
|
-
def transaction(start_db_transaction = true)
|
59
124
|
transaction_open = false
|
60
125
|
begin
|
61
126
|
if block_given?
|
62
|
-
if
|
63
|
-
|
127
|
+
if requires_new || open_transactions == 0
|
128
|
+
if open_transactions == 0
|
129
|
+
begin_db_transaction
|
130
|
+
elsif requires_new
|
131
|
+
create_savepoint
|
132
|
+
end
|
133
|
+
increment_open_transactions
|
64
134
|
transaction_open = true
|
65
135
|
end
|
66
136
|
yield
|
67
137
|
end
|
68
138
|
rescue Exception => database_transaction_rollback
|
69
|
-
if transaction_open
|
139
|
+
if transaction_open && !outside_transaction?
|
70
140
|
transaction_open = false
|
71
|
-
|
141
|
+
decrement_open_transactions
|
142
|
+
if open_transactions == 0
|
143
|
+
rollback_db_transaction
|
144
|
+
else
|
145
|
+
rollback_to_savepoint
|
146
|
+
end
|
72
147
|
end
|
73
|
-
raise unless database_transaction_rollback.is_a?
|
148
|
+
raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
|
74
149
|
end
|
75
150
|
ensure
|
76
|
-
|
151
|
+
@transaction_joinable = last_transaction_joinable
|
152
|
+
|
153
|
+
if outside_transaction?
|
154
|
+
@open_transactions = 0
|
155
|
+
elsif transaction_open
|
156
|
+
decrement_open_transactions
|
77
157
|
begin
|
78
|
-
|
158
|
+
if open_transactions == 0
|
159
|
+
commit_db_transaction
|
160
|
+
else
|
161
|
+
release_savepoint
|
162
|
+
end
|
79
163
|
rescue Exception => database_transaction_rollback
|
80
|
-
|
164
|
+
if open_transactions == 0
|
165
|
+
rollback_db_transaction
|
166
|
+
else
|
167
|
+
rollback_to_savepoint
|
168
|
+
end
|
81
169
|
raise
|
82
170
|
end
|
83
171
|
end
|
84
172
|
end
|
85
|
-
|
173
|
+
|
86
174
|
# Begins the transaction (and turns off auto-committing).
|
87
175
|
def begin_db_transaction() end
|
88
176
|
|
@@ -163,8 +251,8 @@ module ActiveRecord
|
|
163
251
|
# Returns an array of record hashes with the column names as keys and
|
164
252
|
# column values as values.
|
165
253
|
def select(sql, name = nil)
|
166
|
-
raise NotImplementedError, "select is an abstract method"
|
167
254
|
end
|
255
|
+
undef_method :select
|
168
256
|
|
169
257
|
# Returns the last auto-generated ID from the affected table.
|
170
258
|
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|