activerecord 5.2.8.1 → 6.1.6.1

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 (316) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1255 -596
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +9 -8
  7. data/lib/active_record/association_relation.rb +30 -10
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +100 -41
  10. data/lib/active_record/associations/association_scope.rb +23 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +55 -48
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -6
  13. data/lib/active_record/associations/builder/association.rb +45 -22
  14. data/lib/active_record/associations/builder/belongs_to.rb +29 -59
  15. data/lib/active_record/associations/builder/collection_association.rb +8 -17
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -2
  18. data/lib/active_record/associations/builder/has_one.rb +33 -2
  19. data/lib/active_record/associations/builder/singular_association.rb +3 -1
  20. data/lib/active_record/associations/collection_association.rb +44 -34
  21. data/lib/active_record/associations/collection_proxy.rb +25 -21
  22. data/lib/active_record/associations/foreign_association.rb +20 -0
  23. data/lib/active_record/associations/has_many_association.rb +26 -13
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -18
  25. data/lib/active_record/associations/has_one_association.rb +43 -31
  26. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  27. data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
  28. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  29. data/lib/active_record/associations/join_dependency.rb +91 -60
  30. data/lib/active_record/associations/preloader/association.rb +69 -43
  31. data/lib/active_record/associations/preloader/through_association.rb +49 -40
  32. data/lib/active_record/associations/preloader.rb +47 -34
  33. data/lib/active_record/associations/singular_association.rb +3 -17
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/associations.rb +137 -25
  36. data/lib/active_record/attribute_assignment.rb +17 -19
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -7
  38. data/lib/active_record/attribute_methods/dirty.rb +101 -40
  39. data/lib/active_record/attribute_methods/primary_key.rb +20 -25
  40. data/lib/active_record/attribute_methods/query.rb +4 -8
  41. data/lib/active_record/attribute_methods/read.rb +14 -56
  42. data/lib/active_record/attribute_methods/serialization.rb +12 -7
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  44. data/lib/active_record/attribute_methods/write.rb +18 -34
  45. data/lib/active_record/attribute_methods.rb +81 -143
  46. data/lib/active_record/attributes.rb +46 -9
  47. data/lib/active_record/autosave_association.rb +57 -42
  48. data/lib/active_record/base.rb +4 -17
  49. data/lib/active_record/callbacks.rb +158 -43
  50. data/lib/active_record/coders/yaml_column.rb +1 -2
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +272 -130
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -36
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -146
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -14
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +98 -47
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -110
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +211 -90
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +385 -144
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +167 -69
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +229 -99
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +243 -275
  64. data/lib/active_record/connection_adapters/column.rb +30 -12
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  67. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  68. data/lib/active_record/connection_adapters/mysql/database_statements.rb +88 -32
  69. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  70. data/lib/active_record/connection_adapters/mysql/quoting.rb +59 -7
  71. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  72. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  73. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +18 -7
  74. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +142 -19
  75. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +53 -18
  77. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  78. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  79. data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
  80. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -54
  81. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  93. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  94. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  96. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  97. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  98. data/lib/active_record/connection_adapters/postgresql/quoting.rb +47 -10
  99. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  100. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +19 -4
  101. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  102. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  103. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +120 -100
  104. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  105. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  106. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -120
  107. data/lib/active_record/connection_adapters/schema_cache.rb +159 -21
  108. data/lib/active_record/connection_adapters/sql_type_metadata.rb +17 -6
  109. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +146 -0
  110. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
  111. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  112. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +77 -13
  113. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +174 -186
  114. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  115. data/lib/active_record/connection_adapters.rb +52 -0
  116. data/lib/active_record/connection_handling.rb +293 -33
  117. data/lib/active_record/core.rb +333 -98
  118. data/lib/active_record/counter_cache.rb +8 -30
  119. data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -0
  120. data/lib/active_record/database_configurations/database_config.rb +80 -0
  121. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  122. data/lib/active_record/database_configurations/url_config.rb +53 -0
  123. data/lib/active_record/database_configurations.rb +273 -0
  124. data/lib/active_record/delegated_type.rb +209 -0
  125. data/lib/active_record/destroy_association_async_job.rb +36 -0
  126. data/lib/active_record/dynamic_matchers.rb +3 -4
  127. data/lib/active_record/enum.rb +108 -36
  128. data/lib/active_record/errors.rb +62 -19
  129. data/lib/active_record/explain.rb +10 -6
  130. data/lib/active_record/explain_subscriber.rb +1 -1
  131. data/lib/active_record/fixture_set/file.rb +10 -17
  132. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  133. data/lib/active_record/fixture_set/render_context.rb +17 -0
  134. data/lib/active_record/fixture_set/table_row.rb +152 -0
  135. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  136. data/lib/active_record/fixtures.rb +200 -481
  137. data/lib/active_record/gem_version.rb +3 -3
  138. data/lib/active_record/inheritance.rb +53 -24
  139. data/lib/active_record/insert_all.rb +212 -0
  140. data/lib/active_record/integration.rb +67 -17
  141. data/lib/active_record/internal_metadata.rb +28 -9
  142. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  143. data/lib/active_record/locking/optimistic.rb +37 -23
  144. data/lib/active_record/locking/pessimistic.rb +9 -5
  145. data/lib/active_record/log_subscriber.rb +35 -35
  146. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  147. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  148. data/lib/active_record/middleware/database_selector.rb +77 -0
  149. data/lib/active_record/migration/command_recorder.rb +96 -44
  150. data/lib/active_record/migration/compatibility.rb +145 -64
  151. data/lib/active_record/migration/join_table.rb +0 -1
  152. data/lib/active_record/migration.rb +206 -157
  153. data/lib/active_record/model_schema.rb +148 -22
  154. data/lib/active_record/nested_attributes.rb +4 -7
  155. data/lib/active_record/no_touching.rb +8 -1
  156. data/lib/active_record/null_relation.rb +0 -1
  157. data/lib/active_record/persistence.rb +267 -59
  158. data/lib/active_record/query_cache.rb +21 -4
  159. data/lib/active_record/querying.rb +40 -23
  160. data/lib/active_record/railtie.rb +116 -59
  161. data/lib/active_record/railties/console_sandbox.rb +2 -4
  162. data/lib/active_record/railties/controller_runtime.rb +30 -35
  163. data/lib/active_record/railties/databases.rake +411 -80
  164. data/lib/active_record/readonly_attributes.rb +4 -0
  165. data/lib/active_record/reflection.rb +109 -93
  166. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  167. data/lib/active_record/relation/batches.rb +44 -35
  168. data/lib/active_record/relation/calculations.rb +157 -90
  169. data/lib/active_record/relation/delegation.rb +35 -50
  170. data/lib/active_record/relation/finder_methods.rb +64 -39
  171. data/lib/active_record/relation/from_clause.rb +5 -1
  172. data/lib/active_record/relation/merger.rb +32 -40
  173. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  174. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  179. data/lib/active_record/relation/predicate_builder.rb +62 -45
  180. data/lib/active_record/relation/query_attribute.rb +13 -8
  181. data/lib/active_record/relation/query_methods.rb +476 -187
  182. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  183. data/lib/active_record/relation/spawn_methods.rb +9 -9
  184. data/lib/active_record/relation/where_clause.rb +115 -62
  185. data/lib/active_record/relation.rb +379 -115
  186. data/lib/active_record/result.rb +64 -38
  187. data/lib/active_record/runtime_registry.rb +2 -2
  188. data/lib/active_record/sanitization.rb +22 -41
  189. data/lib/active_record/schema.rb +2 -11
  190. data/lib/active_record/schema_dumper.rb +54 -9
  191. data/lib/active_record/schema_migration.rb +7 -9
  192. data/lib/active_record/scoping/default.rb +4 -8
  193. data/lib/active_record/scoping/named.rb +17 -24
  194. data/lib/active_record/scoping.rb +8 -9
  195. data/lib/active_record/secure_token.rb +16 -8
  196. data/lib/active_record/serialization.rb +5 -3
  197. data/lib/active_record/signed_id.rb +116 -0
  198. data/lib/active_record/statement_cache.rb +49 -6
  199. data/lib/active_record/store.rb +88 -9
  200. data/lib/active_record/suppressor.rb +2 -2
  201. data/lib/active_record/table_metadata.rb +42 -43
  202. data/lib/active_record/tasks/database_tasks.rb +277 -81
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +27 -32
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  206. data/lib/active_record/test_databases.rb +24 -0
  207. data/lib/active_record/test_fixtures.rb +287 -0
  208. data/lib/active_record/timestamp.rb +43 -32
  209. data/lib/active_record/touch_later.rb +23 -22
  210. data/lib/active_record/transactions.rb +62 -118
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +3 -13
  213. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  214. data/lib/active_record/type/serialized.rb +6 -3
  215. data/lib/active_record/type/time.rb +10 -0
  216. data/lib/active_record/type/type_map.rb +0 -1
  217. data/lib/active_record/type/unsigned_integer.rb +0 -1
  218. data/lib/active_record/type.rb +10 -5
  219. data/lib/active_record/type_caster/connection.rb +15 -15
  220. data/lib/active_record/type_caster/map.rb +8 -8
  221. data/lib/active_record/validations/associated.rb +1 -2
  222. data/lib/active_record/validations/numericality.rb +35 -0
  223. data/lib/active_record/validations/uniqueness.rb +38 -30
  224. data/lib/active_record/validations.rb +4 -3
  225. data/lib/active_record.rb +13 -12
  226. data/lib/arel/alias_predication.rb +9 -0
  227. data/lib/arel/attributes/attribute.rb +41 -0
  228. data/lib/arel/collectors/bind.rb +29 -0
  229. data/lib/arel/collectors/composite.rb +39 -0
  230. data/lib/arel/collectors/plain_string.rb +20 -0
  231. data/lib/arel/collectors/sql_string.rb +27 -0
  232. data/lib/arel/collectors/substitute_binds.rb +35 -0
  233. data/lib/arel/crud.rb +42 -0
  234. data/lib/arel/delete_manager.rb +18 -0
  235. data/lib/arel/errors.rb +9 -0
  236. data/lib/arel/expressions.rb +29 -0
  237. data/lib/arel/factory_methods.rb +49 -0
  238. data/lib/arel/insert_manager.rb +49 -0
  239. data/lib/arel/math.rb +45 -0
  240. data/lib/arel/nodes/and.rb +32 -0
  241. data/lib/arel/nodes/ascending.rb +23 -0
  242. data/lib/arel/nodes/binary.rb +126 -0
  243. data/lib/arel/nodes/bind_param.rb +44 -0
  244. data/lib/arel/nodes/case.rb +55 -0
  245. data/lib/arel/nodes/casted.rb +62 -0
  246. data/lib/arel/nodes/comment.rb +29 -0
  247. data/lib/arel/nodes/count.rb +12 -0
  248. data/lib/arel/nodes/delete_statement.rb +45 -0
  249. data/lib/arel/nodes/descending.rb +23 -0
  250. data/lib/arel/nodes/equality.rb +15 -0
  251. data/lib/arel/nodes/extract.rb +24 -0
  252. data/lib/arel/nodes/false.rb +16 -0
  253. data/lib/arel/nodes/full_outer_join.rb +8 -0
  254. data/lib/arel/nodes/function.rb +44 -0
  255. data/lib/arel/nodes/grouping.rb +11 -0
  256. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  257. data/lib/arel/nodes/in.rb +15 -0
  258. data/lib/arel/nodes/infix_operation.rb +92 -0
  259. data/lib/arel/nodes/inner_join.rb +8 -0
  260. data/lib/arel/nodes/insert_statement.rb +37 -0
  261. data/lib/arel/nodes/join_source.rb +20 -0
  262. data/lib/arel/nodes/matches.rb +18 -0
  263. data/lib/arel/nodes/named_function.rb +23 -0
  264. data/lib/arel/nodes/node.rb +51 -0
  265. data/lib/arel/nodes/node_expression.rb +13 -0
  266. data/lib/arel/nodes/ordering.rb +27 -0
  267. data/lib/arel/nodes/outer_join.rb +8 -0
  268. data/lib/arel/nodes/over.rb +15 -0
  269. data/lib/arel/nodes/regexp.rb +16 -0
  270. data/lib/arel/nodes/right_outer_join.rb +8 -0
  271. data/lib/arel/nodes/select_core.rb +67 -0
  272. data/lib/arel/nodes/select_statement.rb +41 -0
  273. data/lib/arel/nodes/sql_literal.rb +19 -0
  274. data/lib/arel/nodes/string_join.rb +11 -0
  275. data/lib/arel/nodes/table_alias.rb +31 -0
  276. data/lib/arel/nodes/terminal.rb +16 -0
  277. data/lib/arel/nodes/true.rb +16 -0
  278. data/lib/arel/nodes/unary.rb +44 -0
  279. data/lib/arel/nodes/unary_operation.rb +20 -0
  280. data/lib/arel/nodes/unqualified_column.rb +22 -0
  281. data/lib/arel/nodes/update_statement.rb +41 -0
  282. data/lib/arel/nodes/values_list.rb +9 -0
  283. data/lib/arel/nodes/window.rb +126 -0
  284. data/lib/arel/nodes/with.rb +11 -0
  285. data/lib/arel/nodes.rb +70 -0
  286. data/lib/arel/order_predications.rb +13 -0
  287. data/lib/arel/predications.rb +250 -0
  288. data/lib/arel/select_manager.rb +270 -0
  289. data/lib/arel/table.rb +118 -0
  290. data/lib/arel/tree_manager.rb +72 -0
  291. data/lib/arel/update_manager.rb +34 -0
  292. data/lib/arel/visitors/dot.rb +308 -0
  293. data/lib/arel/visitors/mysql.rb +93 -0
  294. data/lib/arel/visitors/postgresql.rb +120 -0
  295. data/lib/arel/visitors/sqlite.rb +38 -0
  296. data/lib/arel/visitors/to_sql.rb +899 -0
  297. data/lib/arel/visitors/visitor.rb +45 -0
  298. data/lib/arel/visitors.rb +13 -0
  299. data/lib/arel/window_predications.rb +9 -0
  300. data/lib/arel.rb +54 -0
  301. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  302. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  303. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  304. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  305. data/lib/rails/generators/active_record/migration.rb +19 -2
  306. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  307. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  308. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  309. metadata +116 -30
  310. data/lib/active_record/attribute_decorators.rb +0 -90
  311. data/lib/active_record/collection_cache_key.rb +0 -53
  312. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  313. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  314. data/lib/active_record/define_callbacks.rb +0 -22
  315. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  316. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -51,7 +51,10 @@ module ActiveRecord
51
51
 
52
52
  if touch
53
53
  names = touch if touch != true
54
- updates.merge!(touch_attributes_with_time(*names))
54
+ names = Array.wrap(names)
55
+ options = names.extract_options!
56
+ touch_updates = touch_attributes_with_time(*names, **options)
57
+ updates.merge!(touch_updates)
55
58
  end
56
59
 
57
60
  unscoped.where(primary_key => object.id).update_all(updates)
@@ -102,27 +105,7 @@ module ActiveRecord
102
105
  # # `updated_at` = '2016-10-13T09:59:23-05:00'
103
106
  # # WHERE id IN (10, 15)
104
107
  def update_counters(id, counters)
105
- touch = counters.delete(:touch)
106
-
107
- updates = counters.map do |counter_name, value|
108
- operator = value < 0 ? "-" : "+"
109
- quoted_column = connection.quote_column_name(counter_name)
110
- "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
111
- end
112
-
113
- if touch
114
- names = touch if touch != true
115
- touch_updates = touch_attributes_with_time(*names)
116
- updates << sanitize_sql_for_assignment(touch_updates) unless touch_updates.empty?
117
- end
118
-
119
- if id.is_a?(Relation) && self == id.klass
120
- relation = id
121
- else
122
- relation = unscoped.where!(primary_key => id)
123
- end
124
-
125
- relation.update_all updates.join(", ")
108
+ unscoped.where!(primary_key => id).update_counters(counters)
126
109
  end
127
110
 
128
111
  # Increment a numeric field by one, via a direct SQL update.
@@ -179,14 +162,11 @@ module ActiveRecord
179
162
  end
180
163
 
181
164
  private
182
-
183
- def _create_record(*)
165
+ def _create_record(attribute_names = self.attribute_names)
184
166
  id = super
185
167
 
186
168
  each_counter_cached_associations do |association|
187
- if send(association.reflection.name)
188
- association.increment_counters
189
- end
169
+ association.increment_counters
190
170
  end
191
171
 
192
172
  id
@@ -199,9 +179,7 @@ module ActiveRecord
199
179
  each_counter_cached_associations do |association|
200
180
  foreign_key = association.reflection.foreign_key.to_sym
201
181
  unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
202
- if send(association.reflection.name)
203
- association.decrement_counters
204
- end
182
+ association.decrement_counters
205
183
  end
206
184
  end
207
185
  end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "active_support/core_ext/enumerable"
5
+
6
+ module ActiveRecord
7
+ class DatabaseConfigurations
8
+ # Expands a connection string into a hash.
9
+ class ConnectionUrlResolver # :nodoc:
10
+ # == Example
11
+ #
12
+ # url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000"
13
+ # ConnectionUrlResolver.new(url).to_hash
14
+ # # => {
15
+ # adapter: "postgresql",
16
+ # host: "localhost",
17
+ # port: 9000,
18
+ # database: "foo_test",
19
+ # username: "foo",
20
+ # password: "bar",
21
+ # pool: "5",
22
+ # timeout: "3000"
23
+ # }
24
+ def initialize(url)
25
+ raise "Database URL cannot be empty" if url.blank?
26
+ @uri = uri_parser.parse(url)
27
+ @adapter = @uri.scheme && @uri.scheme.tr("-", "_")
28
+ @adapter = "postgresql" if @adapter == "postgres"
29
+
30
+ if @uri.opaque
31
+ @uri.opaque, @query = @uri.opaque.split("?", 2)
32
+ else
33
+ @query = @uri.query
34
+ end
35
+ end
36
+
37
+ # Converts the given URL to a full connection hash.
38
+ def to_hash
39
+ config = raw_config.compact_blank
40
+ config.map { |key, value| config[key] = uri_parser.unescape(value) if value.is_a? String }
41
+ config
42
+ end
43
+
44
+ private
45
+ attr_reader :uri
46
+
47
+ def uri_parser
48
+ @uri_parser ||= URI::Parser.new
49
+ end
50
+
51
+ # Converts the query parameters of the URI into a hash.
52
+ #
53
+ # "localhost?pool=5&reaping_frequency=2"
54
+ # # => { pool: "5", reaping_frequency: "2" }
55
+ #
56
+ # returns empty hash if no query present.
57
+ #
58
+ # "localhost"
59
+ # # => {}
60
+ def query_hash
61
+ Hash[(@query || "").split("&").map { |pair| pair.split("=", 2) }].symbolize_keys
62
+ end
63
+
64
+ def raw_config
65
+ if uri.opaque
66
+ query_hash.merge(
67
+ adapter: @adapter,
68
+ database: uri.opaque
69
+ )
70
+ else
71
+ query_hash.merge(
72
+ adapter: @adapter,
73
+ username: uri.user,
74
+ password: uri.password,
75
+ port: uri.port,
76
+ database: database_from_path,
77
+ host: uri.hostname
78
+ )
79
+ end
80
+ end
81
+
82
+ # Returns name of the database.
83
+ def database_from_path
84
+ if @adapter == "sqlite3"
85
+ # 'sqlite3:/foo' is absolute, because that makes sense. The
86
+ # corresponding relative version, 'sqlite3:foo', is handled
87
+ # elsewhere, as an "opaque".
88
+
89
+ uri.path
90
+ else
91
+ # Only SQLite uses a filename as the "database" name; for
92
+ # anything else, a leading slash would be silly.
93
+
94
+ uri.path.delete_prefix("/")
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class DatabaseConfigurations
5
+ # ActiveRecord::Base.configurations will return either a HashConfig or
6
+ # UrlConfig respectively. It will never return a DatabaseConfig object,
7
+ # as this is the parent class for the types of database configuration objects.
8
+ class DatabaseConfig # :nodoc:
9
+ attr_reader :env_name, :name
10
+
11
+ attr_accessor :owner_name
12
+
13
+ def initialize(env_name, name)
14
+ @env_name = env_name
15
+ @name = name
16
+ end
17
+
18
+ def spec_name
19
+ @name
20
+ end
21
+ deprecate spec_name: "please use name instead"
22
+
23
+ def config
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def adapter_method
28
+ "#{adapter}_connection"
29
+ end
30
+
31
+ def host
32
+ raise NotImplementedError
33
+ end
34
+
35
+ def database
36
+ raise NotImplementedError
37
+ end
38
+
39
+ def _database=(database)
40
+ raise NotImplementedError
41
+ end
42
+
43
+ def adapter
44
+ raise NotImplementedError
45
+ end
46
+
47
+ def pool
48
+ raise NotImplementedError
49
+ end
50
+
51
+ def checkout_timeout
52
+ raise NotImplementedError
53
+ end
54
+
55
+ def reaping_frequency
56
+ raise NotImplementedError
57
+ end
58
+
59
+ def idle_timeout
60
+ raise NotImplementedError
61
+ end
62
+
63
+ def replica?
64
+ raise NotImplementedError
65
+ end
66
+
67
+ def migrations_paths
68
+ raise NotImplementedError
69
+ end
70
+
71
+ def for_current_env?
72
+ env_name == ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
73
+ end
74
+
75
+ def schema_cache_path
76
+ raise NotImplementedError
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class DatabaseConfigurations
5
+ # A HashConfig object is created for each database configuration entry that
6
+ # is created from a hash.
7
+ #
8
+ # A hash config:
9
+ #
10
+ # { "development" => { "database" => "db_name" } }
11
+ #
12
+ # Becomes:
13
+ #
14
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
15
+ # @env_name="development", @name="primary", @config={database: "db_name"}>
16
+ #
17
+ # ==== Options
18
+ #
19
+ # * <tt>:env_name</tt> - The Rails environment, i.e. "development".
20
+ # * <tt>:name</tt> - The db config name. In a standard two-tier
21
+ # database configuration this will default to "primary". In a multiple
22
+ # database three-tier database configuration this corresponds to the name
23
+ # used in the second tier, for example "primary_readonly".
24
+ # * <tt>:config</tt> - The config hash. This is the hash that contains the
25
+ # database adapter, name, and other important information for database
26
+ # connections.
27
+ class HashConfig < DatabaseConfig
28
+ attr_reader :configuration_hash
29
+ def initialize(env_name, name, configuration_hash)
30
+ super(env_name, name)
31
+ @configuration_hash = configuration_hash.symbolize_keys.freeze
32
+ end
33
+
34
+ def config
35
+ ActiveSupport::Deprecation.warn("DatabaseConfig#config will be removed in 7.0.0 in favor of DatabaseConfig#configuration_hash which returns a hash with symbol keys")
36
+ configuration_hash.stringify_keys
37
+ end
38
+
39
+ # Determines whether a database configuration is for a replica / readonly
40
+ # connection. If the +replica+ key is present in the config, +replica?+ will
41
+ # return +true+.
42
+ def replica?
43
+ configuration_hash[:replica]
44
+ end
45
+
46
+ # The migrations paths for a database configuration. If the
47
+ # +migrations_paths+ key is present in the config, +migrations_paths+
48
+ # will return its value.
49
+ def migrations_paths
50
+ configuration_hash[:migrations_paths]
51
+ end
52
+
53
+ def host
54
+ configuration_hash[:host]
55
+ end
56
+
57
+ def database
58
+ configuration_hash[:database]
59
+ end
60
+
61
+ def _database=(database) # :nodoc:
62
+ @configuration_hash = configuration_hash.merge(database: database).freeze
63
+ end
64
+
65
+ def pool
66
+ (configuration_hash[:pool] || 5).to_i
67
+ end
68
+
69
+ def checkout_timeout
70
+ (configuration_hash[:checkout_timeout] || 5).to_f
71
+ end
72
+
73
+ # +reaping_frequency+ is configurable mostly for historical reasons, but it could
74
+ # also be useful if someone wants a very low +idle_timeout+.
75
+ def reaping_frequency
76
+ configuration_hash.fetch(:reaping_frequency, 60)&.to_f
77
+ end
78
+
79
+ def idle_timeout
80
+ timeout = configuration_hash.fetch(:idle_timeout, 300).to_f
81
+ timeout if timeout > 0
82
+ end
83
+
84
+ def adapter
85
+ configuration_hash[:adapter]
86
+ end
87
+
88
+ # The path to the schema cache dump file for a database.
89
+ # If omitted, the filename will be read from ENV or a
90
+ # default will be derived.
91
+ def schema_cache_path
92
+ configuration_hash[:schema_cache_path]
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class DatabaseConfigurations
5
+ # A UrlConfig object is created for each database configuration
6
+ # entry that is created from a URL. This can either be a URL string
7
+ # or a hash with a URL in place of the config hash.
8
+ #
9
+ # A URL config:
10
+ #
11
+ # postgres://localhost/foo
12
+ #
13
+ # Becomes:
14
+ #
15
+ # #<ActiveRecord::DatabaseConfigurations::UrlConfig:0x00007fdc3238f340
16
+ # @env_name="default_env", @name="primary",
17
+ # @config={adapter: "postgresql", database: "foo", host: "localhost"},
18
+ # @url="postgres://localhost/foo">
19
+ #
20
+ # ==== Options
21
+ #
22
+ # * <tt>:env_name</tt> - The Rails environment, ie "development".
23
+ # * <tt>:name</tt> - The db config name. In a standard two-tier
24
+ # database configuration this will default to "primary". In a multiple
25
+ # database three-tier database configuration this corresponds to the name
26
+ # used in the second tier, for example "primary_readonly".
27
+ # * <tt>:url</tt> - The database URL.
28
+ # * <tt>:config</tt> - The config hash. This is the hash that contains the
29
+ # database adapter, name, and other important information for database
30
+ # connections.
31
+ class UrlConfig < HashConfig
32
+ attr_reader :url
33
+
34
+ def initialize(env_name, name, url, configuration_hash = {})
35
+ super(env_name, name, configuration_hash)
36
+
37
+ @url = url
38
+ @configuration_hash = @configuration_hash.merge(build_url_hash).freeze
39
+ end
40
+
41
+ private
42
+ # Return a Hash that can be merged into the main config that represents
43
+ # the passed in url
44
+ def build_url_hash
45
+ if url.nil? || %w(jdbc: http: https:).any? { |protocol| url.start_with?(protocol) }
46
+ { url: url }
47
+ else
48
+ ConnectionUrlResolver.new(url).to_hash
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,273 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "active_record/database_configurations/database_config"
5
+ require "active_record/database_configurations/hash_config"
6
+ require "active_record/database_configurations/url_config"
7
+ require "active_record/database_configurations/connection_url_resolver"
8
+
9
+ module ActiveRecord
10
+ # ActiveRecord::DatabaseConfigurations returns an array of DatabaseConfig
11
+ # objects (either a HashConfig or UrlConfig) that are constructed from the
12
+ # application's database configuration hash or URL string.
13
+ class DatabaseConfigurations
14
+ class InvalidConfigurationError < StandardError; end
15
+
16
+ attr_reader :configurations
17
+ delegate :any?, to: :configurations
18
+
19
+ def initialize(configurations = {})
20
+ @configurations = build_configs(configurations)
21
+ end
22
+
23
+ # Collects the configs for the environment and optionally the specification
24
+ # name passed in. To include replica configurations pass <tt>include_replicas: true</tt>.
25
+ #
26
+ # If a name is provided a single DatabaseConfig object will be
27
+ # returned, otherwise an array of DatabaseConfig objects will be
28
+ # returned that corresponds with the environment and type requested.
29
+ #
30
+ # ==== Options
31
+ #
32
+ # * <tt>env_name:</tt> The environment name. Defaults to +nil+ which will collect
33
+ # configs for all environments.
34
+ # * <tt>name:</tt> The db config name (i.e. primary, animals, etc.). Defaults
35
+ # to +nil+. If no +env_name+ is specified the config for the default env and the
36
+ # passed +name+ will be returned.
37
+ # * <tt>include_replicas:</tt> Determines whether to include replicas in
38
+ # the returned list. Most of the time we're only iterating over the write
39
+ # connection (i.e. migrations don't need to run for the write and read connection).
40
+ # Defaults to +false+.
41
+ def configs_for(env_name: nil, spec_name: nil, name: nil, include_replicas: false)
42
+ if spec_name
43
+ name = spec_name
44
+ ActiveSupport::Deprecation.warn("The kwarg `spec_name` is deprecated in favor of `name`. `spec_name` will be removed in Rails 7.0")
45
+ end
46
+
47
+ env_name ||= default_env if name
48
+ configs = env_with_configs(env_name)
49
+
50
+ unless include_replicas
51
+ configs = configs.select do |db_config|
52
+ !db_config.replica?
53
+ end
54
+ end
55
+
56
+ if name
57
+ configs.find do |db_config|
58
+ db_config.name == name
59
+ end
60
+ else
61
+ configs
62
+ end
63
+ end
64
+
65
+ # Returns the config hash that corresponds with the environment
66
+ #
67
+ # If the application has multiple databases +default_hash+ will
68
+ # return the first config hash for the environment.
69
+ #
70
+ # { database: "my_db", adapter: "mysql2" }
71
+ def default_hash(env = default_env)
72
+ default = find_db_config(env)
73
+ default.configuration_hash if default
74
+ end
75
+ alias :[] :default_hash
76
+ deprecate "[]": "Use configs_for", default_hash: "Use configs_for"
77
+
78
+ # Returns a single DatabaseConfig object based on the requested environment.
79
+ #
80
+ # If the application has multiple databases +find_db_config+ will return
81
+ # the first DatabaseConfig for the environment.
82
+ def find_db_config(env)
83
+ configurations
84
+ .sort_by.with_index { |db_config, i| db_config.for_current_env? ? [0, i] : [1, i] }
85
+ .find do |db_config|
86
+ db_config.env_name == env.to_s ||
87
+ (db_config.for_current_env? && db_config.name == env.to_s)
88
+ end
89
+ end
90
+
91
+ # A primary configuration is one that is named primary or if there is
92
+ # no primary, the first configuration for an environment will be treated
93
+ # as primary. This is used as the "default" configuration and is used
94
+ # when the application needs to treat one configuration differently. For
95
+ # example, when Rails dumps the schema, the primary configuration's schema
96
+ # file will be named `schema.rb` instead of `primary_schema.rb`.
97
+ def primary?(name) # :nodoc:
98
+ return true if name == "primary"
99
+
100
+ first_config = find_db_config(default_env)
101
+ first_config && name == first_config.name
102
+ end
103
+
104
+ # Returns the DatabaseConfigurations object as a Hash.
105
+ def to_h
106
+ configurations.inject({}) do |memo, db_config|
107
+ memo.merge(db_config.env_name => db_config.configuration_hash.stringify_keys)
108
+ end
109
+ end
110
+ deprecate to_h: "You can use `ActiveRecord::Base.configurations.configs_for(env_name: 'env', name: 'primary').configuration_hash` to get the configuration hashes."
111
+
112
+ # Checks if the application's configurations are empty.
113
+ #
114
+ # Aliased to blank?
115
+ def empty?
116
+ configurations.empty?
117
+ end
118
+ alias :blank? :empty?
119
+
120
+ # Returns fully resolved connection, accepts hash, string or symbol.
121
+ # Always returns a DatabaseConfiguration::DatabaseConfig
122
+ #
123
+ # == Examples
124
+ #
125
+ # Symbol representing current environment.
126
+ #
127
+ # DatabaseConfigurations.new("production" => {}).resolve(:production)
128
+ # # => DatabaseConfigurations::HashConfig.new(env_name: "production", config: {})
129
+ #
130
+ # One layer deep hash of connection values.
131
+ #
132
+ # DatabaseConfigurations.new({}).resolve("adapter" => "sqlite3")
133
+ # # => DatabaseConfigurations::HashConfig.new(config: {"adapter" => "sqlite3"})
134
+ #
135
+ # Connection URL.
136
+ #
137
+ # DatabaseConfigurations.new({}).resolve("postgresql://localhost/foo")
138
+ # # => DatabaseConfigurations::UrlConfig.new(config: {"adapter" => "postgresql", "host" => "localhost", "database" => "foo"})
139
+ def resolve(config) # :nodoc:
140
+ return config if DatabaseConfigurations::DatabaseConfig === config
141
+
142
+ case config
143
+ when Symbol
144
+ resolve_symbol_connection(config)
145
+ when Hash, String
146
+ build_db_config_from_raw_config(default_env, "primary", config)
147
+ else
148
+ raise TypeError, "Invalid type for configuration. Expected Symbol, String, or Hash. Got #{config.inspect}"
149
+ end
150
+ end
151
+
152
+ private
153
+ def default_env
154
+ ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
155
+ end
156
+
157
+ def env_with_configs(env = nil)
158
+ if env
159
+ configurations.select { |db_config| db_config.env_name == env }
160
+ else
161
+ configurations
162
+ end
163
+ end
164
+
165
+ def build_configs(configs)
166
+ return configs.configurations if configs.is_a?(DatabaseConfigurations)
167
+ return configs if configs.is_a?(Array)
168
+
169
+ db_configs = configs.flat_map do |env_name, config|
170
+ if config.is_a?(Hash) && config.all? { |_, v| v.is_a?(Hash) }
171
+ walk_configs(env_name.to_s, config)
172
+ else
173
+ build_db_config_from_raw_config(env_name.to_s, "primary", config)
174
+ end
175
+ end
176
+
177
+ unless db_configs.find(&:for_current_env?)
178
+ db_configs << environment_url_config(default_env, "primary", {})
179
+ end
180
+
181
+ merge_db_environment_variables(default_env, db_configs.compact)
182
+ end
183
+
184
+ def walk_configs(env_name, config)
185
+ config.map do |name, sub_config|
186
+ build_db_config_from_raw_config(env_name, name.to_s, sub_config)
187
+ end
188
+ end
189
+
190
+ def resolve_symbol_connection(name)
191
+ if db_config = find_db_config(name)
192
+ db_config
193
+ else
194
+ raise AdapterNotSpecified, <<~MSG
195
+ The `#{name}` database is not configured for the `#{default_env}` environment.
196
+
197
+ Available databases configurations are:
198
+
199
+ #{build_configuration_sentence}
200
+ MSG
201
+ end
202
+ end
203
+
204
+ def build_configuration_sentence
205
+ configs = configs_for(include_replicas: true)
206
+
207
+ configs.group_by(&:env_name).map do |env, config|
208
+ names = config.map(&:name)
209
+ if names.size > 1
210
+ "#{env}: #{names.join(", ")}"
211
+ else
212
+ env
213
+ end
214
+ end.join("\n")
215
+ end
216
+
217
+ def build_db_config_from_raw_config(env_name, name, config)
218
+ case config
219
+ when String
220
+ build_db_config_from_string(env_name, name, config)
221
+ when Hash
222
+ build_db_config_from_hash(env_name, name, config.symbolize_keys)
223
+ else
224
+ raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
225
+ end
226
+ end
227
+
228
+ def build_db_config_from_string(env_name, name, config)
229
+ url = config
230
+ uri = URI.parse(url)
231
+ if uri.scheme
232
+ UrlConfig.new(env_name, name, url)
233
+ else
234
+ raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
235
+ end
236
+ end
237
+
238
+ def build_db_config_from_hash(env_name, name, config)
239
+ if config.has_key?(:url)
240
+ url = config[:url]
241
+ config_without_url = config.dup
242
+ config_without_url.delete :url
243
+
244
+ UrlConfig.new(env_name, name, url, config_without_url)
245
+ else
246
+ HashConfig.new(env_name, name, config)
247
+ end
248
+ end
249
+
250
+ def merge_db_environment_variables(current_env, configs)
251
+ configs.map do |config|
252
+ next config if config.is_a?(UrlConfig) || config.env_name != current_env
253
+
254
+ url_config = environment_url_config(current_env, config.name, config.configuration_hash)
255
+ url_config || config
256
+ end
257
+ end
258
+
259
+ def environment_url_config(env, name, config)
260
+ url = environment_value_for(name)
261
+ return unless url
262
+
263
+ UrlConfig.new(env, name, url, config)
264
+ end
265
+
266
+ def environment_value_for(name)
267
+ name_env_key = "#{name.upcase}_DATABASE_URL"
268
+ url = ENV[name_env_key]
269
+ url ||= ENV["DATABASE_URL"] if name == "primary"
270
+ url
271
+ end
272
+ end
273
+ end