activerecord 5.2.8.1 → 6.1.0

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 +849 -630
  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 +5 -4
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +95 -42
  10. data/lib/active_record/associations/association_scope.rb +21 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +50 -46
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -5
  13. data/lib/active_record/associations/builder/association.rb +23 -21
  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 +31 -29
  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 +41 -20
  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 +71 -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 +133 -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 +45 -8
  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 +2 -15
  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 +203 -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 +381 -146
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +155 -68
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +229 -98
  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 +31 -0
  67. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  68. data/lib/active_record/connection_adapters/mysql/database_statements.rb +86 -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 +44 -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 +14 -6
  74. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +139 -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 +63 -0
  78. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  79. data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
  80. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +38 -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/money.rb +2 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  94. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  95. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  97. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  98. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  99. data/lib/active_record/connection_adapters/postgresql/quoting.rb +47 -10
  100. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  101. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +19 -4
  102. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  103. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  104. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +120 -100
  105. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  106. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  107. data/lib/active_record/connection_adapters/postgresql_adapter.rb +222 -112
  108. data/lib/active_record/connection_adapters/schema_cache.rb +127 -21
  109. data/lib/active_record/connection_adapters/sql_type_metadata.rb +19 -6
  110. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  111. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
  112. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  113. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +77 -13
  114. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +174 -186
  115. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  116. data/lib/active_record/connection_adapters.rb +50 -0
  117. data/lib/active_record/connection_handling.rb +285 -33
  118. data/lib/active_record/core.rb +304 -106
  119. data/lib/active_record/counter_cache.rb +8 -30
  120. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  121. data/lib/active_record/database_configurations/database_config.rb +80 -0
  122. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  123. data/lib/active_record/database_configurations/url_config.rb +53 -0
  124. data/lib/active_record/database_configurations.rb +272 -0
  125. data/lib/active_record/delegated_type.rb +209 -0
  126. data/lib/active_record/destroy_association_async_job.rb +36 -0
  127. data/lib/active_record/dynamic_matchers.rb +3 -4
  128. data/lib/active_record/enum.rb +71 -17
  129. data/lib/active_record/errors.rb +62 -19
  130. data/lib/active_record/explain.rb +10 -6
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/file.rb +10 -17
  133. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  134. data/lib/active_record/fixture_set/render_context.rb +17 -0
  135. data/lib/active_record/fixture_set/table_row.rb +152 -0
  136. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  137. data/lib/active_record/fixtures.rb +197 -481
  138. data/lib/active_record/gem_version.rb +4 -4
  139. data/lib/active_record/inheritance.rb +53 -24
  140. data/lib/active_record/insert_all.rb +208 -0
  141. data/lib/active_record/integration.rb +67 -17
  142. data/lib/active_record/internal_metadata.rb +26 -9
  143. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  144. data/lib/active_record/locking/optimistic.rb +26 -22
  145. data/lib/active_record/locking/pessimistic.rb +9 -5
  146. data/lib/active_record/log_subscriber.rb +34 -35
  147. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  149. data/lib/active_record/middleware/database_selector.rb +77 -0
  150. data/lib/active_record/migration/command_recorder.rb +96 -44
  151. data/lib/active_record/migration/compatibility.rb +141 -64
  152. data/lib/active_record/migration/join_table.rb +0 -1
  153. data/lib/active_record/migration.rb +205 -156
  154. data/lib/active_record/model_schema.rb +148 -22
  155. data/lib/active_record/nested_attributes.rb +4 -7
  156. data/lib/active_record/no_touching.rb +8 -1
  157. data/lib/active_record/null_relation.rb +0 -1
  158. data/lib/active_record/persistence.rb +267 -59
  159. data/lib/active_record/query_cache.rb +21 -4
  160. data/lib/active_record/querying.rb +40 -23
  161. data/lib/active_record/railtie.rb +113 -74
  162. data/lib/active_record/railties/controller_runtime.rb +30 -35
  163. data/lib/active_record/railties/databases.rake +402 -78
  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 +153 -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 +4 -7
  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 +58 -40
  180. data/lib/active_record/relation/query_attribute.rb +13 -8
  181. data/lib/active_record/relation/query_methods.rb +472 -186
  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 +108 -58
  185. data/lib/active_record/relation.rb +375 -104
  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 -6
  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 +39 -43
  202. data/lib/active_record/tasks/database_tasks.rb +276 -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 +246 -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 +58 -116
  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 +72 -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 +120 -35
  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
@@ -14,9 +14,9 @@ module ActiveRecord
14
14
  # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
15
15
  #
16
16
  # Account.transaction do
17
- # # select * from accounts where name = 'shugo' limit 1 for update
18
- # shugo = Account.where("name = 'shugo'").lock(true).first
19
- # yuko = Account.where("name = 'yuko'").lock(true).first
17
+ # # select * from accounts where name = 'shugo' limit 1 for update nowait
18
+ # shugo = Account.lock("FOR UPDATE NOWAIT").find_by(name: "shugo")
19
+ # yuko = Account.lock("FOR UPDATE NOWAIT").find_by(name: "yuko")
20
20
  # shugo.balance -= 100
21
21
  # shugo.save!
22
22
  # yuko.balance += 100
@@ -53,8 +53,12 @@ module ActiveRecord
53
53
  # end
54
54
  #
55
55
  # Database-specific information on row locking:
56
- # MySQL: https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
57
- # PostgreSQL: https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
56
+ #
57
+ # [MySQL]
58
+ # https://dev.mysql.com/doc/refman/en/innodb-locking-reads.html
59
+ #
60
+ # [PostgreSQL]
61
+ # https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
58
62
  module Pessimistic
59
63
  # Obtain a row lock on this record. Reloads the record to obtain the requested
60
64
  # lock. Pass an SQL locking clause to append the end of the SELECT statement
@@ -4,6 +4,8 @@ module ActiveRecord
4
4
  class LogSubscriber < ActiveSupport::LogSubscriber
5
5
  IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
6
6
 
7
+ class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
8
+
7
9
  def self.runtime=(value)
8
10
  ActiveRecord::RuntimeRegistry.sql_runtime = value
9
11
  end
@@ -17,6 +19,15 @@ module ActiveRecord
17
19
  rt
18
20
  end
19
21
 
22
+ def strict_loading_violation(event)
23
+ debug do
24
+ owner = event.payload[:owner]
25
+ association = event.payload[:association]
26
+
27
+ color("Strict loading violation: #{association} lazily loaded on #{owner}.", RED)
28
+ end
29
+ end
30
+
20
31
  def sql(event)
21
32
  self.class.runtime += event.duration
22
33
  return unless logger.debug?
@@ -30,15 +41,19 @@ module ActiveRecord
30
41
  sql = payload[:sql]
31
42
  binds = nil
32
43
 
33
- unless (payload[:binds] || []).empty?
44
+ if payload[:binds]&.any?
34
45
  casted_params = type_casted_binds(payload[:type_casted_binds])
35
- binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
36
- render_bind(attr, value)
37
- }.inspect
46
+
47
+ binds = []
48
+ payload[:binds].each_with_index do |attr, i|
49
+ binds << render_bind(attr, casted_params[i])
50
+ end
51
+ binds = binds.inspect
52
+ binds.prepend(" ")
38
53
  end
39
54
 
40
55
  name = colorize_payload_name(name, payload[:name])
41
- sql = color(sql, sql_color(sql), true)
56
+ sql = color(sql, sql_color(sql), true) if colorize_logging
42
57
 
43
58
  debug " #{name} #{sql}#{binds}"
44
59
  end
@@ -49,13 +64,18 @@ module ActiveRecord
49
64
  end
50
65
 
51
66
  def render_bind(attr, value)
52
- if attr.is_a?(Array)
67
+ case attr
68
+ when ActiveModel::Attribute
69
+ if attr.type.binary? && attr.value
70
+ value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
71
+ end
72
+ when Array
53
73
  attr = attr.first
54
- elsif attr.type.binary? && attr.value
55
- value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
74
+ else
75
+ attr = nil
56
76
  end
57
77
 
58
- [attr && attr.name, value]
78
+ [attr&.name, value]
59
79
  end
60
80
 
61
81
  def colorize_payload_name(name, payload_name)
@@ -100,36 +120,15 @@ module ActiveRecord
100
120
  end
101
121
 
102
122
  def log_query_source
103
- source_line, line_number = extract_callstack(caller_locations)
123
+ source = extract_query_source_location(caller)
104
124
 
105
- if source_line
106
- if defined?(::Rails.root)
107
- app_root = "#{::Rails.root.to_s}/".freeze
108
- source_line = source_line.sub(app_root, "")
109
- end
110
-
111
- logger.debug(" ↳ #{ source_line }:#{ line_number }")
112
- end
113
- end
114
-
115
- def extract_callstack(callstack)
116
- line = callstack.find do |frame|
117
- frame.absolute_path && !ignored_callstack(frame.absolute_path)
125
+ if source
126
+ logger.debug(" ↳ #{source}")
118
127
  end
119
-
120
- offending_line = line || callstack.first
121
-
122
- [
123
- offending_line.path,
124
- offending_line.lineno
125
- ]
126
128
  end
127
129
 
128
- RAILS_GEM_ROOT = File.expand_path("../../..", __dir__) + "/"
129
-
130
- def ignored_callstack(path)
131
- path.start_with?(RAILS_GEM_ROOT) ||
132
- path.start_with?(RbConfig::CONFIG["rubylibdir"])
130
+ def extract_query_source_location(locations)
131
+ backtrace_cleaner.clean(locations.lazy).first
133
132
  end
134
133
  end
135
134
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Middleware
5
+ class DatabaseSelector
6
+ class Resolver
7
+ # The session class is used by the DatabaseSelector::Resolver to save
8
+ # timestamps of the last write in the session.
9
+ #
10
+ # The last_write is used to determine whether it's safe to read
11
+ # from the replica or the request needs to be sent to the primary.
12
+ class Session # :nodoc:
13
+ def self.call(request)
14
+ new(request.session)
15
+ end
16
+
17
+ # Converts time to a timestamp that represents milliseconds since
18
+ # epoch.
19
+ def self.convert_time_to_timestamp(time)
20
+ time.to_i * 1000 + time.usec / 1000
21
+ end
22
+
23
+ # Converts milliseconds since epoch timestamp into a time object.
24
+ def self.convert_timestamp_to_time(timestamp)
25
+ timestamp ? Time.at(timestamp / 1000, (timestamp % 1000) * 1000) : Time.at(0)
26
+ end
27
+
28
+ def initialize(session)
29
+ @session = session
30
+ end
31
+
32
+ attr_reader :session
33
+
34
+ def last_write_timestamp
35
+ self.class.convert_timestamp_to_time(session[:last_write])
36
+ end
37
+
38
+ def update_last_write_timestamp
39
+ session[:last_write] = self.class.convert_time_to_timestamp(Time.now)
40
+ end
41
+
42
+ def save(response)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/middleware/database_selector/resolver/session"
4
+ require "active_support/core_ext/numeric/time"
5
+
6
+ module ActiveRecord
7
+ module Middleware
8
+ class DatabaseSelector
9
+ # The Resolver class is used by the DatabaseSelector middleware to
10
+ # determine which database the request should use.
11
+ #
12
+ # To change the behavior of the Resolver class in your application,
13
+ # create a custom resolver class that inherits from
14
+ # DatabaseSelector::Resolver and implements the methods that need to
15
+ # be changed.
16
+ #
17
+ # By default the Resolver class will send read traffic to the replica
18
+ # if it's been 2 seconds since the last write.
19
+ class Resolver # :nodoc:
20
+ SEND_TO_REPLICA_DELAY = 2.seconds
21
+
22
+ def self.call(context, options = {})
23
+ new(context, options)
24
+ end
25
+
26
+ def initialize(context, options = {})
27
+ @context = context
28
+ @options = options
29
+ @delay = @options && @options[:delay] ? @options[:delay] : SEND_TO_REPLICA_DELAY
30
+ @instrumenter = ActiveSupport::Notifications.instrumenter
31
+ end
32
+
33
+ attr_reader :context, :delay, :instrumenter
34
+
35
+ def read(&blk)
36
+ if read_from_primary?
37
+ read_from_primary(&blk)
38
+ else
39
+ read_from_replica(&blk)
40
+ end
41
+ end
42
+
43
+ def write(&blk)
44
+ write_to_primary(&blk)
45
+ end
46
+
47
+ def update_context(response)
48
+ context.save(response)
49
+ end
50
+
51
+ private
52
+ def read_from_primary(&blk)
53
+ ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: true) do
54
+ instrumenter.instrument("database_selector.active_record.read_from_primary") do
55
+ yield
56
+ end
57
+ end
58
+ end
59
+
60
+ def read_from_replica(&blk)
61
+ ActiveRecord::Base.connected_to(role: ActiveRecord::Base.reading_role, prevent_writes: true) do
62
+ instrumenter.instrument("database_selector.active_record.read_from_replica") do
63
+ yield
64
+ end
65
+ end
66
+ end
67
+
68
+ def write_to_primary(&blk)
69
+ ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: false) do
70
+ instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
71
+ yield
72
+ ensure
73
+ context.update_last_write_timestamp
74
+ end
75
+ end
76
+ end
77
+
78
+ def read_from_primary?
79
+ !time_since_last_write_ok?
80
+ end
81
+
82
+ def send_to_replica_delay
83
+ delay
84
+ end
85
+
86
+ def time_since_last_write_ok?
87
+ Time.now - context.last_write_timestamp >= send_to_replica_delay
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/middleware/database_selector/resolver"
4
+
5
+ module ActiveRecord
6
+ module Middleware
7
+ # The DatabaseSelector Middleware provides a framework for automatically
8
+ # swapping from the primary to the replica database connection. Rails
9
+ # provides a basic framework to determine when to swap and allows for
10
+ # applications to write custom strategy classes to override the default
11
+ # behavior.
12
+ #
13
+ # The resolver class defines when the application should switch (i.e. read
14
+ # from the primary if a write occurred less than 2 seconds ago) and a
15
+ # resolver context class that sets a value that helps the resolver class
16
+ # decide when to switch.
17
+ #
18
+ # Rails default middleware uses the request's session to set a timestamp
19
+ # that informs the application when to read from a primary or read from a
20
+ # replica.
21
+ #
22
+ # To use the DatabaseSelector in your application with default settings add
23
+ # the following options to your environment config:
24
+ #
25
+ # config.active_record.database_selector = { delay: 2.seconds }
26
+ # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
27
+ # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
28
+ #
29
+ # New applications will include these lines commented out in the production.rb.
30
+ #
31
+ # The default behavior can be changed by setting the config options to a
32
+ # custom class:
33
+ #
34
+ # config.active_record.database_selector = { delay: 2.seconds }
35
+ # config.active_record.database_resolver = MyResolver
36
+ # config.active_record.database_resolver_context = MyResolver::MySession
37
+ class DatabaseSelector
38
+ def initialize(app, resolver_klass = nil, context_klass = nil, options = {})
39
+ @app = app
40
+ @resolver_klass = resolver_klass || Resolver
41
+ @context_klass = context_klass || Resolver::Session
42
+ @options = options
43
+ end
44
+
45
+ attr_reader :resolver_klass, :context_klass, :options
46
+
47
+ # Middleware that determines which database connection to use in a multiple
48
+ # database application.
49
+ def call(env)
50
+ request = ActionDispatch::Request.new(env)
51
+
52
+ select_database(request) do
53
+ @app.call(env)
54
+ end
55
+ end
56
+
57
+ private
58
+ def select_database(request, &blk)
59
+ context = context_klass.call(request)
60
+ resolver = resolver_klass.call(context, options)
61
+
62
+ response = if reading_request?(request)
63
+ resolver.read(&blk)
64
+ else
65
+ resolver.write(&blk)
66
+ end
67
+
68
+ resolver.update_context(response)
69
+ response
70
+ end
71
+
72
+ def reading_request?(request)
73
+ request.get? || request.head?
74
+ end
75
+ end
76
+ end
77
+ end
@@ -8,12 +8,15 @@ module ActiveRecord
8
8
  #
9
9
  # * add_column
10
10
  # * add_foreign_key
11
+ # * add_check_constraint
11
12
  # * add_index
12
13
  # * add_reference
13
14
  # * add_timestamps
14
15
  # * change_column
15
16
  # * change_column_default (must supply a :from and :to option)
16
17
  # * change_column_null
18
+ # * change_column_comment (must supply a :from and :to option)
19
+ # * change_table_comment (must supply a :from and :to option)
17
20
  # * create_join_table
18
21
  # * create_table
19
22
  # * disable_extension
@@ -23,6 +26,7 @@ module ActiveRecord
23
26
  # * remove_column (must supply a type)
24
27
  # * remove_columns (must specify at least one column name or more)
25
28
  # * remove_foreign_key (must supply a second table)
29
+ # * remove_check_constraint
26
30
  # * remove_index
27
31
  # * remove_reference
28
32
  # * remove_timestamps
@@ -30,12 +34,15 @@ module ActiveRecord
30
34
  # * rename_index
31
35
  # * rename_table
32
36
  class CommandRecorder
33
- ReversibleAndIrreversibleMethods = [:create_table, :create_join_table, :rename_table, :add_column, :remove_column,
37
+ ReversibleAndIrreversibleMethods = [
38
+ :create_table, :create_join_table, :rename_table, :add_column, :remove_column,
34
39
  :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps,
35
40
  :change_column_default, :add_reference, :remove_reference, :transaction,
36
41
  :drop_join_table, :drop_table, :execute_block, :enable_extension, :disable_extension,
37
42
  :change_column, :execute, :remove_columns, :change_column_null,
38
- :add_foreign_key, :remove_foreign_key
43
+ :add_foreign_key, :remove_foreign_key,
44
+ :change_column_comment, :change_table_comment,
45
+ :add_check_constraint, :remove_check_constraint
39
46
  ]
40
47
  include JoinTable
41
48
 
@@ -81,11 +88,16 @@ module ActiveRecord
81
88
  # recorder.inverse_of(:rename_table, [:old, :new])
82
89
  # # => [:rename_table, [:new, :old]]
83
90
  #
91
+ # If the inverse of a command requires several commands, returns array of commands.
92
+ #
93
+ # recorder.inverse_of(:remove_columns, [:some_table, :foo, :bar, type: :string])
94
+ # # => [[:add_column, :some_table, :foo, :string], [:add_column, :some_table, :bar, :string]]
95
+ #
84
96
  # This method will raise an +IrreversibleMigration+ exception if it cannot
85
97
  # invert the +command+.
86
98
  def inverse_of(command, args, &block)
87
99
  method = :"invert_#{command}"
88
- raise IrreversibleMigration, <<-MSG.strip_heredoc unless respond_to?(method, true)
100
+ raise IrreversibleMigration, <<~MSG unless respond_to?(method, true)
89
101
  This migration uses #{command}, which is not automatically reversible.
90
102
  To make the migration reversible you can either:
91
103
  1. Define #up and #down methods in place of the #change method.
@@ -100,25 +112,34 @@ module ActiveRecord
100
112
  record(:"#{method}", args, &block) # record(:create_table, args, &block)
101
113
  end # end
102
114
  EOV
115
+ ruby2_keywords(method) if respond_to?(:ruby2_keywords, true)
103
116
  end
104
117
  alias :add_belongs_to :add_reference
105
118
  alias :remove_belongs_to :remove_reference
106
119
 
107
- def change_table(table_name, options = {}) # :nodoc:
120
+ def change_table(table_name, **options) # :nodoc:
108
121
  yield delegate.update_table_definition(table_name, self)
109
122
  end
110
123
 
111
- private
124
+ def replay(migration)
125
+ commands.each do |cmd, args, block|
126
+ migration.send(cmd, *args, &block)
127
+ end
128
+ end
112
129
 
130
+ private
113
131
  module StraightReversions # :nodoc:
114
132
  private
115
- { transaction: :transaction,
133
+ {
116
134
  execute_block: :execute_block,
117
135
  create_table: :drop_table,
118
136
  create_join_table: :drop_join_table,
119
137
  add_column: :remove_column,
138
+ add_index: :remove_index,
120
139
  add_timestamps: :remove_timestamps,
121
140
  add_reference: :remove_reference,
141
+ add_foreign_key: :remove_foreign_key,
142
+ add_check_constraint: :remove_check_constraint,
122
143
  enable_extension: :disable_extension
123
144
  }.each do |cmd, inv|
124
145
  [[inv, cmd], [cmd, inv]].uniq.each do |method, inverse|
@@ -133,6 +154,17 @@ module ActiveRecord
133
154
 
134
155
  include StraightReversions
135
156
 
157
+ def invert_transaction(args)
158
+ sub_recorder = CommandRecorder.new(delegate)
159
+ sub_recorder.revert { yield }
160
+
161
+ invertions_proc = proc {
162
+ sub_recorder.replay(self)
163
+ }
164
+
165
+ [:transaction, args, invertions_proc]
166
+ end
167
+
136
168
  def invert_drop_table(args, &block)
137
169
  if args.size == 1 && block == nil
138
170
  raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)."
@@ -149,44 +181,49 @@ module ActiveRecord
149
181
  super
150
182
  end
151
183
 
184
+ def invert_remove_columns(args)
185
+ unless args[-1].is_a?(Hash) && args[-1].has_key?(:type)
186
+ raise ActiveRecord::IrreversibleMigration, "remove_columns is only reversible if given a type."
187
+ end
188
+
189
+ [:add_columns, args]
190
+ end
191
+
152
192
  def invert_rename_index(args)
153
- [:rename_index, [args.first] + args.last(2).reverse]
193
+ table_name, old_name, new_name = args
194
+ [:rename_index, [table_name, new_name, old_name]]
154
195
  end
155
196
 
156
197
  def invert_rename_column(args)
157
- [:rename_column, [args.first] + args.last(2).reverse]
198
+ table_name, old_name, new_name = args
199
+ [:rename_column, [table_name, new_name, old_name]]
158
200
  end
159
201
 
160
- def invert_add_index(args)
161
- table, columns, options = *args
162
- options ||= {}
163
-
164
- options_hash = options.slice(:name, :algorithm)
165
- options_hash[:column] = columns if !options_hash[:name]
202
+ def invert_remove_index(args)
203
+ options = args.extract_options!
204
+ table, columns = args
166
205
 
167
- [:remove_index, [table, options_hash]]
168
- end
206
+ columns ||= options.delete(:column)
169
207
 
170
- def invert_remove_index(args)
171
- table, options_or_column = *args
172
- if (options = options_or_column).is_a?(Hash)
173
- unless options[:column]
174
- raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option."
175
- end
176
- options = options.dup
177
- [:add_index, [table, options.delete(:column), options]]
178
- elsif (column = options_or_column).present?
179
- [:add_index, [table, column]]
208
+ unless columns
209
+ raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option."
180
210
  end
211
+
212
+ options.delete(:if_exists)
213
+
214
+ args = [table, columns]
215
+ args << options unless options.empty?
216
+
217
+ [:add_index, args]
181
218
  end
182
219
 
183
220
  alias :invert_add_belongs_to :invert_add_reference
184
221
  alias :invert_remove_belongs_to :invert_remove_reference
185
222
 
186
223
  def invert_change_column_default(args)
187
- table, column, options = *args
224
+ table, column, options = args
188
225
 
189
- unless options && options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
226
+ unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
190
227
  raise ActiveRecord::IrreversibleMigration, "change_column_default is only reversible if given a :from and :to option."
191
228
  end
192
229
 
@@ -198,29 +235,43 @@ module ActiveRecord
198
235
  [:change_column_null, args]
199
236
  end
200
237
 
201
- def invert_add_foreign_key(args)
202
- from_table, to_table, add_options = args
203
- add_options ||= {}
238
+ def invert_remove_foreign_key(args)
239
+ options = args.extract_options!
240
+ from_table, to_table = args
204
241
 
205
- if add_options[:name]
206
- options = { name: add_options[:name] }
207
- elsif add_options[:column]
208
- options = { column: add_options[:column] }
209
- else
210
- options = to_table
242
+ to_table ||= options.delete(:to_table)
243
+
244
+ raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil?
245
+
246
+ reversed_args = [from_table, to_table]
247
+ reversed_args << options unless options.empty?
248
+
249
+ [:add_foreign_key, reversed_args]
250
+ end
251
+
252
+ def invert_change_column_comment(args)
253
+ table, column, options = args
254
+
255
+ unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
256
+ raise ActiveRecord::IrreversibleMigration, "change_column_comment is only reversible if given a :from and :to option."
211
257
  end
212
258
 
213
- [:remove_foreign_key, [from_table, options]]
259
+ [:change_column_comment, [table, column, from: options[:to], to: options[:from]]]
214
260
  end
215
261
 
216
- def invert_remove_foreign_key(args)
217
- from_table, to_table, remove_options = args
218
- raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil? || to_table.is_a?(Hash)
262
+ def invert_change_table_comment(args)
263
+ table, options = args
219
264
 
220
- reversed_args = [from_table, to_table]
221
- reversed_args << remove_options if remove_options
265
+ unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
266
+ raise ActiveRecord::IrreversibleMigration, "change_table_comment is only reversible if given a :from and :to option."
267
+ end
222
268
 
223
- [:add_foreign_key, reversed_args]
269
+ [:change_table_comment, [table, from: options[:to], to: options[:from]]]
270
+ end
271
+
272
+ def invert_remove_check_constraint(args)
273
+ raise ActiveRecord::IrreversibleMigration, "remove_check_constraint is only reversible if given an expression." if args.size < 2
274
+ super
224
275
  end
225
276
 
226
277
  def respond_to_missing?(method, _)
@@ -235,6 +286,7 @@ module ActiveRecord
235
286
  super
236
287
  end
237
288
  end
289
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
238
290
  end
239
291
  end
240
292
  end