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.

Files changed (120) hide show
  1. data/CHANGELOG +438 -396
  2. data/Rakefile +4 -2
  3. data/lib/active_record.rb +46 -43
  4. data/lib/active_record/association_preload.rb +34 -19
  5. data/lib/active_record/associations.rb +193 -251
  6. data/lib/active_record/associations/association_collection.rb +38 -21
  7. data/lib/active_record/associations/association_proxy.rb +11 -4
  8. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +2 -2
  9. data/lib/active_record/associations/has_many_association.rb +2 -2
  10. data/lib/active_record/associations/has_many_through_association.rb +8 -8
  11. data/lib/active_record/associations/has_one_association.rb +11 -2
  12. data/lib/active_record/attribute_methods.rb +1 -0
  13. data/lib/active_record/autosave_association.rb +349 -0
  14. data/lib/active_record/base.rb +292 -106
  15. data/lib/active_record/batches.rb +73 -0
  16. data/lib/active_record/calculations.rb +34 -16
  17. data/lib/active_record/callbacks.rb +37 -8
  18. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +16 -0
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -0
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +103 -15
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -6
  22. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +28 -25
  23. data/lib/active_record/connection_adapters/abstract_adapter.rb +29 -5
  24. data/lib/active_record/connection_adapters/mysql_adapter.rb +50 -21
  25. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -41
  26. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
  27. data/lib/active_record/connection_adapters/sqlite_adapter.rb +41 -21
  28. data/lib/active_record/dirty.rb +1 -1
  29. data/lib/active_record/dynamic_scope_match.rb +25 -0
  30. data/lib/active_record/fixtures.rb +193 -198
  31. data/lib/active_record/locale/en.yml +1 -1
  32. data/lib/active_record/locking/optimistic.rb +33 -0
  33. data/lib/active_record/migration.rb +8 -2
  34. data/lib/active_record/named_scope.rb +13 -6
  35. data/lib/active_record/nested_attributes.rb +329 -0
  36. data/lib/active_record/query_cache.rb +25 -13
  37. data/lib/active_record/reflection.rb +6 -1
  38. data/lib/active_record/schema_dumper.rb +2 -0
  39. data/lib/active_record/serialization.rb +3 -1
  40. data/lib/active_record/serializers/json_serializer.rb +19 -0
  41. data/lib/active_record/serializers/xml_serializer.rb +28 -13
  42. data/lib/active_record/session_store.rb +318 -0
  43. data/lib/active_record/test_case.rb +15 -9
  44. data/lib/active_record/timestamp.rb +2 -2
  45. data/lib/active_record/transactions.rb +58 -8
  46. data/lib/active_record/validations.rb +29 -24
  47. data/lib/active_record/version.rb +2 -2
  48. data/test/cases/ar_schema_test.rb +0 -1
  49. data/test/cases/associations/belongs_to_associations_test.rb +35 -131
  50. data/test/cases/associations/cascaded_eager_loading_test.rb +8 -0
  51. data/test/cases/associations/eager_load_nested_include_test.rb +29 -0
  52. data/test/cases/associations/eager_test.rb +137 -7
  53. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +45 -7
  54. data/test/cases/associations/has_many_associations_test.rb +110 -149
  55. data/test/cases/associations/has_many_through_associations_test.rb +39 -7
  56. data/test/cases/associations/has_one_associations_test.rb +39 -92
  57. data/test/cases/associations/has_one_through_associations_test.rb +34 -3
  58. data/test/cases/associations/inner_join_association_test.rb +0 -5
  59. data/test/cases/associations/join_model_test.rb +5 -7
  60. data/test/cases/attribute_methods_test.rb +13 -1
  61. data/test/cases/autosave_association_test.rb +901 -0
  62. data/test/cases/base_test.rb +41 -21
  63. data/test/cases/batches_test.rb +61 -0
  64. data/test/cases/calculations_test.rb +37 -17
  65. data/test/cases/callbacks_test.rb +43 -5
  66. data/test/cases/connection_pool_test.rb +25 -0
  67. data/test/cases/copy_table_test_sqlite.rb +11 -0
  68. data/test/cases/datatype_test_postgresql.rb +1 -0
  69. data/test/cases/defaults_test.rb +37 -26
  70. data/test/cases/dirty_test.rb +26 -2
  71. data/test/cases/finder_test.rb +79 -44
  72. data/test/cases/fixtures_test.rb +15 -19
  73. data/test/cases/helper.rb +26 -19
  74. data/test/cases/inheritance_test.rb +2 -2
  75. data/test/cases/json_serialization_test.rb +1 -1
  76. data/test/cases/locking_test.rb +23 -5
  77. data/test/cases/method_scoping_test.rb +126 -3
  78. data/test/cases/migration_test.rb +253 -237
  79. data/test/cases/named_scope_test.rb +73 -3
  80. data/test/cases/nested_attributes_test.rb +509 -0
  81. data/test/cases/query_cache_test.rb +0 -4
  82. data/test/cases/reflection_test.rb +13 -3
  83. data/test/cases/reload_models_test.rb +3 -1
  84. data/test/cases/repair_helper.rb +50 -0
  85. data/test/cases/schema_dumper_test.rb +0 -1
  86. data/test/cases/transactions_test.rb +177 -12
  87. data/test/cases/validations_i18n_test.rb +288 -294
  88. data/test/cases/validations_test.rb +230 -180
  89. data/test/cases/xml_serialization_test.rb +19 -1
  90. data/test/fixtures/fixture_database.sqlite3 +0 -0
  91. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  92. data/test/fixtures/member_types.yml +6 -0
  93. data/test/fixtures/members.yml +3 -1
  94. data/test/fixtures/people.yml +10 -1
  95. data/test/fixtures/toys.yml +4 -0
  96. data/test/models/author.rb +1 -2
  97. data/test/models/bird.rb +3 -0
  98. data/test/models/category.rb +1 -0
  99. data/test/models/company.rb +3 -0
  100. data/test/models/developer.rb +12 -0
  101. data/test/models/event.rb +3 -0
  102. data/test/models/member.rb +1 -0
  103. data/test/models/member_detail.rb +1 -0
  104. data/test/models/member_type.rb +3 -0
  105. data/test/models/owner.rb +2 -1
  106. data/test/models/parrot.rb +2 -0
  107. data/test/models/person.rb +6 -0
  108. data/test/models/pet.rb +2 -1
  109. data/test/models/pirate.rb +55 -1
  110. data/test/models/post.rb +6 -0
  111. data/test/models/project.rb +1 -0
  112. data/test/models/reply.rb +6 -0
  113. data/test/models/ship.rb +8 -1
  114. data/test/models/ship_part.rb +5 -0
  115. data/test/models/topic.rb +13 -1
  116. data/test/models/toy.rb +4 -0
  117. data/test/schema/schema.rb +35 -2
  118. metadata +70 -9
  119. data/test/fixtures/fixture_database.sqlite +0 -0
  120. 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. The value is returned as a float. See +calculate+ for examples with options.
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 with the same data type of the column. See +calculate+ for examples with options.
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. The value is returned with the same data type of the column. See +calculate+ for examples with options.
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. The value is returned with the same data type of the column. See +calculate+ for examples with options.
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) ? options = args[0] : column_name = args[0]
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 if args.size > 0
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
- options[:having].downcase!
212
- options[:having].gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias)
229
+ having.downcase!
230
+ having.gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias)
213
231
  end
214
232
 
215
- sql << " HAVING #{options[: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 descendent to decide whether they want to call +super+ and trigger the inherited callbacks.
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.credit_card_number = encrypt(record.credit_card_number)
149
+ # record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
119
150
  # end
120
151
  #
121
152
  # def after_save(record)
122
- # record.credit_card_number = decrypt(record.credit_card_number)
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
- callback(:after_save)
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 start_db_transaction
63
- begin_db_transaction
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
- rollback_db_transaction
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? ActiveRecord::Rollback
148
+ raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
74
149
  end
75
150
  ensure
76
- if transaction_open
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
- commit_db_transaction
158
+ if open_transactions == 0
159
+ commit_db_transaction
160
+ else
161
+ release_savepoint
162
+ end
79
163
  rescue Exception => database_transaction_rollback
80
- rollback_db_transaction
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)