activerecord 2.1.2 → 2.2.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 (110) hide show
  1. data/CHANGELOG +32 -6
  2. data/README +0 -0
  3. data/Rakefile +4 -5
  4. data/lib/active_record.rb +11 -10
  5. data/lib/active_record/aggregations.rb +110 -38
  6. data/lib/active_record/association_preload.rb +104 -15
  7. data/lib/active_record/associations.rb +427 -212
  8. data/lib/active_record/associations/association_collection.rb +101 -16
  9. data/lib/active_record/associations/association_proxy.rb +65 -13
  10. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -0
  12. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +13 -3
  13. data/lib/active_record/associations/has_many_association.rb +28 -28
  14. data/lib/active_record/associations/has_many_through_association.rb +21 -19
  15. data/lib/active_record/associations/has_one_association.rb +24 -7
  16. data/lib/active_record/associations/has_one_through_association.rb +3 -4
  17. data/lib/active_record/attribute_methods.rb +13 -5
  18. data/lib/active_record/base.rb +435 -212
  19. data/lib/active_record/calculations.rb +12 -5
  20. data/lib/active_record/callbacks.rb +28 -9
  21. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +355 -0
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +42 -215
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -5
  24. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
  25. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +48 -7
  26. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +10 -4
  27. data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -26
  28. data/lib/active_record/connection_adapters/mysql_adapter.rb +71 -45
  29. data/lib/active_record/connection_adapters/postgresql_adapter.rb +155 -84
  30. data/lib/active_record/dirty.rb +25 -7
  31. data/lib/active_record/dynamic_finder_match.rb +41 -0
  32. data/lib/active_record/fixtures.rb +10 -9
  33. data/lib/active_record/i18n_interpolation_deprecation.rb +26 -0
  34. data/lib/active_record/locale/en.yml +54 -0
  35. data/lib/active_record/migration.rb +47 -10
  36. data/lib/active_record/named_scope.rb +29 -16
  37. data/lib/active_record/reflection.rb +118 -54
  38. data/lib/active_record/schema_dumper.rb +13 -7
  39. data/lib/active_record/test_case.rb +18 -5
  40. data/lib/active_record/transactions.rb +89 -34
  41. data/lib/active_record/validations.rb +270 -180
  42. data/lib/active_record/version.rb +1 -1
  43. data/test/cases/active_schema_test_mysql.rb +5 -0
  44. data/test/cases/adapter_test.rb +6 -0
  45. data/test/cases/aggregations_test.rb +39 -0
  46. data/test/cases/associations/belongs_to_associations_test.rb +10 -0
  47. data/test/cases/associations/eager_load_nested_include_test.rb +30 -12
  48. data/test/cases/associations/eager_test.rb +54 -5
  49. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +77 -10
  50. data/test/cases/associations/has_many_associations_test.rb +74 -7
  51. data/test/cases/associations/has_many_through_associations_test.rb +50 -3
  52. data/test/cases/associations/has_one_associations_test.rb +17 -0
  53. data/test/cases/associations/has_one_through_associations_test.rb +49 -1
  54. data/test/cases/associations_test.rb +0 -0
  55. data/test/cases/attribute_methods_test.rb +59 -4
  56. data/test/cases/base_test.rb +93 -21
  57. data/test/cases/binary_test.rb +1 -5
  58. data/test/cases/calculations_test.rb +5 -0
  59. data/test/cases/callbacks_observers_test.rb +38 -0
  60. data/test/cases/connection_test_mysql.rb +1 -1
  61. data/test/cases/defaults_test.rb +32 -1
  62. data/test/cases/deprecated_finder_test.rb +0 -0
  63. data/test/cases/dirty_test.rb +13 -0
  64. data/test/cases/finder_test.rb +162 -12
  65. data/test/cases/fixtures_test.rb +32 -3
  66. data/test/cases/helper.rb +15 -0
  67. data/test/cases/i18n_test.rb +41 -0
  68. data/test/cases/inheritance_test.rb +2 -2
  69. data/test/cases/lifecycle_test.rb +0 -0
  70. data/test/cases/locking_test.rb +4 -9
  71. data/test/cases/method_scoping_test.rb +109 -2
  72. data/test/cases/migration_test.rb +43 -8
  73. data/test/cases/multiple_db_test.rb +25 -0
  74. data/test/cases/named_scope_test.rb +74 -0
  75. data/test/cases/pooled_connections_test.rb +103 -0
  76. data/test/cases/readonly_test.rb +0 -0
  77. data/test/cases/reflection_test.rb +11 -3
  78. data/test/cases/reload_models_test.rb +20 -0
  79. data/test/cases/sanitize_test.rb +25 -0
  80. data/test/cases/schema_authorization_test_postgresql.rb +2 -2
  81. data/test/cases/transactions_test.rb +62 -12
  82. data/test/cases/unconnected_test.rb +0 -0
  83. data/test/cases/validations_i18n_test.rb +921 -0
  84. data/test/cases/validations_test.rb +44 -33
  85. data/test/connections/native_mysql/connection.rb +1 -3
  86. data/test/fixtures/companies.yml +1 -0
  87. data/test/fixtures/customers.yml +10 -1
  88. data/test/fixtures/fixture_database.sqlite3 +0 -0
  89. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  90. data/test/fixtures/organizations.yml +5 -0
  91. data/test/migrations/broken/100_migration_that_raises_exception.rb +10 -0
  92. data/test/models/author.rb +3 -0
  93. data/test/models/category.rb +3 -0
  94. data/test/models/club.rb +6 -0
  95. data/test/models/company.rb +25 -1
  96. data/test/models/customer.rb +19 -1
  97. data/test/models/member.rb +2 -0
  98. data/test/models/member_detail.rb +4 -0
  99. data/test/models/organization.rb +4 -0
  100. data/test/models/parrot.rb +1 -0
  101. data/test/models/post.rb +3 -0
  102. data/test/models/reply.rb +0 -0
  103. data/test/models/topic.rb +3 -0
  104. data/test/schema/schema.rb +12 -1
  105. metadata +22 -10
  106. data/lib/active_record/vendor/mysql.rb +0 -1214
  107. data/test/cases/adapter_test_sqlserver.rb +0 -95
  108. data/test/cases/table_name_test_sqlserver.rb +0 -23
  109. data/test/cases/threaded_connections_test.rb +0 -48
  110. data/test/schema/sqlserver_specific_schema.rb +0 -5
@@ -14,7 +14,7 @@ module ActiveRecord
14
14
  #
15
15
  # The third approach, count using options, accepts an option hash as the only parameter. The options are:
16
16
  #
17
- # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
17
+ # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base.
18
18
  # * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
19
19
  # or named associations in the same form used for the <tt>:include</tt> option, which will perform an INNER JOIN on the associated table(s).
20
20
  # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
@@ -98,7 +98,7 @@ module ActiveRecord
98
98
  # end
99
99
  #
100
100
  # Options:
101
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
101
+ # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base.
102
102
  # * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything, the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
103
103
  # * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
104
104
  # The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
@@ -188,7 +188,7 @@ module ActiveRecord
188
188
  end
189
189
 
190
190
  joins = ""
191
- add_joins!(joins, options, scope)
191
+ add_joins!(joins, options[:joins], scope)
192
192
 
193
193
  if merged_includes.any?
194
194
  join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, joins)
@@ -266,7 +266,14 @@ module ActiveRecord
266
266
  # column_alias_for("count(*)") # => "count_all"
267
267
  # column_alias_for("count", "id") # => "count_id"
268
268
  def column_alias_for(*keys)
269
- connection.table_alias_for(keys.join(' ').downcase.gsub(/\*/, 'all').gsub(/\W+/, ' ').strip.gsub(/ +/, '_'))
269
+ table_name = keys.join(' ')
270
+ table_name.downcase!
271
+ table_name.gsub!(/\*/, 'all')
272
+ table_name.gsub!(/\W+/, ' ')
273
+ table_name.strip!
274
+ table_name.gsub!(/ +/, '_')
275
+
276
+ connection.table_alias_for(table_name)
270
277
  end
271
278
 
272
279
  def column_for(field)
@@ -279,7 +286,7 @@ module ActiveRecord
279
286
  case operation
280
287
  when 'count' then value.to_i
281
288
  when 'sum' then type_cast_using_column(value || '0', column)
282
- when 'avg' then value && value.to_d
289
+ when 'avg' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d
283
290
  else type_cast_using_column(value, column)
284
291
  end
285
292
  end
@@ -3,9 +3,9 @@ require 'observer'
3
3
  module ActiveRecord
4
4
  # Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic
5
5
  # before or after an alteration of the object state. This can be used to make sure that associated and
6
- # dependent objects are deleted when destroy is called (by overwriting +before_destroy+) or to massage attributes
6
+ # dependent objects are deleted when +destroy+ is called (by overwriting +before_destroy+) or to massage attributes
7
7
  # before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider
8
- # the <tt>Base#save</tt> call:
8
+ # the <tt>Base#save</tt> call for a new record:
9
9
  #
10
10
  # * (-) <tt>save</tt>
11
11
  # * (-) <tt>valid</tt>
@@ -22,7 +22,8 @@ module ActiveRecord
22
22
  # * (8) <tt>after_save</tt>
23
23
  #
24
24
  # That's a total of eight callbacks, which gives you immense power to react and prepare for each state in the
25
- # Active Record lifecycle.
25
+ # Active Record lifecycle. The sequence for calling <tt>Base#save</tt> an existing record is similar, except that each
26
+ # <tt>_on_create</tt> callback is replaced by the corresponding <tt>_on_update</tt> callback.
26
27
  #
27
28
  # Examples:
28
29
  # class CreditCard < ActiveRecord::Base
@@ -50,7 +51,7 @@ module ActiveRecord
50
51
  #
51
52
  # == Inheritable callback queues
52
53
  #
53
- # Besides the overwriteable callback methods, it's also possible to register callbacks through the use of the callback macros.
54
+ # Besides the overwritable callback methods, it's also possible to register callbacks through the use of the callback macros.
54
55
  # Their main advantage is that the macros add behavior into a callback queue that is kept intact down through an inheritance
55
56
  # hierarchy. Example:
56
57
  #
@@ -161,7 +162,7 @@ module ActiveRecord
161
162
  # == <tt>before_validation*</tt> returning statements
162
163
  #
163
164
  # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be aborted and <tt>Base#save</tt> will return +false+.
164
- # If Base#save! is called it will raise a RecordNotSaved exception.
165
+ # If Base#save! is called it will raise a ActiveRecord::RecordInvalid exception.
165
166
  # Nothing will be appended to the errors object.
166
167
  #
167
168
  # == Canceling callbacks
@@ -169,6 +170,18 @@ module ActiveRecord
169
170
  # If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are cancelled. If an <tt>after_*</tt> callback returns
170
171
  # +false+, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks
171
172
  # defined as methods on the model, which are called last.
173
+ #
174
+ # == Transactions
175
+ #
176
+ # The entire callback chain of a +save+, <tt>save!</tt>, or +destroy+ call runs
177
+ # within a transaction. That includes <tt>after_*</tt> hooks. If everything
178
+ # goes fine a COMMIT is executed once the chain has been completed.
179
+ #
180
+ # If a <tt>before_*</tt> callback cancels the action a ROLLBACK is issued. You
181
+ # can also trigger a ROLLBACK raising an exception in any of the callbacks,
182
+ # including <tt>after_*</tt> hooks. Note, however, that in that case the client
183
+ # needs to be aware of it because an ordinary +save+ will raise such exception
184
+ # instead of quietly returning +false+.
172
185
  module Callbacks
173
186
  CALLBACKS = %w(
174
187
  after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
@@ -197,6 +210,8 @@ module ActiveRecord
197
210
  def before_save() end
198
211
 
199
212
  # Is called _after_ <tt>Base.save</tt> (regardless of whether it's a +create+ or +update+ save).
213
+ # Note that this callback is still wrapped in the transaction around +save+. For example, if you
214
+ # invoke an external indexer at this point it won't see the changes in the database.
200
215
  #
201
216
  # class Contact < ActiveRecord::Base
202
217
  # after_save { logger.info( 'New contact saved!' ) }
@@ -214,6 +229,8 @@ module ActiveRecord
214
229
  def before_create() end
215
230
 
216
231
  # Is called _after_ <tt>Base.save</tt> on new objects that haven't been saved yet (no record exists).
232
+ # Note that this callback is still wrapped in the transaction around +save+. For example, if you
233
+ # invoke an external indexer at this point it won't see the changes in the database.
217
234
  def after_create() end
218
235
  def create_with_callbacks #:nodoc:
219
236
  return false if callback(:before_create) == false
@@ -227,6 +244,8 @@ module ActiveRecord
227
244
  def before_update() end
228
245
 
229
246
  # Is called _after_ <tt>Base.save</tt> on existing objects that have a record.
247
+ # Note that this callback is still wrapped in the transaction around +save+. For example, if you
248
+ # invoke an external indexer at this point it won't see the changes in the database.
230
249
  def after_update() end
231
250
 
232
251
  def update_with_callbacks(*args) #:nodoc:
@@ -262,7 +281,7 @@ module ActiveRecord
262
281
  def valid_with_callbacks? #:nodoc:
263
282
  return false if callback(:before_validation) == false
264
283
  if new_record? then result = callback(:before_validation_on_create) else result = callback(:before_validation_on_update) end
265
- return false if result == false
284
+ return false if false == result
266
285
 
267
286
  result = valid_without_callbacks?
268
287
 
@@ -293,14 +312,14 @@ module ActiveRecord
293
312
 
294
313
  private
295
314
  def callback(method)
296
- notify(method)
297
-
298
- result = run_callbacks(method) { |result, object| result == false }
315
+ result = run_callbacks(method) { |result, object| false == result }
299
316
 
300
317
  if result != false && respond_to_without_attributes?(method)
301
318
  result = send(method)
302
319
  end
303
320
 
321
+ notify(method)
322
+
304
323
  return result
305
324
  end
306
325
 
@@ -0,0 +1,355 @@
1
+ require 'monitor'
2
+ require 'set'
3
+
4
+ module ActiveRecord
5
+ # Raised when a connection could not be obtained within the connection
6
+ # acquisition timeout period.
7
+ class ConnectionTimeoutError < ConnectionNotEstablished
8
+ end
9
+
10
+ module ConnectionAdapters
11
+ # Connection pool base class for managing ActiveRecord database
12
+ # connections.
13
+ #
14
+ # == Introduction
15
+ #
16
+ # A connection pool synchronizes thread access to a limited number of
17
+ # database connections. The basic idea is that each thread checks out a
18
+ # database connection from the pool, uses that connection, and checks the
19
+ # connection back in. ConnectionPool is completely thread-safe, and will
20
+ # ensure that a connection cannot be used by two threads at the same time,
21
+ # as long as ConnectionPool's contract is correctly followed. It will also
22
+ # handle cases in which there are more threads than connections: if all
23
+ # connections have been checked out, and a thread tries to checkout a
24
+ # connection anyway, then ConnectionPool will wait until some other thread
25
+ # has checked in a connection.
26
+ #
27
+ # == Obtaining (checking out) a connection
28
+ #
29
+ # Connections can be obtained and used from a connection pool in several
30
+ # ways:
31
+ #
32
+ # 1. Simply use ActiveRecord::Base.connection as with ActiveRecord 2.1 and
33
+ # earlier (pre-connection-pooling). Eventually, when you're done with
34
+ # the connection(s) and wish it to be returned to the pool, you call
35
+ # ActiveRecord::Base.clear_active_connections!. This will be the
36
+ # default behavior for ActiveRecord when used in conjunction with
37
+ # ActionPack's request handling cycle.
38
+ # 2. Manually check out a connection from the pool with
39
+ # ActiveRecord::Base.connection_pool.checkout. You are responsible for
40
+ # returning this connection to the pool when finished by calling
41
+ # ActiveRecord::Base.connection_pool.checkin(connection).
42
+ # 3. Use ActiveRecord::Base.connection_pool.with_connection(&block), which
43
+ # obtains a connection, yields it as the sole argument to the block,
44
+ # and returns it to the pool after the block completes.
45
+ #
46
+ # Connections in the pool are actually AbstractAdapter objects (or objects
47
+ # compatible with AbstractAdapter's interface).
48
+ #
49
+ # == Options
50
+ #
51
+ # There are two connection-pooling-related options that you can add to
52
+ # your database connection configuration:
53
+ #
54
+ # * +pool+: number indicating size of connection pool (default 5)
55
+ # * +wait_timeout+: number of seconds to block and wait for a connection
56
+ # before giving up and raising a timeout error (default 5 seconds).
57
+ class ConnectionPool
58
+ attr_reader :spec
59
+
60
+ # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
61
+ # object which describes database connection information (e.g. adapter,
62
+ # host name, username, password, etc), as well as the maximum size for
63
+ # this ConnectionPool.
64
+ #
65
+ # The default ConnectionPool maximum size is 5.
66
+ def initialize(spec)
67
+ @spec = spec
68
+
69
+ # The cache of reserved connections mapped to threads
70
+ @reserved_connections = {}
71
+
72
+ # The mutex used to synchronize pool access
73
+ @connection_mutex = Monitor.new
74
+ @queue = @connection_mutex.new_cond
75
+
76
+ # default 5 second timeout unless on ruby 1.9
77
+ @timeout =
78
+ if RUBY_VERSION < '1.9'
79
+ spec.config[:wait_timeout] || 5
80
+ end
81
+
82
+ # default max pool size to 5
83
+ @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
84
+
85
+ @connections = []
86
+ @checked_out = []
87
+ end
88
+
89
+ # Retrieve the connection associated with the current thread, or call
90
+ # #checkout to obtain one if necessary.
91
+ #
92
+ # #connection can be called any number of times; the connection is
93
+ # held in a hash keyed by the thread id.
94
+ def connection
95
+ if conn = @reserved_connections[current_connection_id]
96
+ conn
97
+ else
98
+ @reserved_connections[current_connection_id] = checkout
99
+ end
100
+ end
101
+
102
+ # Signal that the thread is finished with the current connection.
103
+ # #release_connection releases the connection-thread association
104
+ # and returns the connection to the pool.
105
+ def release_connection
106
+ conn = @reserved_connections.delete(current_connection_id)
107
+ checkin conn if conn
108
+ end
109
+
110
+ # Reserve a connection, and yield it to a block. Ensure the connection is
111
+ # checked back in when finished.
112
+ def with_connection
113
+ conn = checkout
114
+ yield conn
115
+ ensure
116
+ checkin conn
117
+ end
118
+
119
+ # Returns true if a connection has already been opened.
120
+ def connected?
121
+ !@connections.empty?
122
+ end
123
+
124
+ # Disconnects all connections in the pool, and clears the pool.
125
+ def disconnect!
126
+ @reserved_connections.each do |name,conn|
127
+ checkin conn
128
+ end
129
+ @reserved_connections = {}
130
+ @connections.each do |conn|
131
+ conn.disconnect!
132
+ end
133
+ @connections = []
134
+ end
135
+
136
+ # Clears the cache which maps classes
137
+ def clear_reloadable_connections!
138
+ @reserved_connections.each do |name, conn|
139
+ checkin conn
140
+ end
141
+ @reserved_connections = {}
142
+ @connections.each do |conn|
143
+ conn.disconnect! if conn.requires_reloading?
144
+ end
145
+ @connections = []
146
+ end
147
+
148
+ # Verify active connections and remove and disconnect connections
149
+ # associated with stale threads.
150
+ def verify_active_connections! #:nodoc:
151
+ clear_stale_cached_connections!
152
+ @connections.each do |connection|
153
+ connection.verify!
154
+ end
155
+ end
156
+
157
+ # Return any checked-out connections back to the pool by threads that
158
+ # are no longer alive.
159
+ def clear_stale_cached_connections!
160
+ remove_stale_cached_threads!(@reserved_connections) do |name, conn|
161
+ checkin conn
162
+ end
163
+ end
164
+
165
+ # Check-out a database connection from the pool, indicating that you want
166
+ # to use it. You should call #checkin when you no longer need this.
167
+ #
168
+ # This is done by either returning an existing connection, or by creating
169
+ # a new connection. If the maximum number of connections for this pool has
170
+ # already been reached, but the pool is empty (i.e. they're all being used),
171
+ # then this method will wait until a thread has checked in a connection.
172
+ # The wait time is bounded however: if no connection can be checked out
173
+ # within the timeout specified for this pool, then a ConnectionTimeoutError
174
+ # exception will be raised.
175
+ #
176
+ # Returns: an AbstractAdapter object.
177
+ #
178
+ # Raises:
179
+ # - ConnectionTimeoutError: no connection can be obtained from the pool
180
+ # within the timeout period.
181
+ def checkout
182
+ # Checkout an available connection
183
+ @connection_mutex.synchronize do
184
+ loop do
185
+ conn = if @checked_out.size < @connections.size
186
+ checkout_existing_connection
187
+ elsif @connections.size < @size
188
+ checkout_new_connection
189
+ end
190
+ return conn if conn
191
+ # No connections available; wait for one
192
+ if @queue.wait(@timeout)
193
+ next
194
+ else
195
+ # try looting dead threads
196
+ clear_stale_cached_connections!
197
+ if @size == @checked_out.size
198
+ raise ConnectionTimeoutError, "could not obtain a database connection#{" within #{@timeout} seconds" if @timeout}. The max pool size is currently #{@size}; consider increasing it."
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ # Check-in a database connection back into the pool, indicating that you
206
+ # no longer need this connection.
207
+ #
208
+ # +conn+: an AbstractAdapter object, which was obtained by earlier by
209
+ # calling +checkout+ on this pool.
210
+ def checkin(conn)
211
+ @connection_mutex.synchronize do
212
+ conn.run_callbacks :checkin
213
+ @checked_out.delete conn
214
+ @queue.signal
215
+ end
216
+ end
217
+
218
+ synchronize :clear_reloadable_connections!, :verify_active_connections!,
219
+ :connected?, :disconnect!, :with => :@connection_mutex
220
+
221
+ private
222
+ def new_connection
223
+ ActiveRecord::Base.send(spec.adapter_method, spec.config)
224
+ end
225
+
226
+ def current_connection_id #:nodoc:
227
+ Thread.current.object_id
228
+ end
229
+
230
+ # Remove stale threads from the cache.
231
+ def remove_stale_cached_threads!(cache, &block)
232
+ keys = Set.new(cache.keys)
233
+
234
+ Thread.list.each do |thread|
235
+ keys.delete(thread.object_id) if thread.alive?
236
+ end
237
+ keys.each do |key|
238
+ next unless cache.has_key?(key)
239
+ block.call(key, cache[key])
240
+ cache.delete(key)
241
+ end
242
+ end
243
+
244
+ def checkout_new_connection
245
+ c = new_connection
246
+ @connections << c
247
+ checkout_and_verify(c)
248
+ end
249
+
250
+ def checkout_existing_connection
251
+ c = (@connections - @checked_out).first
252
+ checkout_and_verify(c)
253
+ end
254
+
255
+ def checkout_and_verify(c)
256
+ c.verify!
257
+ c.run_callbacks :checkout
258
+ @checked_out << c
259
+ c
260
+ end
261
+ end
262
+
263
+ # ConnectionHandler is a collection of ConnectionPool objects. It is used
264
+ # for keeping separate connection pools for ActiveRecord models that connect
265
+ # to different databases.
266
+ #
267
+ # For example, suppose that you have 5 models, with the following hierarchy:
268
+ #
269
+ # |
270
+ # +-- Book
271
+ # | |
272
+ # | +-- ScaryBook
273
+ # | +-- GoodBook
274
+ # +-- Author
275
+ # +-- BankAccount
276
+ #
277
+ # Suppose that Book is to connect to a separate database (i.e. one other
278
+ # than the default database). Then Book, ScaryBook and GoodBook will all use
279
+ # the same connection pool. Likewise, Author and BankAccount will use the
280
+ # same connection pool. However, the connection pool used by Author/BankAccount
281
+ # is not the same as the one used by Book/ScaryBook/GoodBook.
282
+ #
283
+ # Normally there is only a single ConnectionHandler instance, accessible via
284
+ # ActiveRecord::Base.connection_handler. ActiveRecord models use this to
285
+ # determine that connection pool that they should use.
286
+ class ConnectionHandler
287
+ def initialize(pools = {})
288
+ @connection_pools = pools
289
+ end
290
+
291
+ def connection_pools
292
+ @connection_pools ||= {}
293
+ end
294
+
295
+ def establish_connection(name, spec)
296
+ @connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec)
297
+ end
298
+
299
+ # Returns any connections in use by the current thread back to the pool,
300
+ # and also returns connections to the pool cached by threads that are no
301
+ # longer alive.
302
+ def clear_active_connections!
303
+ @connection_pools.each_value {|pool| pool.release_connection }
304
+ end
305
+
306
+ # Clears the cache which maps classes
307
+ def clear_reloadable_connections!
308
+ @connection_pools.each_value {|pool| pool.clear_reloadable_connections! }
309
+ end
310
+
311
+ def clear_all_connections!
312
+ @connection_pools.each_value {|pool| pool.disconnect! }
313
+ end
314
+
315
+ # Verify active connections.
316
+ def verify_active_connections! #:nodoc:
317
+ @connection_pools.each_value {|pool| pool.verify_active_connections! }
318
+ end
319
+
320
+ # Locate the connection of the nearest super class. This can be an
321
+ # active or defined connection: if it is the latter, it will be
322
+ # opened and set as the active connection for the class it was defined
323
+ # for (not necessarily the current class).
324
+ def retrieve_connection(klass) #:nodoc:
325
+ pool = retrieve_connection_pool(klass)
326
+ (pool && pool.connection) or raise ConnectionNotEstablished
327
+ end
328
+
329
+ # Returns true if a connection that's accessible to this class has
330
+ # already been opened.
331
+ def connected?(klass)
332
+ conn = retrieve_connection_pool(klass)
333
+ conn ? conn.connected? : false
334
+ end
335
+
336
+ # Remove the connection for this class. This will close the active
337
+ # connection and the defined connection (if they exist). The result
338
+ # can be used as an argument for establish_connection, for easily
339
+ # re-establishing the connection.
340
+ def remove_connection(klass)
341
+ pool = @connection_pools[klass.name]
342
+ @connection_pools.delete_if { |key, value| value == pool }
343
+ pool.disconnect! if pool
344
+ pool.spec.config if pool
345
+ end
346
+
347
+ def retrieve_connection_pool(klass)
348
+ pool = @connection_pools[klass.name]
349
+ return pool if pool
350
+ return nil if ActiveRecord::Base == klass
351
+ retrieve_connection_pool klass.superclass
352
+ end
353
+ end
354
+ end
355
+ end