activerecord 4.2.11.3 → 5.0.7.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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1638 -1132
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +7 -8
  5. data/examples/performance.rb +2 -3
  6. data/examples/simple.rb +0 -1
  7. data/lib/active_record.rb +7 -2
  8. data/lib/active_record/aggregations.rb +34 -21
  9. data/lib/active_record/association_relation.rb +7 -4
  10. data/lib/active_record/associations.rb +347 -218
  11. data/lib/active_record/associations/alias_tracker.rb +19 -16
  12. data/lib/active_record/associations/association.rb +22 -10
  13. data/lib/active_record/associations/association_scope.rb +75 -104
  14. data/lib/active_record/associations/belongs_to_association.rb +21 -32
  15. data/lib/active_record/associations/builder/association.rb +28 -34
  16. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  17. data/lib/active_record/associations/builder/collection_association.rb +7 -19
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +16 -11
  19. data/lib/active_record/associations/builder/has_many.rb +4 -4
  20. data/lib/active_record/associations/builder/has_one.rb +11 -6
  21. data/lib/active_record/associations/builder/singular_association.rb +13 -11
  22. data/lib/active_record/associations/collection_association.rb +85 -69
  23. data/lib/active_record/associations/collection_proxy.rb +104 -46
  24. data/lib/active_record/associations/foreign_association.rb +1 -1
  25. data/lib/active_record/associations/has_many_association.rb +21 -78
  26. data/lib/active_record/associations/has_many_through_association.rb +6 -47
  27. data/lib/active_record/associations/has_one_association.rb +12 -5
  28. data/lib/active_record/associations/join_dependency.rb +38 -22
  29. data/lib/active_record/associations/join_dependency/join_association.rb +15 -14
  30. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  31. data/lib/active_record/associations/preloader.rb +14 -4
  32. data/lib/active_record/associations/preloader/association.rb +52 -71
  33. data/lib/active_record/associations/preloader/collection_association.rb +0 -7
  34. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +0 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +36 -17
  38. data/lib/active_record/associations/singular_association.rb +13 -1
  39. data/lib/active_record/associations/through_association.rb +12 -4
  40. data/lib/active_record/attribute.rb +69 -19
  41. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  42. data/lib/active_record/attribute_assignment.rb +19 -140
  43. data/lib/active_record/attribute_decorators.rb +6 -5
  44. data/lib/active_record/attribute_methods.rb +69 -44
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  46. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  47. data/lib/active_record/attribute_methods/primary_key.rb +16 -3
  48. data/lib/active_record/attribute_methods/query.rb +2 -2
  49. data/lib/active_record/attribute_methods/read.rb +31 -59
  50. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  52. data/lib/active_record/attribute_methods/write.rb +13 -37
  53. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  54. data/lib/active_record/attribute_set.rb +32 -3
  55. data/lib/active_record/attribute_set/builder.rb +42 -16
  56. data/lib/active_record/attributes.rb +199 -81
  57. data/lib/active_record/autosave_association.rb +54 -17
  58. data/lib/active_record/base.rb +32 -23
  59. data/lib/active_record/callbacks.rb +39 -43
  60. data/lib/active_record/coders/json.rb +1 -1
  61. data/lib/active_record/coders/yaml_column.rb +20 -8
  62. data/lib/active_record/collection_cache_key.rb +50 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +467 -189
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -62
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +39 -4
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +86 -13
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -188
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +407 -156
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -71
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +433 -399
  76. data/lib/active_record/connection_adapters/column.rb +28 -43
  77. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  78. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  79. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  80. data/lib/active_record/connection_adapters/mysql/database_statements.rb +108 -0
  81. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  82. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  83. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  84. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  87. data/lib/active_record/connection_adapters/mysql2_adapter.rb +25 -166
  88. data/lib/active_record/connection_adapters/postgresql/column.rb +33 -11
  89. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -72
  90. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  92. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +37 -57
  93. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +3 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -2
  95. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  97. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +13 -3
  98. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  99. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  102. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  106. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +56 -19
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +250 -154
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -2
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +264 -170
  116. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  118. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  121. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +151 -194
  122. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  123. data/lib/active_record/connection_handling.rb +37 -14
  124. data/lib/active_record/core.rb +92 -108
  125. data/lib/active_record/counter_cache.rb +13 -24
  126. data/lib/active_record/dynamic_matchers.rb +1 -20
  127. data/lib/active_record/enum.rb +116 -76
  128. data/lib/active_record/errors.rb +87 -48
  129. data/lib/active_record/explain.rb +20 -9
  130. data/lib/active_record/explain_registry.rb +1 -1
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/file.rb +26 -5
  133. data/lib/active_record/fixtures.rb +77 -41
  134. data/lib/active_record/gem_version.rb +4 -4
  135. data/lib/active_record/inheritance.rb +32 -40
  136. data/lib/active_record/integration.rb +17 -14
  137. data/lib/active_record/internal_metadata.rb +56 -0
  138. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  139. data/lib/active_record/locale/en.yml +3 -2
  140. data/lib/active_record/locking/optimistic.rb +15 -15
  141. data/lib/active_record/locking/pessimistic.rb +1 -1
  142. data/lib/active_record/log_subscriber.rb +48 -24
  143. data/lib/active_record/migration.rb +362 -111
  144. data/lib/active_record/migration/command_recorder.rb +59 -18
  145. data/lib/active_record/migration/compatibility.rb +126 -0
  146. data/lib/active_record/model_schema.rb +270 -73
  147. data/lib/active_record/nested_attributes.rb +58 -29
  148. data/lib/active_record/no_touching.rb +4 -0
  149. data/lib/active_record/null_relation.rb +16 -8
  150. data/lib/active_record/persistence.rb +152 -90
  151. data/lib/active_record/query_cache.rb +18 -23
  152. data/lib/active_record/querying.rb +12 -11
  153. data/lib/active_record/railtie.rb +23 -16
  154. data/lib/active_record/railties/controller_runtime.rb +1 -1
  155. data/lib/active_record/railties/databases.rake +52 -41
  156. data/lib/active_record/readonly_attributes.rb +1 -1
  157. data/lib/active_record/reflection.rb +302 -115
  158. data/lib/active_record/relation.rb +187 -120
  159. data/lib/active_record/relation/batches.rb +141 -36
  160. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  161. data/lib/active_record/relation/calculations.rb +92 -117
  162. data/lib/active_record/relation/delegation.rb +8 -20
  163. data/lib/active_record/relation/finder_methods.rb +173 -89
  164. data/lib/active_record/relation/from_clause.rb +32 -0
  165. data/lib/active_record/relation/merger.rb +16 -42
  166. data/lib/active_record/relation/predicate_builder.rb +120 -107
  167. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  168. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  169. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  170. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  171. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  172. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  173. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  174. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  175. data/lib/active_record/relation/query_attribute.rb +19 -0
  176. data/lib/active_record/relation/query_methods.rb +308 -244
  177. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  178. data/lib/active_record/relation/spawn_methods.rb +4 -7
  179. data/lib/active_record/relation/where_clause.rb +174 -0
  180. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  181. data/lib/active_record/result.rb +11 -4
  182. data/lib/active_record/runtime_registry.rb +1 -1
  183. data/lib/active_record/sanitization.rb +105 -66
  184. data/lib/active_record/schema.rb +26 -22
  185. data/lib/active_record/schema_dumper.rb +54 -37
  186. data/lib/active_record/schema_migration.rb +11 -14
  187. data/lib/active_record/scoping.rb +34 -16
  188. data/lib/active_record/scoping/default.rb +28 -10
  189. data/lib/active_record/scoping/named.rb +59 -26
  190. data/lib/active_record/secure_token.rb +38 -0
  191. data/lib/active_record/serialization.rb +3 -5
  192. data/lib/active_record/statement_cache.rb +17 -15
  193. data/lib/active_record/store.rb +8 -3
  194. data/lib/active_record/suppressor.rb +58 -0
  195. data/lib/active_record/table_metadata.rb +69 -0
  196. data/lib/active_record/tasks/database_tasks.rb +66 -49
  197. data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
  198. data/lib/active_record/tasks/postgresql_database_tasks.rb +12 -3
  199. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  200. data/lib/active_record/timestamp.rb +20 -9
  201. data/lib/active_record/touch_later.rb +63 -0
  202. data/lib/active_record/transactions.rb +139 -57
  203. data/lib/active_record/type.rb +66 -17
  204. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  205. data/lib/active_record/type/date.rb +2 -45
  206. data/lib/active_record/type/date_time.rb +2 -49
  207. data/lib/active_record/type/internal/abstract_json.rb +33 -0
  208. data/lib/active_record/type/internal/timezone.rb +15 -0
  209. data/lib/active_record/type/serialized.rb +15 -14
  210. data/lib/active_record/type/time.rb +10 -16
  211. data/lib/active_record/type/type_map.rb +4 -4
  212. data/lib/active_record/type_caster.rb +7 -0
  213. data/lib/active_record/type_caster/connection.rb +29 -0
  214. data/lib/active_record/type_caster/map.rb +19 -0
  215. data/lib/active_record/validations.rb +33 -32
  216. data/lib/active_record/validations/absence.rb +23 -0
  217. data/lib/active_record/validations/associated.rb +10 -3
  218. data/lib/active_record/validations/length.rb +24 -0
  219. data/lib/active_record/validations/presence.rb +11 -12
  220. data/lib/active_record/validations/uniqueness.rb +33 -33
  221. data/lib/rails/generators/active_record/migration.rb +15 -0
  222. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -5
  223. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  224. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  225. data/lib/rails/generators/active_record/model/model_generator.rb +33 -16
  226. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  227. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  228. metadata +58 -34
  229. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  230. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  231. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  232. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  233. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  234. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  235. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  236. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  237. data/lib/active_record/type/big_integer.rb +0 -13
  238. data/lib/active_record/type/binary.rb +0 -50
  239. data/lib/active_record/type/boolean.rb +0 -31
  240. data/lib/active_record/type/decimal.rb +0 -64
  241. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  242. data/lib/active_record/type/decorator.rb +0 -14
  243. data/lib/active_record/type/float.rb +0 -19
  244. data/lib/active_record/type/integer.rb +0 -59
  245. data/lib/active_record/type/mutable.rb +0 -16
  246. data/lib/active_record/type/numeric.rb +0 -36
  247. data/lib/active_record/type/string.rb +0 -40
  248. data/lib/active_record/type/text.rb +0 -11
  249. data/lib/active_record/type/time_value.rb +0 -38
  250. data/lib/active_record/type/unsigned_integer.rb +0 -15
  251. data/lib/active_record/type/value.rb +0 -110
@@ -1,11 +1,9 @@
1
1
  require 'yaml'
2
- require 'set'
3
2
  require 'active_support/benchmarkable'
4
3
  require 'active_support/dependencies'
5
4
  require 'active_support/descendants_tracker'
6
5
  require 'active_support/time'
7
6
  require 'active_support/core_ext/module/attribute_accessors'
8
- require 'active_support/core_ext/class/delegating_attributes'
9
7
  require 'active_support/core_ext/array/extract_options'
10
8
  require 'active_support/core_ext/hash/deep_merge'
11
9
  require 'active_support/core_ext/hash/slice'
@@ -15,13 +13,13 @@ require 'active_support/core_ext/kernel/singleton_class'
15
13
  require 'active_support/core_ext/module/introspection'
16
14
  require 'active_support/core_ext/object/duplicable'
17
15
  require 'active_support/core_ext/class/subclasses'
18
- require 'arel'
19
16
  require 'active_record/attribute_decorators'
20
17
  require 'active_record/errors'
21
18
  require 'active_record/log_subscriber'
22
19
  require 'active_record/explain_subscriber'
23
20
  require 'active_record/relation/delegation'
24
21
  require 'active_record/attributes'
22
+ require 'active_record/type_caster'
25
23
 
26
24
  module ActiveRecord #:nodoc:
27
25
  # = Active Record
@@ -133,14 +131,11 @@ module ActiveRecord #:nodoc:
133
131
  # end
134
132
  # end
135
133
  #
136
- # You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
137
- # or <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
138
- #
139
134
  # == Attribute query methods
140
135
  #
141
136
  # In addition to the basic accessors, query methods are also automatically available on the Active Record object.
142
137
  # Query methods allow you to test whether an attribute value is present.
143
- # For numeric values, present is defined as non-zero.
138
+ # Additionally, when dealing with numeric values, a query method will return false if the value is zero.
144
139
  #
145
140
  # For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
146
141
  # to determine whether the user has a name:
@@ -171,10 +166,11 @@ module ActiveRecord #:nodoc:
171
166
  # <tt>Person.find_by_user_name(user_name)</tt>.
172
167
  #
173
168
  # It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
174
- # <tt>ActiveRecord::RecordNotFound</tt> error if they do not return any records,
169
+ # ActiveRecord::RecordNotFound error if they do not return any records,
175
170
  # like <tt>Person.find_by_last_name!</tt>.
176
171
  #
177
- # It's also possible to use multiple attributes in the same find by separating them with "_and_".
172
+ # It's also possible to use multiple attributes in the same <tt>find_by_</tt> by separating them with
173
+ # "_and_".
178
174
  #
179
175
  # Person.find_by(user_name: user_name, password: password)
180
176
  # Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
@@ -186,7 +182,8 @@ module ActiveRecord #:nodoc:
186
182
  # == Saving arrays, hashes, and other non-mappable objects in text columns
187
183
  #
188
184
  # Active Record can serialize any object in text columns using YAML. To do so, you must
189
- # specify this with a call to the class method +serialize+.
185
+ # specify this with a call to the class method
186
+ # {serialize}[rdoc-ref:AttributeMethods::Serialization::ClassMethods#serialize].
190
187
  # This makes it possible to store arrays, hashes, and other non-mappable objects without doing
191
188
  # any additional work.
192
189
  #
@@ -226,39 +223,47 @@ module ActiveRecord #:nodoc:
226
223
  #
227
224
  # == Connection to multiple databases in different models
228
225
  #
229
- # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved
226
+ # Connections are usually created through
227
+ # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] and retrieved
230
228
  # by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
231
229
  # connection. But you can also set a class-specific connection. For example, if Course is an
232
230
  # ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
233
231
  # and Course and all of its subclasses will use this connection instead.
234
232
  #
235
233
  # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is
236
- # a Hash indexed by the class. If a connection is requested, the retrieve_connection method
234
+ # a hash indexed by the class. If a connection is requested, the
235
+ # {ActiveRecord::Base.retrieve_connection}[rdoc-ref:ConnectionHandling#retrieve_connection] method
237
236
  # will go up the class-hierarchy until a connection is found in the connection pool.
238
237
  #
239
238
  # == Exceptions
240
239
  #
241
240
  # * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
242
- # * AdapterNotSpecified - The configuration hash used in <tt>establish_connection</tt> didn't include an
243
- # <tt>:adapter</tt> key.
244
- # * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a
245
- # non-existent adapter
241
+ # * AdapterNotSpecified - The configuration hash used in
242
+ # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection]
243
+ # didn't include an <tt>:adapter</tt> key.
244
+ # * AdapterNotFound - The <tt>:adapter</tt> key used in
245
+ # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection]
246
+ # specified a non-existent adapter
246
247
  # (or a bad spelling of an existing one).
247
248
  # * AssociationTypeMismatch - The object assigned to the association wasn't of the type
248
249
  # specified in the association definition.
249
250
  # * AttributeAssignmentError - An error occurred while doing a mass assignment through the
250
- # <tt>attributes=</tt> method.
251
+ # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
251
252
  # You can inspect the +attribute+ property of the exception object to determine which attribute
252
253
  # triggered the error.
253
- # * ConnectionNotEstablished - No connection has been established. Use <tt>establish_connection</tt>
254
- # before querying.
254
+ # * ConnectionNotEstablished - No connection has been established.
255
+ # Use {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] before querying.
255
256
  # * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
256
- # <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of
257
+ # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
258
+ # The +errors+ property of this exception contains an array of
257
259
  # AttributeAssignmentError
258
260
  # objects that should be inspected to determine which attributes triggered the errors.
259
- # * RecordInvalid - raised by save! and create! when the record is invalid.
260
- # * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
261
- # or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
261
+ # * RecordInvalid - raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
262
+ # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
263
+ # when the record is invalid.
264
+ # * RecordNotFound - No record responded to the {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method.
265
+ # Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
266
+ # Some {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] calls do not raise this exception to signal
262
267
  # nothing was found, please check its documentation for further details.
263
268
  # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
264
269
  # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
@@ -280,6 +285,7 @@ module ActiveRecord #:nodoc:
280
285
  extend Explain
281
286
  extend Enum
282
287
  extend Delegation::DelegateCache
288
+ extend CollectionCacheKey
283
289
 
284
290
  include Core
285
291
  include Persistence
@@ -306,10 +312,13 @@ module ActiveRecord #:nodoc:
306
312
  include NestedAttributes
307
313
  include Aggregations
308
314
  include Transactions
315
+ include TouchLater
309
316
  include NoTouching
310
317
  include Reflection
311
318
  include Serialization
312
319
  include Store
320
+ include SecureToken
321
+ include Suppressor
313
322
  end
314
323
 
315
324
  ActiveSupport.run_load_hooks(:active_record, Base)
@@ -1,11 +1,11 @@
1
1
  module ActiveRecord
2
- # = Active Record Callbacks
2
+ # = Active Record \Callbacks
3
3
  #
4
- # Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic
4
+ # \Callbacks are hooks into the life cycle 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
7
- # before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider
8
- # the <tt>Base#save</tt> call for a new record:
6
+ # dependent objects are deleted when {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] is called (by overwriting +before_destroy+) or
7
+ # to massage attributes before they're validated (by overwriting +before_validation+).
8
+ # As an example of the callbacks initiated, consider the {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] call for a new record:
9
9
  #
10
10
  # * (-) <tt>save</tt>
11
11
  # * (-) <tt>valid</tt>
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  # * (7) <tt>after_commit</tt>
21
21
  #
22
22
  # Also, an <tt>after_rollback</tt> callback can be configured to be triggered whenever a rollback is issued.
23
- # Check out <tt>ActiveRecord::Transactions</tt> for more details about <tt>after_commit</tt> and
23
+ # Check out ActiveRecord::Transactions for more details about <tt>after_commit</tt> and
24
24
  # <tt>after_rollback</tt>.
25
25
  #
26
26
  # Additionally, an <tt>after_touch</tt> callback is triggered whenever an
@@ -31,7 +31,7 @@ module ActiveRecord
31
31
  # are instantiated as well.
32
32
  #
33
33
  # There are nineteen callbacks in total, which give you immense power to react and prepare for each state in the
34
- # Active Record life cycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar,
34
+ # Active Record life cycle. The sequence for calling {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] for an existing record is similar,
35
35
  # except that each <tt>_create</tt> callback is replaced by the corresponding <tt>_update</tt> callback.
36
36
  #
37
37
  # Examples:
@@ -53,9 +53,9 @@ module ActiveRecord
53
53
  # end
54
54
  #
55
55
  # class Firm < ActiveRecord::Base
56
- # # Destroys the associated clients and people when the firm is destroyed
57
- # before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
58
- # before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
56
+ # # Disables access to the system, for associated clients and people when the firm is destroyed
57
+ # before_destroy { |record| Person.where(firm_id: record.id).update_all(access: 'disabled') }
58
+ # before_destroy { |record| Client.where(client_of: record.id).update_all(access: 'disabled') }
59
59
  # end
60
60
  #
61
61
  # == Inheritable callback queues
@@ -175,43 +175,30 @@ module ActiveRecord
175
175
  # end
176
176
  # end
177
177
  #
178
- # The callback macros usually accept a symbol for the method they're supposed to run, but you can also
179
- # pass a "method string", which will then be evaluated within the binding of the callback. Example:
180
- #
181
- # class Topic < ActiveRecord::Base
182
- # before_destroy 'self.class.delete_all "parent_id = #{id}"'
183
- # end
184
- #
185
- # Notice that single quotes (') are used so the <tt>#{id}</tt> part isn't evaluated until the callback
186
- # is triggered. Also note that these inline callbacks can be stacked just like the regular ones:
187
- #
188
- # class Topic < ActiveRecord::Base
189
- # before_destroy 'self.class.delete_all "parent_id = #{id}"',
190
- # 'puts "Evaluated after parents are destroyed"'
191
- # end
192
- #
193
178
  # == <tt>before_validation*</tt> returning statements
194
179
  #
195
- # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be
196
- # aborted and <tt>Base#save</tt> will return +false+. If Base#save! is called it will raise a
197
- # ActiveRecord::RecordInvalid exception. Nothing will be appended to the errors object.
180
+ # If the +before_validation+ callback throws +:abort+, the process will be
181
+ # aborted and {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will return +false+.
182
+ # If {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] is called it will raise an ActiveRecord::RecordInvalid exception.
183
+ # Nothing will be appended to the errors object.
198
184
  #
199
185
  # == Canceling callbacks
200
186
  #
201
- # If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are
202
- # cancelled.
187
+ # If a <tt>before_*</tt> callback throws +:abort+, all the later callbacks and
188
+ # the associated action are cancelled.
203
189
  # Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
204
190
  # methods on the model, which are called last.
205
191
  #
206
192
  # == Ordering callbacks
207
193
  #
208
194
  # Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+
209
- # callback (+log_children+ in this case) should be executed before the children get destroyed by the +dependent: destroy+ option.
195
+ # callback (+log_children+ in this case) should be executed before the children get destroyed by the
196
+ # <tt>dependent: :destroy</tt> option.
210
197
  #
211
198
  # Let's look at the code below:
212
199
  #
213
200
  # class Topic < ActiveRecord::Base
214
- # has_many :children, dependent: destroy
201
+ # has_many :children, dependent: :destroy
215
202
  #
216
203
  # before_destroy :log_children
217
204
  #
@@ -222,10 +209,11 @@ module ActiveRecord
222
209
  # end
223
210
  #
224
211
  # In this case, the problem is that when the +before_destroy+ callback is executed, the children are not available
225
- # because the +destroy+ callback gets executed first. You can use the +prepend+ option on the +before_destroy+ callback to avoid this.
212
+ # because the {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] callback gets executed first.
213
+ # You can use the +prepend+ option on the +before_destroy+ callback to avoid this.
226
214
  #
227
215
  # class Topic < ActiveRecord::Base
228
- # has_many :children, dependent: destroy
216
+ # has_many :children, dependent: :destroy
229
217
  #
230
218
  # before_destroy :log_children, prepend: true
231
219
  #
@@ -235,23 +223,23 @@ module ActiveRecord
235
223
  # end
236
224
  # end
237
225
  #
238
- # This way, the +before_destroy+ gets executed before the <tt>dependent: destroy</tt> is called, and the data is still available.
226
+ # This way, the +before_destroy+ gets executed before the <tt>dependent: :destroy</tt> is called, and the data is still available.
239
227
  #
240
- # == Transactions
228
+ # == \Transactions
241
229
  #
242
- # The entire callback chain of a +save+, <tt>save!</tt>, or +destroy+ call runs
243
- # within a transaction. That includes <tt>after_*</tt> hooks. If everything
244
- # goes fine a COMMIT is executed once the chain has been completed.
230
+ # The entire callback chain of a {#save}[rdoc-ref:Persistence#save], {#save!}[rdoc-ref:Persistence#save!],
231
+ # or {#destroy}[rdoc-ref:Persistence#destroy] call runs within a transaction. That includes <tt>after_*</tt> hooks.
232
+ # If everything goes fine a COMMIT is executed once the chain has been completed.
245
233
  #
246
234
  # If a <tt>before_*</tt> callback cancels the action a ROLLBACK is issued. You
247
235
  # can also trigger a ROLLBACK raising an exception in any of the callbacks,
248
236
  # including <tt>after_*</tt> hooks. Note, however, that in that case the client
249
- # needs to be aware of it because an ordinary +save+ will raise such exception
237
+ # needs to be aware of it because an ordinary {#save}[rdoc-ref:Persistence#save] will raise such exception
250
238
  # instead of quietly returning +false+.
251
239
  #
252
240
  # == Debugging callbacks
253
241
  #
254
- # The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. ActiveModel Callbacks support
242
+ # The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. Active Model \Callbacks support
255
243
  # <tt>:before</tt>, <tt>:after</tt> and <tt>:around</tt> as values for the <tt>kind</tt> property. The <tt>kind</tt> property
256
244
  # defines what part of the chain the callback runs in.
257
245
  #
@@ -277,7 +265,7 @@ module ActiveRecord
277
265
  :before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
278
266
  ]
279
267
 
280
- module ClassMethods
268
+ module ClassMethods # :nodoc:
281
269
  include ActiveModel::Callbacks
282
270
  end
283
271
 
@@ -289,7 +277,15 @@ module ActiveRecord
289
277
  end
290
278
 
291
279
  def destroy #:nodoc:
280
+ @_destroy_callback_already_called ||= false
281
+ return if @_destroy_callback_already_called
282
+ @_destroy_callback_already_called = true
292
283
  _run_destroy_callbacks { super }
284
+ rescue RecordNotDestroyed => e
285
+ @_association_destroy_exception = e
286
+ false
287
+ ensure
288
+ @_destroy_callback_already_called = false
293
289
  end
294
290
 
295
291
  def touch(*) #:nodoc:
@@ -298,7 +294,7 @@ module ActiveRecord
298
294
 
299
295
  private
300
296
 
301
- def create_or_update #:nodoc:
297
+ def create_or_update(*) #:nodoc:
302
298
  _run_save_callbacks { super }
303
299
  end
304
300
 
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  end
7
7
 
8
8
  def self.load(json)
9
- ActiveSupport::JSON.decode(json) unless json.nil?
9
+ ActiveSupport::JSON.decode(json) unless json.blank?
10
10
  end
11
11
  end
12
12
  end
@@ -8,15 +8,13 @@ module ActiveRecord
8
8
 
9
9
  def initialize(object_class = Object)
10
10
  @object_class = object_class
11
+ check_arity_of_constructor
11
12
  end
12
13
 
13
14
  def dump(obj)
14
15
  return if obj.nil?
15
16
 
16
- unless obj.is_a?(object_class)
17
- raise SerializationTypeMismatch,
18
- "Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
19
- end
17
+ assert_valid_value(obj)
20
18
  YAML.dump obj
21
19
  end
22
20
 
@@ -25,14 +23,28 @@ module ActiveRecord
25
23
  return yaml unless yaml.is_a?(String) && yaml =~ /^---/
26
24
  obj = YAML.load(yaml)
27
25
 
28
- unless obj.is_a?(object_class) || obj.nil?
29
- raise SerializationTypeMismatch,
30
- "Attribute was supposed to be a #{object_class}, but was a #{obj.class}"
31
- end
26
+ assert_valid_value(obj)
32
27
  obj ||= object_class.new if object_class != Object
33
28
 
34
29
  obj
35
30
  end
31
+
32
+ def assert_valid_value(obj)
33
+ unless obj.nil? || obj.is_a?(object_class)
34
+ raise SerializationTypeMismatch,
35
+ "Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def check_arity_of_constructor
42
+ begin
43
+ load(nil)
44
+ rescue ArgumentError
45
+ raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
46
+ end
47
+ end
36
48
  end
37
49
  end
38
50
  end
@@ -0,0 +1,50 @@
1
+ module ActiveRecord
2
+ module CollectionCacheKey
3
+
4
+ def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
5
+ query_signature = Digest::MD5.hexdigest(collection.to_sql)
6
+ key = "#{collection.model_name.cache_key}/query-#{query_signature}"
7
+
8
+ if collection.loaded?
9
+ size = collection.size
10
+ if size > 0
11
+ timestamp = collection.max_by(&timestamp_column)._read_attribute(timestamp_column)
12
+ end
13
+ else
14
+ column_type = type_for_attribute(timestamp_column.to_s)
15
+ column = "#{connection.quote_table_name(collection.table_name)}.#{connection.quote_column_name(timestamp_column)}"
16
+ select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
17
+
18
+ if collection.limit_value || collection.offset_value
19
+ query = collection.spawn
20
+ query.select_values = [column]
21
+ subquery_alias = "subquery_for_cache_key"
22
+ subquery_column = "#{subquery_alias}.#{timestamp_column}"
23
+ subquery = query.arel.as(subquery_alias)
24
+ arel = Arel::SelectManager.new(query.engine).project(select_values % subquery_column).from(subquery)
25
+ else
26
+ query = collection.unscope(:order)
27
+ query.select_values = [select_values % column]
28
+ arel = query.arel
29
+ end
30
+
31
+ result = connection.select_one(arel, nil, query.bound_attributes)
32
+
33
+ if result.blank?
34
+ size = 0
35
+ timestamp = nil
36
+ else
37
+ size = result["size"]
38
+ timestamp = column_type.deserialize(result["timestamp"])
39
+ end
40
+
41
+ end
42
+
43
+ if timestamp
44
+ "#{key}-#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
45
+ else
46
+ "#{key}-#{size}"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,8 +1,6 @@
1
1
  require 'thread'
2
- require 'thread_safe'
2
+ require 'concurrent/map'
3
3
  require 'monitor'
4
- require 'set'
5
- require 'active_support/core_ext/string/filters'
6
4
 
7
5
  module ActiveRecord
8
6
  # Raised when a connection could not be obtained within the connection
@@ -11,6 +9,13 @@ module ActiveRecord
11
9
  class ConnectionTimeoutError < ConnectionNotEstablished
12
10
  end
13
11
 
12
+ # Raised when a pool was unable to get ahold of all its connections
13
+ # to perform a "group" action such as
14
+ # {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
15
+ # or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
16
+ class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
17
+ end
18
+
14
19
  module ConnectionAdapters
15
20
  # Connection pool base class for managing Active Record database
16
21
  # connections.
@@ -33,17 +38,18 @@ module ActiveRecord
33
38
  # Connections can be obtained and used from a connection pool in several
34
39
  # ways:
35
40
  #
36
- # 1. Simply use ActiveRecord::Base.connection as with Active Record 2.1 and
41
+ # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection]
42
+ # as with Active Record 2.1 and
37
43
  # earlier (pre-connection-pooling). Eventually, when you're done with
38
44
  # the connection(s) and wish it to be returned to the pool, you call
39
- # ActiveRecord::Base.clear_active_connections!. This will be the
40
- # default behavior for Active Record when used in conjunction with
45
+ # {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
46
+ # This will be the default behavior for Active Record when used in conjunction with
41
47
  # Action Pack's request handling cycle.
42
48
  # 2. Manually check out a connection from the pool with
43
- # ActiveRecord::Base.connection_pool.checkout. You are responsible for
49
+ # {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
44
50
  # returning this connection to the pool when finished by calling
45
- # ActiveRecord::Base.connection_pool.checkin(connection).
46
- # 3. Use ActiveRecord::Base.connection_pool.with_connection(&block), which
51
+ # {ActiveRecord::Base.connection_pool.checkin(connection)}[rdoc-ref:#checkin].
52
+ # 3. Use {ActiveRecord::Base.connection_pool.with_connection(&block)}[rdoc-ref:#with_connection], which
47
53
  # obtains a connection, yields it as the sole argument to the block,
48
54
  # and returns it to the pool after the block completes.
49
55
  #
@@ -64,6 +70,15 @@ module ActiveRecord
64
70
  # connection at the end of a thread or a thread dies unexpectedly.
65
71
  # Regardless of this setting, the Reaper will be invoked before every
66
72
  # blocking wait. (Default nil, which means don't schedule the Reaper).
73
+ #
74
+ #--
75
+ # Synchronization policy:
76
+ # * all public methods can be called outside +synchronize+
77
+ # * access to these i-vars needs to be in +synchronize+:
78
+ # * @connections
79
+ # * @now_connecting
80
+ # * private methods that require being called in a +synchronize+ blocks
81
+ # are now explicitly documented
67
82
  class ConnectionPool
68
83
  # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
69
84
  # with which it shares a Monitor. But could be a generic Queue.
@@ -122,25 +137,23 @@ module ActiveRecord
122
137
  # greater than the number of threads currently waiting (that
123
138
  # is, don't jump ahead in line). Otherwise, return nil.
124
139
  #
125
- # If +timeout+ is given, block if it there is no element
140
+ # If +timeout+ is given, block if there is no element
126
141
  # available, waiting up to +timeout+ seconds for an element to
127
142
  # become available.
128
143
  #
129
144
  # Raises:
130
- # - ConnectionTimeoutError if +timeout+ is given and no element
131
- # becomes available after +timeout+ seconds,
145
+ # - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element
146
+ # becomes available within +timeout+ seconds,
132
147
  def poll(timeout = nil)
133
- synchronize do
134
- if timeout
135
- no_wait_poll || wait_poll(timeout)
136
- else
137
- no_wait_poll
138
- end
139
- end
148
+ synchronize { internal_poll(timeout) }
140
149
  end
141
150
 
142
151
  private
143
152
 
153
+ def internal_poll(timeout)
154
+ no_wait_poll || (timeout && wait_poll(timeout))
155
+ end
156
+
144
157
  def synchronize(&block)
145
158
  @lock.synchronize(&block)
146
159
  end
@@ -151,7 +164,7 @@ module ActiveRecord
151
164
  end
152
165
 
153
166
  # A thread can remove an element from the queue without
154
- # waiting if an only if the number of currently available
167
+ # waiting if and only if the number of currently available
155
168
  # connections is strictly greater than the number of waiting
156
169
  # threads.
157
170
  def can_remove_no_wait?
@@ -184,7 +197,7 @@ module ActiveRecord
184
197
 
185
198
  elapsed = Time.now - t0
186
199
  if elapsed >= timeout
187
- msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' %
200
+ msg = 'could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use' %
188
201
  [timeout, elapsed]
189
202
  raise ConnectionTimeoutError, msg
190
203
  end
@@ -194,6 +207,80 @@ module ActiveRecord
194
207
  end
195
208
  end
196
209
 
210
+ # Adds the ability to turn a basic fair FIFO queue into one
211
+ # biased to some thread.
212
+ module BiasableQueue # :nodoc:
213
+ class BiasedConditionVariable # :nodoc:
214
+ # semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+,
215
+ # +signal+ and +wait+ methods are only called while holding a lock
216
+ def initialize(lock, other_cond, preferred_thread)
217
+ @real_cond = lock.new_cond
218
+ @other_cond = other_cond
219
+ @preferred_thread = preferred_thread
220
+ @num_waiting_on_real_cond = 0
221
+ end
222
+
223
+ def broadcast
224
+ broadcast_on_biased
225
+ @other_cond.broadcast
226
+ end
227
+
228
+ def broadcast_on_biased
229
+ @num_waiting_on_real_cond = 0
230
+ @real_cond.broadcast
231
+ end
232
+
233
+ def signal
234
+ if @num_waiting_on_real_cond > 0
235
+ @num_waiting_on_real_cond -= 1
236
+ @real_cond
237
+ else
238
+ @other_cond
239
+ end.signal
240
+ end
241
+
242
+ def wait(timeout)
243
+ if Thread.current == @preferred_thread
244
+ @num_waiting_on_real_cond += 1
245
+ @real_cond
246
+ else
247
+ @other_cond
248
+ end.wait(timeout)
249
+ end
250
+ end
251
+
252
+ def with_a_bias_for(thread)
253
+ previous_cond = nil
254
+ new_cond = nil
255
+ synchronize do
256
+ previous_cond = @cond
257
+ @cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread)
258
+ end
259
+ yield
260
+ ensure
261
+ synchronize do
262
+ @cond = previous_cond if previous_cond
263
+ new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers
264
+ end
265
+ end
266
+ end
267
+
268
+ # Connections must be leased while holding the main pool mutex. This is
269
+ # an internal subclass that also +.leases+ returned connections while
270
+ # still in queue's critical section (queue synchronizes with the same
271
+ # +@lock+ as the main pool) so that a returned connection is already
272
+ # leased and there is no need to re-enter synchronized block.
273
+ class ConnectionLeasingQueue < Queue # :nodoc:
274
+ include BiasableQueue
275
+
276
+ private
277
+ def internal_poll(timeout)
278
+ conn = super
279
+ conn.lease if conn
280
+ conn
281
+ end
282
+ end
283
+
197
284
  # Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
198
285
  # A reaper instantiated with a nil frequency will never reap the
199
286
  # connection pool.
@@ -220,8 +307,9 @@ module ActiveRecord
220
307
  end
221
308
 
222
309
  include MonitorMixin
310
+ include QueryCache::ConnectionPoolConfiguration
223
311
 
224
- attr_accessor :automatic_reconnect, :checkout_timeout
312
+ attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
225
313
  attr_reader :spec, :connections, :size, :reaper
226
314
 
227
315
  # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
@@ -242,56 +330,74 @@ module ActiveRecord
242
330
  # default max pool size to 5
243
331
  @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
244
332
 
245
- # The cache of reserved connections mapped to threads
246
- @reserved_connections = ThreadSafe::Cache.new(:initial_capacity => @size)
333
+ # The cache of threads mapped to reserved connections, the sole purpose
334
+ # of the cache is to speed-up +connection+ method, it is not the authoritative
335
+ # registry of which thread owns which connection, that is tracked by
336
+ # +connection.owner+ attr on each +connection+ instance.
337
+ # The invariant works like this: if there is mapping of <tt>thread => conn</tt>,
338
+ # then that +thread+ does indeed own that +conn+, however an absence of a such
339
+ # mapping does not mean that the +thread+ doesn't own the said connection, in
340
+ # that case +conn.owner+ attr should be consulted.
341
+ # Access and modification of +@thread_cached_conns+ does not require
342
+ # synchronization.
343
+ @thread_cached_conns = Concurrent::Map.new(:initial_capacity => @size)
247
344
 
248
345
  @connections = []
249
346
  @automatic_reconnect = true
250
347
 
251
- @available = Queue.new self
348
+ # Connection pool allows for concurrent (outside the main +synchronize+ section)
349
+ # establishment of new connections. This variable tracks the number of threads
350
+ # currently in the process of independently establishing connections to the DB.
351
+ @now_connecting = 0
352
+
353
+ @threads_blocking_new_connections = 0
354
+
355
+ @available = ConnectionLeasingQueue.new self
252
356
  end
253
357
 
254
358
  # Retrieve the connection associated with the current thread, or call
255
359
  # #checkout to obtain one if necessary.
256
360
  #
257
361
  # #connection can be called any number of times; the connection is
258
- # held in a hash keyed by the thread id.
362
+ # held in a cache keyed by a thread.
259
363
  def connection
260
- # this is correctly done double-checked locking
261
- # (ThreadSafe::Cache's lookups have volatile semantics)
262
- @reserved_connections[current_connection_id] || synchronize do
263
- @reserved_connections[current_connection_id] ||= checkout
264
- end
364
+ @thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout
265
365
  end
266
366
 
267
367
  # Is there an open connection that is being used for the current thread?
368
+ #
369
+ # This method only works for connections that have been obtained through
370
+ # #connection or #with_connection methods, connections obtained through
371
+ # #checkout will not be detected by #active_connection?
268
372
  def active_connection?
269
- synchronize do
270
- @reserved_connections.fetch(current_connection_id) {
271
- return false
272
- }.in_use?
273
- end
373
+ @thread_cached_conns[connection_cache_key(Thread.current)]
274
374
  end
275
375
 
276
376
  # Signal that the thread is finished with the current connection.
277
377
  # #release_connection releases the connection-thread association
278
378
  # and returns the connection to the pool.
279
- def release_connection(with_id = current_connection_id)
280
- synchronize do
281
- conn = @reserved_connections.delete(with_id)
282
- checkin conn if conn
379
+ #
380
+ # This method only works for connections that have been obtained through
381
+ # #connection or #with_connection methods, connections obtained through
382
+ # #checkout will not be automatically released.
383
+ def release_connection(owner_thread = Thread.current)
384
+ if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
385
+ checkin conn
283
386
  end
284
387
  end
285
388
 
286
- # If a connection already exists yield it to the block. If no connection
389
+ # If a connection obtained through #connection or #with_connection methods
390
+ # already exists yield it to the block. If no such connection
287
391
  # exists checkout a connection, yield it to the block, and checkin the
288
392
  # connection when finished.
289
393
  def with_connection
290
- connection_id = current_connection_id
291
- fresh_connection = true unless active_connection?
292
- yield connection
394
+ unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
395
+ conn = connection
396
+ fresh_connection = true
397
+ end
398
+ yield conn
293
399
  ensure
294
- release_connection(connection_id) if fresh_connection
400
+ release_connection if fresh_connection
295
401
  end
296
402
 
297
403
  # Returns true if a connection has already been opened.
@@ -300,36 +406,72 @@ module ActiveRecord
300
406
  end
301
407
 
302
408
  # Disconnects all connections in the pool, and clears the pool.
303
- def disconnect!
304
- synchronize do
305
- @reserved_connections.clear
306
- @connections.each do |conn|
307
- checkin conn
308
- conn.disconnect!
409
+ #
410
+ # Raises:
411
+ # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
412
+ # connections in the pool within a timeout interval (default duration is
413
+ # <tt>spec.config[:checkout_timeout] * 2</tt> seconds).
414
+ def disconnect(raise_on_acquisition_timeout = true)
415
+ with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
416
+ synchronize do
417
+ @connections.each do |conn|
418
+ if conn.in_use?
419
+ conn.steal!
420
+ checkin conn
421
+ end
422
+ conn.disconnect!
423
+ end
424
+ @connections = []
425
+ @available.clear
309
426
  end
310
- @connections = []
311
- @available.clear
312
427
  end
313
428
  end
314
429
 
315
- # Clears the cache which maps classes.
316
- def clear_reloadable_connections!
317
- synchronize do
318
- @reserved_connections.clear
319
- @connections.each do |conn|
320
- checkin conn
321
- conn.disconnect! if conn.requires_reloading?
322
- end
323
- @connections.delete_if do |conn|
324
- conn.requires_reloading?
325
- end
326
- @available.clear
327
- @connections.each do |conn|
328
- @available.add conn
430
+ # Disconnects all connections in the pool, and clears the pool.
431
+ #
432
+ # The pool first tries to gain ownership of all connections, if unable to
433
+ # do so within a timeout interval (default duration is
434
+ # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool is forcefully
435
+ # disconnected without any regard for other connection owning threads.
436
+ def disconnect!
437
+ disconnect(false)
438
+ end
439
+
440
+ # Clears the cache which maps classes and re-connects connections that
441
+ # require reloading.
442
+ #
443
+ # Raises:
444
+ # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
445
+ # connections in the pool within a timeout interval (default duration is
446
+ # <tt>spec.config[:checkout_timeout] * 2</tt> seconds).
447
+ def clear_reloadable_connections(raise_on_acquisition_timeout = true)
448
+ with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
449
+ synchronize do
450
+ @connections.each do |conn|
451
+ if conn.in_use?
452
+ conn.steal!
453
+ checkin conn
454
+ end
455
+ conn.disconnect! if conn.requires_reloading?
456
+ end
457
+ @connections.delete_if(&:requires_reloading?)
458
+ @available.clear
329
459
  end
330
460
  end
331
461
  end
332
462
 
463
+ # Clears the cache which maps classes and re-connects connections that
464
+ # require reloading.
465
+ #
466
+ # The pool first tries to gain ownership of all connections, if unable to
467
+ # do so within a timeout interval (default duration is
468
+ # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool forcefully
469
+ # clears the cache and reloads connections without any regard for other
470
+ # connection owning threads.
471
+ def clear_reloadable_connections!
472
+ clear_reloadable_connections(false)
473
+ end
474
+
333
475
  # Check-out a database connection from the pool, indicating that you want
334
476
  # to use it. You should call #checkin when you no longer need this.
335
477
  #
@@ -343,70 +485,212 @@ module ActiveRecord
343
485
  # Returns: an AbstractAdapter object.
344
486
  #
345
487
  # Raises:
346
- # - ConnectionTimeoutError: no connection can be obtained from the pool.
347
- def checkout
348
- synchronize do
349
- conn = acquire_connection
350
- conn.lease
351
- checkout_and_verify(conn)
352
- end
488
+ # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
489
+ def checkout(checkout_timeout = @checkout_timeout)
490
+ checkout_and_verify(acquire_connection(checkout_timeout))
353
491
  end
354
492
 
355
493
  # Check-in a database connection back into the pool, indicating that you
356
494
  # no longer need this connection.
357
495
  #
358
496
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
359
- # calling +checkout+ on this pool.
497
+ # calling #checkout on this pool.
360
498
  def checkin(conn)
361
499
  synchronize do
362
- owner = conn.owner
500
+ remove_connection_from_thread_cache conn
363
501
 
364
502
  conn._run_checkin_callbacks do
365
503
  conn.expire
366
504
  end
367
505
 
368
- release conn, owner
369
-
370
506
  @available.add conn
371
507
  end
372
508
  end
373
509
 
374
- # Remove a connection from the connection pool. The connection will
510
+ # Remove a connection from the connection pool. The connection will
375
511
  # remain open and active but will no longer be managed by this pool.
376
512
  def remove(conn)
513
+ needs_new_connection = false
514
+
377
515
  synchronize do
516
+ remove_connection_from_thread_cache conn
517
+
378
518
  @connections.delete conn
379
519
  @available.delete conn
380
520
 
381
- release conn, conn.owner
382
-
383
- @available.add checkout_new_connection if @available.any_waiting?
521
+ # @available.any_waiting? => true means that prior to removing this
522
+ # conn, the pool was at its max size (@connections.size == @size)
523
+ # this would mean that any threads stuck waiting in the queue wouldn't
524
+ # know they could checkout_new_connection, so let's do it for them.
525
+ # Because condition-wait loop is encapsulated in the Queue class
526
+ # (that in turn is oblivious to ConnectionPool implementation), threads
527
+ # that are "stuck" there are helpless, they have no way of creating
528
+ # new connections and are completely reliant on us feeding available
529
+ # connections into the Queue.
530
+ needs_new_connection = @available.any_waiting?
384
531
  end
532
+
533
+ # This is intentionally done outside of the synchronized section as we
534
+ # would like not to hold the main mutex while checking out new connections,
535
+ # thus there is some chance that needs_new_connection information is now
536
+ # stale, we can live with that (bulk_make_new_connections will make
537
+ # sure not to exceed the pool's @size limit).
538
+ bulk_make_new_connections(1) if needs_new_connection
385
539
  end
386
540
 
387
- # Recover lost connections for the pool. A lost connection can occur if
541
+ # Recover lost connections for the pool. A lost connection can occur if
388
542
  # a programmer forgets to checkin a connection at the end of a thread
389
543
  # or a thread dies unexpectedly.
390
544
  def reap
391
545
  stale_connections = synchronize do
392
546
  @connections.select do |conn|
393
547
  conn.in_use? && !conn.owner.alive?
548
+ end.each do |conn|
549
+ conn.steal!
394
550
  end
395
551
  end
396
552
 
397
553
  stale_connections.each do |conn|
398
- synchronize do
399
- if conn.active?
400
- conn.reset!
401
- checkin conn
402
- else
403
- remove conn
404
- end
554
+ if conn.active?
555
+ conn.reset!
556
+ checkin conn
557
+ else
558
+ remove conn
405
559
  end
406
560
  end
407
561
  end
408
562
 
563
+ def num_waiting_in_queue # :nodoc:
564
+ @available.num_waiting
565
+ end
566
+
409
567
  private
568
+ #--
569
+ # this is unfortunately not concurrent
570
+ def bulk_make_new_connections(num_new_conns_needed)
571
+ num_new_conns_needed.times do
572
+ # try_to_checkout_new_connection will not exceed pool's @size limit
573
+ if new_conn = try_to_checkout_new_connection
574
+ # make the new_conn available to the starving threads stuck @available Queue
575
+ checkin(new_conn)
576
+ end
577
+ end
578
+ end
579
+
580
+ #--
581
+ # From the discussion on GitHub:
582
+ # https://github.com/rails/rails/pull/14938#commitcomment-6601951
583
+ # This hook-in method allows for easier monkey-patching fixes needed by
584
+ # JRuby users that use Fibers.
585
+ def connection_cache_key(thread)
586
+ thread
587
+ end
588
+
589
+ # Take control of all existing connections so a "group" action such as
590
+ # reload/disconnect can be performed safely. It is no longer enough to
591
+ # wrap it in +synchronize+ because some pool's actions are allowed
592
+ # to be performed outside of the main +synchronize+ block.
593
+ def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
594
+ with_new_connections_blocked do
595
+ attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
596
+ yield
597
+ end
598
+ end
599
+
600
+ def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
601
+ collected_conns = synchronize do
602
+ # account for our own connections
603
+ @connections.select {|conn| conn.owner == Thread.current}
604
+ end
605
+
606
+ newly_checked_out = []
607
+ timeout_time = Time.now + (@checkout_timeout * 2)
608
+
609
+ @available.with_a_bias_for(Thread.current) do
610
+ while true
611
+ synchronize do
612
+ return if collected_conns.size == @connections.size && @now_connecting == 0
613
+ remaining_timeout = timeout_time - Time.now
614
+ remaining_timeout = 0 if remaining_timeout < 0
615
+ conn = checkout_for_exclusive_access(remaining_timeout)
616
+ collected_conns << conn
617
+ newly_checked_out << conn
618
+ end
619
+ end
620
+ end
621
+ rescue ExclusiveConnectionTimeoutError
622
+ # <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any
623
+ # timeouts and are expected to just give up: we've obtained as many connections
624
+ # as possible, note that in a case like that we don't return any of the
625
+ # +newly_checked_out+ connections.
626
+
627
+ if raise_on_acquisition_timeout
628
+ release_newly_checked_out = true
629
+ raise
630
+ end
631
+ rescue Exception # if something else went wrong
632
+ # this can't be a "naked" rescue, because we have should return conns
633
+ # even for non-StandardErrors
634
+ release_newly_checked_out = true
635
+ raise
636
+ ensure
637
+ if release_newly_checked_out && newly_checked_out
638
+ # releasing only those conns that were checked out in this method, conns
639
+ # checked outside this method (before it was called) are not for us to release
640
+ newly_checked_out.each {|conn| checkin(conn)}
641
+ end
642
+ end
643
+
644
+ #--
645
+ # Must be called in a synchronize block.
646
+ def checkout_for_exclusive_access(checkout_timeout)
647
+ checkout(checkout_timeout)
648
+ rescue ConnectionTimeoutError
649
+ # this block can't be easily moved into attempt_to_checkout_all_existing_connections's
650
+ # rescue block, because doing so would put it outside of synchronize section, without
651
+ # being in a critical section thread_report might become inaccurate
652
+ msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds"
653
+
654
+ thread_report = []
655
+ @connections.each do |conn|
656
+ unless conn.owner == Thread.current
657
+ thread_report << "#{conn} is owned by #{conn.owner}"
658
+ end
659
+ end
660
+
661
+ msg << " (#{thread_report.join(', ')})" if thread_report.any?
662
+
663
+ raise ExclusiveConnectionTimeoutError, msg
664
+ end
665
+
666
+ def with_new_connections_blocked
667
+ synchronize do
668
+ @threads_blocking_new_connections += 1
669
+ end
670
+
671
+ yield
672
+ ensure
673
+ num_new_conns_required = 0
674
+
675
+ synchronize do
676
+ @threads_blocking_new_connections -= 1
677
+
678
+ if @threads_blocking_new_connections.zero?
679
+ @available.clear
680
+
681
+ num_new_conns_required = num_waiting_in_queue
682
+
683
+ @connections.each do |conn|
684
+ next if conn.in_use?
685
+
686
+ @available.add conn
687
+ num_new_conns_required -= 1
688
+ end
689
+ end
690
+ end
691
+
692
+ bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
693
+ end
410
694
 
411
695
  # Acquire a connection by one of 1) immediately removing one
412
696
  # from the queue of available connections, 2) creating a new
@@ -414,41 +698,79 @@ module ActiveRecord
414
698
  # queue for a connection to become available.
415
699
  #
416
700
  # Raises:
417
- # - ConnectionTimeoutError if a connection could not be acquired
418
- def acquire_connection
419
- if conn = @available.poll
701
+ # - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
702
+ #
703
+ #--
704
+ # Implementation detail: the connection returned by +acquire_connection+
705
+ # will already be "+connection.lease+ -ed" to the current thread.
706
+ def acquire_connection(checkout_timeout)
707
+ # NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to
708
+ # +conn.lease+ the returned connection (and to do this in a +synchronized+
709
+ # section), this is not the cleanest implementation, as ideally we would
710
+ # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+
711
+ # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
712
+ # of the said methods and avoid an additional +synchronize+ overhead.
713
+ if conn = @available.poll || try_to_checkout_new_connection
420
714
  conn
421
- elsif @connections.size < @size
422
- checkout_new_connection
423
715
  else
424
716
  reap
425
- @available.poll(@checkout_timeout)
717
+ @available.poll(checkout_timeout)
426
718
  end
427
719
  end
428
720
 
429
- def release(conn, owner)
430
- thread_id = owner.object_id
431
-
432
- if @reserved_connections[thread_id] == conn
433
- @reserved_connections.delete thread_id
434
- end
721
+ #--
722
+ # if owner_thread param is omitted, this must be called in synchronize block
723
+ def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
724
+ @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
435
725
  end
726
+ alias_method :release, :remove_connection_from_thread_cache
436
727
 
437
728
  def new_connection
438
- Base.send(spec.adapter_method, spec.config)
729
+ Base.send(spec.adapter_method, spec.config).tap do |conn|
730
+ conn.schema_cache = schema_cache.dup if schema_cache
731
+ end
732
+ end
733
+
734
+ # If the pool is not at a +@size+ limit, establish new connection. Connecting
735
+ # to the DB is done outside main synchronized section.
736
+ #--
737
+ # Implementation constraint: a newly established connection returned by this
738
+ # method must be in the +.leased+ state.
739
+ def try_to_checkout_new_connection
740
+ # first in synchronized section check if establishing new conns is allowed
741
+ # and increment @now_connecting, to prevent overstepping this pool's @size
742
+ # constraint
743
+ do_checkout = synchronize do
744
+ if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
745
+ @now_connecting += 1
746
+ end
747
+ end
748
+ if do_checkout
749
+ begin
750
+ # if successfully incremented @now_connecting establish new connection
751
+ # outside of synchronized section
752
+ conn = checkout_new_connection
753
+ ensure
754
+ synchronize do
755
+ if conn
756
+ adopt_connection(conn)
757
+ # returned conn needs to be already leased
758
+ conn.lease
759
+ end
760
+ @now_connecting -= 1
761
+ end
762
+ end
763
+ end
439
764
  end
440
765
 
441
- def current_connection_id #:nodoc:
442
- Base.connection_id ||= Thread.current.object_id
766
+ def adopt_connection(conn)
767
+ conn.pool = self
768
+ @connections << conn
443
769
  end
444
770
 
445
771
  def checkout_new_connection
446
772
  raise ConnectionNotEstablished unless @automatic_reconnect
447
-
448
- c = new_connection
449
- c.pool = self
450
- @connections << c
451
- c
773
+ new_connection
452
774
  end
453
775
 
454
776
  def checkout_and_verify(c)
@@ -464,8 +786,7 @@ module ActiveRecord
464
786
  end
465
787
 
466
788
  # ConnectionHandler is a collection of ConnectionPool objects. It is used
467
- # for keeping separate connection pools for Active Record models that connect
468
- # to different databases.
789
+ # for keeping separate connection pools that connect to different databases.
469
790
  #
470
791
  # For example, suppose that you have 5 models, with the following hierarchy:
471
792
  #
@@ -507,36 +828,25 @@ module ActiveRecord
507
828
  # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
508
829
  # All Active Record models use this handler to determine the connection pool that they
509
830
  # should use.
831
+ #
832
+ # The ConnectionHandler class is not coupled with the Active models, as it has no knowlodge
833
+ # about the model. The model, needs to pass a specification name to the handler,
834
+ # in order to lookup the correct connection pool.
510
835
  class ConnectionHandler
511
836
  def initialize
512
- # These caches are keyed by klass.name, NOT klass. Keying them by klass
513
- # alone would lead to memory leaks in development mode as all previous
514
- # instances of the class would stay in memory.
515
- @owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
516
- h[k] = ThreadSafe::Cache.new(:initial_capacity => 2)
517
- end
518
- @class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
519
- h[k] = ThreadSafe::Cache.new
837
+ # These caches are keyed by spec.name (ConnectionSpecification#name).
838
+ @owner_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k|
839
+ h[k] = Concurrent::Map.new(:initial_capacity => 2)
520
840
  end
521
841
  end
522
842
 
523
843
  def connection_pool_list
524
844
  owner_to_pool.values.compact
525
845
  end
846
+ alias :connection_pools :connection_pool_list
526
847
 
527
- def connection_pools
528
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
529
- In the next release, this will return the same as `#connection_pool_list`.
530
- (An array of pools, rather than a hash mapping specs to pools.)
531
- MSG
532
-
533
- Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
534
- end
535
-
536
- def establish_connection(owner, spec)
537
- @class_to_pool.clear
538
- raise RuntimeError, "Anonymous class is not allowed." unless owner.name
539
- owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
848
+ def establish_connection(spec)
849
+ owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
540
850
  end
541
851
 
542
852
  # Returns true if there are any active connections among the connection
@@ -553,6 +863,8 @@ module ActiveRecord
553
863
  end
554
864
 
555
865
  # Clears the cache which maps classes.
866
+ #
867
+ # See ConnectionPool#clear_reloadable_connections! for details.
556
868
  def clear_reloadable_connections!
557
869
  connection_pool_list.each(&:clear_reloadable_connections!)
558
870
  end
@@ -565,18 +877,18 @@ module ActiveRecord
565
877
  # active or defined connection: if it is the latter, it will be
566
878
  # opened and set as the active connection for the class it was defined
567
879
  # for (not necessarily the current class).
568
- def retrieve_connection(klass) #:nodoc:
569
- pool = retrieve_connection_pool(klass)
570
- raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
880
+ def retrieve_connection(spec_name) #:nodoc:
881
+ pool = retrieve_connection_pool(spec_name)
882
+ raise ConnectionNotEstablished, "No connection pool with id #{spec_name} found." unless pool
571
883
  conn = pool.connection
572
- raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
884
+ raise ConnectionNotEstablished, "No connection for #{spec_name} in connection pool" unless conn
573
885
  conn
574
886
  end
575
887
 
576
888
  # Returns true if a connection that's accessible to this class has
577
889
  # already been opened.
578
- def connected?(klass)
579
- conn = retrieve_connection_pool(klass)
890
+ def connected?(spec_name)
891
+ conn = retrieve_connection_pool(spec_name)
580
892
  conn && conn.connected?
581
893
  end
582
894
 
@@ -584,9 +896,8 @@ module ActiveRecord
584
896
  # connection and the defined connection (if they exist). The result
585
897
  # can be used as an argument for establish_connection, for easily
586
898
  # re-establishing the connection.
587
- def remove_connection(owner)
588
- if pool = owner_to_pool.delete(owner.name)
589
- @class_to_pool.clear
899
+ def remove_connection(spec_name)
900
+ if pool = owner_to_pool.delete(spec_name)
590
901
  pool.automatic_reconnect = false
591
902
  pool.disconnect!
592
903
  pool.spec.config
@@ -602,63 +913,30 @@ module ActiveRecord
602
913
  # #fetch is significantly slower than #[]. So in the nil case, no caching will
603
914
  # take place, but that's ok since the nil case is not the common one that we wish
604
915
  # to optimise for.
605
- def retrieve_connection_pool(klass)
606
- class_to_pool[klass.name] ||= begin
607
- until pool = pool_for(klass)
608
- klass = klass.superclass
609
- break unless klass <= Base
610
- end
611
-
612
- class_to_pool[klass.name] = pool
613
- end
614
- end
615
-
616
- private
617
-
618
- def owner_to_pool
619
- @owner_to_pool[Process.pid]
620
- end
621
-
622
- def class_to_pool
623
- @class_to_pool[Process.pid]
624
- end
625
-
626
- def pool_for(owner)
627
- owner_to_pool.fetch(owner.name) {
628
- if ancestor_pool = pool_from_any_process_for(owner)
916
+ def retrieve_connection_pool(spec_name)
917
+ owner_to_pool.fetch(spec_name) do
918
+ if ancestor_pool = pool_from_any_process_for(spec_name)
629
919
  # A connection was established in an ancestor process that must have
630
920
  # subsequently forked. We can't reuse the connection, but we can copy
631
921
  # the specification and establish a new connection with it.
632
- establish_connection owner, ancestor_pool.spec
922
+ establish_connection(ancestor_pool.spec).tap do |pool|
923
+ pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
924
+ end
633
925
  else
634
- owner_to_pool[owner.name] = nil
926
+ owner_to_pool[spec_name] = nil
635
927
  end
636
- }
928
+ end
637
929
  end
638
930
 
639
- def pool_from_any_process_for(owner)
640
- owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[owner.name] }
641
- owner_to_pool && owner_to_pool[owner.name]
642
- end
643
- end
931
+ private
644
932
 
645
- class ConnectionManagement
646
- def initialize(app)
647
- @app = app
933
+ def owner_to_pool
934
+ @owner_to_pool[Process.pid]
648
935
  end
649
936
 
650
- def call(env)
651
- testing = env['rack.test']
652
-
653
- response = @app.call(env)
654
- response[2] = ::Rack::BodyProxy.new(response[2]) do
655
- ActiveRecord::Base.clear_active_connections! unless testing
656
- end
657
-
658
- response
659
- rescue Exception
660
- ActiveRecord::Base.clear_active_connections! unless testing
661
- raise
937
+ def pool_from_any_process_for(spec_name)
938
+ owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[spec_name] }
939
+ owner_to_pool && owner_to_pool[spec_name]
662
940
  end
663
941
  end
664
942
  end