activerecord 6.1.7 → 7.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2030 -1020
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +17 -14
  6. data/lib/active_record/association_relation.rb +1 -11
  7. data/lib/active_record/associations/association.rb +51 -19
  8. data/lib/active_record/associations/association_scope.rb +17 -12
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -9
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +11 -5
  12. data/lib/active_record/associations/builder/belongs_to.rb +40 -14
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  15. data/lib/active_record/associations/builder/has_many.rb +3 -2
  16. data/lib/active_record/associations/builder/has_one.rb +2 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  18. data/lib/active_record/associations/collection_association.rb +39 -35
  19. data/lib/active_record/associations/collection_proxy.rb +30 -15
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  21. data/lib/active_record/associations/foreign_association.rb +10 -3
  22. data/lib/active_record/associations/has_many_association.rb +28 -18
  23. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  24. data/lib/active_record/associations/has_one_association.rb +20 -10
  25. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  27. data/lib/active_record/associations/join_dependency.rb +28 -20
  28. data/lib/active_record/associations/preloader/association.rb +210 -52
  29. data/lib/active_record/associations/preloader/batch.rb +48 -0
  30. data/lib/active_record/associations/preloader/branch.rb +147 -0
  31. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  32. data/lib/active_record/associations/preloader.rb +50 -121
  33. data/lib/active_record/associations/singular_association.rb +9 -3
  34. data/lib/active_record/associations/through_association.rb +25 -14
  35. data/lib/active_record/associations.rb +446 -306
  36. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  37. data/lib/active_record/attribute_assignment.rb +1 -3
  38. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  39. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  40. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  41. data/lib/active_record/attribute_methods/query.rb +31 -19
  42. data/lib/active_record/attribute_methods/read.rb +27 -12
  43. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
  45. data/lib/active_record/attribute_methods/write.rb +12 -15
  46. data/lib/active_record/attribute_methods.rb +161 -40
  47. data/lib/active_record/attributes.rb +27 -38
  48. data/lib/active_record/autosave_association.rb +65 -31
  49. data/lib/active_record/base.rb +25 -2
  50. data/lib/active_record/callbacks.rb +18 -34
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -46
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +113 -597
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +172 -50
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -27
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +367 -141
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -150
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +317 -164
  70. data/lib/active_record/connection_adapters/column.rb +13 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +39 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +112 -55
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +89 -52
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  89. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  91. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +397 -75
  101. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  102. data/lib/active_record/connection_adapters/postgresql_adapter.rb +508 -246
  103. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  104. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  105. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +72 -53
  106. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  107. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  108. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  109. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +296 -104
  110. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  111. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  112. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  113. data/lib/active_record/connection_adapters.rb +9 -6
  114. data/lib/active_record/connection_handling.rb +108 -137
  115. data/lib/active_record/core.rb +242 -233
  116. data/lib/active_record/counter_cache.rb +52 -27
  117. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -2
  118. data/lib/active_record/database_configurations/database_config.rb +21 -12
  119. data/lib/active_record/database_configurations/hash_config.rb +88 -16
  120. data/lib/active_record/database_configurations/url_config.rb +18 -12
  121. data/lib/active_record/database_configurations.rb +95 -59
  122. data/lib/active_record/delegated_type.rb +66 -20
  123. data/lib/active_record/deprecator.rb +7 -0
  124. data/lib/active_record/destroy_association_async_job.rb +4 -2
  125. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  126. data/lib/active_record/dynamic_matchers.rb +1 -1
  127. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  128. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  129. data/lib/active_record/encryption/cipher.rb +53 -0
  130. data/lib/active_record/encryption/config.rb +68 -0
  131. data/lib/active_record/encryption/configurable.rb +60 -0
  132. data/lib/active_record/encryption/context.rb +42 -0
  133. data/lib/active_record/encryption/contexts.rb +76 -0
  134. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  135. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  136. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  137. data/lib/active_record/encryption/encrypted_attribute_type.rb +155 -0
  138. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  139. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  140. data/lib/active_record/encryption/encryptor.rb +155 -0
  141. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  142. data/lib/active_record/encryption/errors.rb +15 -0
  143. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  144. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  145. data/lib/active_record/encryption/key.rb +28 -0
  146. data/lib/active_record/encryption/key_generator.rb +53 -0
  147. data/lib/active_record/encryption/key_provider.rb +46 -0
  148. data/lib/active_record/encryption/message.rb +33 -0
  149. data/lib/active_record/encryption/message_serializer.rb +92 -0
  150. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  151. data/lib/active_record/encryption/properties.rb +76 -0
  152. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  153. data/lib/active_record/encryption/scheme.rb +100 -0
  154. data/lib/active_record/encryption.rb +58 -0
  155. data/lib/active_record/enum.rb +154 -63
  156. data/lib/active_record/errors.rb +172 -15
  157. data/lib/active_record/explain.rb +23 -3
  158. data/lib/active_record/explain_registry.rb +11 -6
  159. data/lib/active_record/explain_subscriber.rb +1 -1
  160. data/lib/active_record/fixture_set/file.rb +15 -1
  161. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  162. data/lib/active_record/fixture_set/render_context.rb +2 -0
  163. data/lib/active_record/fixture_set/table_row.rb +70 -14
  164. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  165. data/lib/active_record/fixtures.rb +147 -86
  166. data/lib/active_record/future_result.rb +174 -0
  167. data/lib/active_record/gem_version.rb +3 -3
  168. data/lib/active_record/inheritance.rb +81 -29
  169. data/lib/active_record/insert_all.rb +135 -22
  170. data/lib/active_record/integration.rb +11 -10
  171. data/lib/active_record/internal_metadata.rb +119 -33
  172. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  173. data/lib/active_record/locking/optimistic.rb +37 -22
  174. data/lib/active_record/locking/pessimistic.rb +15 -6
  175. data/lib/active_record/log_subscriber.rb +52 -19
  176. data/lib/active_record/marshalling.rb +59 -0
  177. data/lib/active_record/message_pack.rb +124 -0
  178. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  179. data/lib/active_record/middleware/database_selector.rb +23 -13
  180. data/lib/active_record/middleware/shard_selector.rb +62 -0
  181. data/lib/active_record/migration/command_recorder.rb +112 -14
  182. data/lib/active_record/migration/compatibility.rb +233 -46
  183. data/lib/active_record/migration/default_strategy.rb +23 -0
  184. data/lib/active_record/migration/execution_strategy.rb +19 -0
  185. data/lib/active_record/migration/join_table.rb +1 -1
  186. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  187. data/lib/active_record/migration.rb +361 -173
  188. data/lib/active_record/model_schema.rb +125 -101
  189. data/lib/active_record/nested_attributes.rb +50 -20
  190. data/lib/active_record/no_touching.rb +3 -3
  191. data/lib/active_record/normalization.rb +167 -0
  192. data/lib/active_record/persistence.rb +409 -88
  193. data/lib/active_record/promise.rb +84 -0
  194. data/lib/active_record/query_cache.rb +4 -22
  195. data/lib/active_record/query_logs.rb +174 -0
  196. data/lib/active_record/query_logs_formatter.rb +41 -0
  197. data/lib/active_record/querying.rb +29 -6
  198. data/lib/active_record/railtie.rb +220 -44
  199. data/lib/active_record/railties/controller_runtime.rb +15 -10
  200. data/lib/active_record/railties/databases.rake +188 -252
  201. data/lib/active_record/railties/job_runtime.rb +23 -0
  202. data/lib/active_record/readonly_attributes.rb +41 -3
  203. data/lib/active_record/reflection.rb +248 -81
  204. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  205. data/lib/active_record/relation/batches.rb +192 -63
  206. data/lib/active_record/relation/calculations.rb +246 -90
  207. data/lib/active_record/relation/delegation.rb +28 -14
  208. data/lib/active_record/relation/finder_methods.rb +108 -51
  209. data/lib/active_record/relation/merger.rb +22 -13
  210. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  211. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  212. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  213. data/lib/active_record/relation/predicate_builder.rb +27 -20
  214. data/lib/active_record/relation/query_attribute.rb +30 -12
  215. data/lib/active_record/relation/query_methods.rb +670 -129
  216. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  217. data/lib/active_record/relation/spawn_methods.rb +20 -3
  218. data/lib/active_record/relation/where_clause.rb +10 -19
  219. data/lib/active_record/relation.rb +287 -120
  220. data/lib/active_record/result.rb +37 -11
  221. data/lib/active_record/runtime_registry.rb +32 -13
  222. data/lib/active_record/sanitization.rb +65 -20
  223. data/lib/active_record/schema.rb +36 -22
  224. data/lib/active_record/schema_dumper.rb +73 -24
  225. data/lib/active_record/schema_migration.rb +68 -33
  226. data/lib/active_record/scoping/default.rb +72 -15
  227. data/lib/active_record/scoping/named.rb +5 -13
  228. data/lib/active_record/scoping.rb +65 -34
  229. data/lib/active_record/secure_password.rb +60 -0
  230. data/lib/active_record/secure_token.rb +21 -3
  231. data/lib/active_record/serialization.rb +6 -1
  232. data/lib/active_record/signed_id.rb +10 -8
  233. data/lib/active_record/store.rb +10 -10
  234. data/lib/active_record/suppressor.rb +13 -15
  235. data/lib/active_record/table_metadata.rb +16 -3
  236. data/lib/active_record/tasks/database_tasks.rb +251 -140
  237. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  238. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  239. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  240. data/lib/active_record/test_databases.rb +1 -1
  241. data/lib/active_record/test_fixtures.rb +117 -96
  242. data/lib/active_record/timestamp.rb +32 -19
  243. data/lib/active_record/token_for.rb +113 -0
  244. data/lib/active_record/touch_later.rb +11 -6
  245. data/lib/active_record/transactions.rb +48 -27
  246. data/lib/active_record/translation.rb +3 -3
  247. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  248. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  249. data/lib/active_record/type/internal/timezone.rb +7 -2
  250. data/lib/active_record/type/serialized.rb +9 -5
  251. data/lib/active_record/type/time.rb +4 -0
  252. data/lib/active_record/type/type_map.rb +17 -20
  253. data/lib/active_record/type.rb +1 -2
  254. data/lib/active_record/validations/absence.rb +1 -1
  255. data/lib/active_record/validations/associated.rb +4 -4
  256. data/lib/active_record/validations/numericality.rb +5 -4
  257. data/lib/active_record/validations/presence.rb +5 -28
  258. data/lib/active_record/validations/uniqueness.rb +51 -6
  259. data/lib/active_record/validations.rb +8 -4
  260. data/lib/active_record/version.rb +1 -1
  261. data/lib/active_record.rb +335 -32
  262. data/lib/arel/attributes/attribute.rb +0 -8
  263. data/lib/arel/crud.rb +28 -22
  264. data/lib/arel/delete_manager.rb +18 -4
  265. data/lib/arel/errors.rb +10 -0
  266. data/lib/arel/factory_methods.rb +4 -0
  267. data/lib/arel/filter_predications.rb +9 -0
  268. data/lib/arel/insert_manager.rb +2 -3
  269. data/lib/arel/nodes/and.rb +4 -0
  270. data/lib/arel/nodes/binary.rb +6 -1
  271. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  272. data/lib/arel/nodes/casted.rb +1 -1
  273. data/lib/arel/nodes/cte.rb +36 -0
  274. data/lib/arel/nodes/delete_statement.rb +12 -13
  275. data/lib/arel/nodes/filter.rb +10 -0
  276. data/lib/arel/nodes/fragments.rb +35 -0
  277. data/lib/arel/nodes/function.rb +1 -0
  278. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  279. data/lib/arel/nodes/insert_statement.rb +2 -2
  280. data/lib/arel/nodes/leading_join.rb +8 -0
  281. data/lib/arel/nodes/node.rb +111 -2
  282. data/lib/arel/nodes/select_core.rb +2 -2
  283. data/lib/arel/nodes/select_statement.rb +2 -2
  284. data/lib/arel/nodes/sql_literal.rb +6 -0
  285. data/lib/arel/nodes/table_alias.rb +4 -0
  286. data/lib/arel/nodes/update_statement.rb +8 -3
  287. data/lib/arel/nodes.rb +5 -0
  288. data/lib/arel/predications.rb +13 -3
  289. data/lib/arel/select_manager.rb +10 -4
  290. data/lib/arel/table.rb +9 -6
  291. data/lib/arel/tree_manager.rb +5 -13
  292. data/lib/arel/update_manager.rb +18 -4
  293. data/lib/arel/visitors/dot.rb +80 -90
  294. data/lib/arel/visitors/mysql.rb +16 -3
  295. data/lib/arel/visitors/postgresql.rb +0 -10
  296. data/lib/arel/visitors/to_sql.rb +141 -20
  297. data/lib/arel/visitors/visitor.rb +2 -2
  298. data/lib/arel.rb +18 -3
  299. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  300. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  301. data/lib/rails/generators/active_record/migration.rb +3 -1
  302. data/lib/rails/generators/active_record/model/USAGE +113 -0
  303. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  304. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  305. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  306. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  307. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  308. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  309. metadata +96 -16
  310. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  311. data/lib/active_record/null_relation.rb +0 -67
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module MessagePack # :nodoc:
5
+ FORMAT_VERSION = 1
6
+
7
+ class << self
8
+ def dump(input)
9
+ encoder = Encoder.new
10
+ [FORMAT_VERSION, encoder.encode(input), encoder.entries]
11
+ end
12
+
13
+ def load(dumped)
14
+ format_version, top_level, entries = dumped
15
+ unless format_version == FORMAT_VERSION
16
+ raise "Invalid format version: #{format_version.inspect}"
17
+ end
18
+ Decoder.new(entries).decode(top_level)
19
+ end
20
+ end
21
+
22
+ module Extensions
23
+ extend self
24
+
25
+ def install(registry)
26
+ registry.register_type 119, ActiveModel::Type::Binary::Data,
27
+ packer: :to_s,
28
+ unpacker: :new
29
+
30
+ registry.register_type 120, ActiveRecord::Base,
31
+ packer: method(:write_record),
32
+ unpacker: method(:read_record),
33
+ recursive: true
34
+ end
35
+
36
+ def write_record(record, packer)
37
+ packer.write(ActiveRecord::MessagePack.dump(record))
38
+ end
39
+
40
+ def read_record(unpacker)
41
+ ActiveRecord::MessagePack.load(unpacker.read)
42
+ end
43
+ end
44
+
45
+ class Encoder
46
+ attr_reader :entries
47
+
48
+ def initialize
49
+ @entries = []
50
+ @refs = {}.compare_by_identity
51
+ end
52
+
53
+ def encode(input)
54
+ if input.is_a?(Array)
55
+ input.map { |record| encode_record(record) }
56
+ elsif input
57
+ encode_record(input)
58
+ end
59
+ end
60
+
61
+ def encode_record(record)
62
+ ref = @refs[record]
63
+
64
+ if !ref
65
+ ref = @refs[record] = @entries.size
66
+ @entries << build_entry(record)
67
+ add_cached_associations(record, @entries.last)
68
+ end
69
+
70
+ ref
71
+ end
72
+
73
+ def build_entry(record)
74
+ [
75
+ ActiveSupport::MessagePack::Extensions.dump_class(record.class),
76
+ record.attributes_for_database,
77
+ record.new_record?
78
+ ]
79
+ end
80
+
81
+ def add_cached_associations(record, entry)
82
+ record.class.reflections.each_value do |reflection|
83
+ if record.association_cached?(reflection.name) && record.association(reflection.name).loaded?
84
+ entry << reflection.name << encode(record.association(reflection.name).target)
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ class Decoder
91
+ def initialize(entries)
92
+ @records = entries.map { |entry| build_record(entry) }
93
+ @records.zip(entries) { |record, entry| resolve_cached_associations(record, entry) }
94
+ end
95
+
96
+ def decode(ref)
97
+ if ref.is_a?(Array)
98
+ ref.map { |r| @records[r] }
99
+ elsif ref
100
+ @records[ref]
101
+ end
102
+ end
103
+
104
+ def build_record(entry)
105
+ class_name, attributes_hash, is_new_record, * = entry
106
+ klass = ActiveSupport::MessagePack::Extensions.load_class(class_name)
107
+ attributes = klass.attributes_builder.build_from_database(attributes_hash)
108
+ klass.allocate.init_with_attributes(attributes, is_new_record)
109
+ end
110
+
111
+ def resolve_cached_associations(record, entry)
112
+ i = 3 # entry == [class_name, attributes_hash, is_new_record, *associations]
113
+ while i < entry.length
114
+ begin
115
+ record.association(entry[i]).target = decode(entry[i + 1])
116
+ rescue ActiveRecord::AssociationNotFoundError
117
+ # The association no longer exists, so just skip it.
118
+ end
119
+ i += 2
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -48,25 +48,25 @@ module ActiveRecord
48
48
  context.save(response)
49
49
  end
50
50
 
51
+ def reading_request?(request)
52
+ request.get? || request.head?
53
+ end
54
+
51
55
  private
52
56
  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
+ ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role, prevent_writes: true) do
58
+ instrumenter.instrument("database_selector.active_record.read_from_primary", &blk)
57
59
  end
58
60
  end
59
61
 
60
62
  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
63
+ ActiveRecord::Base.connected_to(role: ActiveRecord.reading_role, prevent_writes: true) do
64
+ instrumenter.instrument("database_selector.active_record.read_from_replica", &blk)
65
65
  end
66
66
  end
67
67
 
68
- def write_to_primary(&blk)
69
- ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: false) do
68
+ def write_to_primary
69
+ ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role, prevent_writes: false) do
70
70
  instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
71
71
  yield
72
72
  ensure
@@ -4,8 +4,10 @@ require "active_record/middleware/database_selector/resolver"
4
4
 
5
5
  module ActiveRecord
6
6
  module Middleware
7
+ # = Database Selector \Middleware
8
+ #
7
9
  # The DatabaseSelector Middleware provides a framework for automatically
8
- # swapping from the primary to the replica database connection. Rails
10
+ # swapping from the primary to the replica database connection. \Rails
9
11
  # provides a basic framework to determine when to swap and allows for
10
12
  # applications to write custom strategy classes to override the default
11
13
  # behavior.
@@ -15,18 +17,26 @@ module ActiveRecord
15
17
  # resolver context class that sets a value that helps the resolver class
16
18
  # decide when to switch.
17
19
  #
18
- # Rails default middleware uses the request's session to set a timestamp
20
+ # \Rails default middleware uses the request's session to set a timestamp
19
21
  # that informs the application when to read from a primary or read from a
20
22
  # replica.
21
23
  #
22
- # To use the DatabaseSelector in your application with default settings add
23
- # the following options to your environment config:
24
+ # To use the DatabaseSelector in your application with default settings,
25
+ # run the provided generator.
24
26
  #
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
27
+ # $ bin/rails g active_record:multi_db
28
+ #
29
+ # This will create a file named +config/initializers/multi_db.rb+ with the
30
+ # following contents:
31
+ #
32
+ # Rails.application.configure do
33
+ # config.active_record.database_selector = { delay: 2.seconds }
34
+ # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
35
+ # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
36
+ # end
28
37
  #
29
- # New applications will include these lines commented out in the production.rb.
38
+ # Alternatively you can set the options in your environment config or
39
+ # any other config file loaded on boot.
30
40
  #
31
41
  # The default behavior can be changed by setting the config options to a
32
42
  # custom class:
@@ -34,6 +44,10 @@ module ActiveRecord
34
44
  # config.active_record.database_selector = { delay: 2.seconds }
35
45
  # config.active_record.database_resolver = MyResolver
36
46
  # config.active_record.database_resolver_context = MyResolver::MySession
47
+ #
48
+ # Note: If you are using <tt>rails new my_app --minimal</tt> you will need
49
+ # to call <tt>require "active_support/core_ext/integer/time"</tt> to load
50
+ # the core extension in order to use +2.seconds+
37
51
  class DatabaseSelector
38
52
  def initialize(app, resolver_klass = nil, context_klass = nil, options = {})
39
53
  @app = app
@@ -59,7 +73,7 @@ module ActiveRecord
59
73
  context = context_klass.call(request)
60
74
  resolver = resolver_klass.call(context, options)
61
75
 
62
- response = if reading_request?(request)
76
+ response = if resolver.reading_request?(request)
63
77
  resolver.read(&blk)
64
78
  else
65
79
  resolver.write(&blk)
@@ -68,10 +82,6 @@ module ActiveRecord
68
82
  resolver.update_context(response)
69
83
  response
70
84
  end
71
-
72
- def reading_request?(request)
73
- request.get? || request.head?
74
- end
75
85
  end
76
86
  end
77
87
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Middleware
5
+ # = Shard Selector \Middleware
6
+ #
7
+ # The ShardSelector Middleware provides a framework for automatically
8
+ # swapping shards. \Rails provides a basic framework to determine which
9
+ # shard to switch to and allows for applications to write custom strategies
10
+ # for swapping if needed.
11
+ #
12
+ # The ShardSelector takes a set of options (currently only +lock+ is supported)
13
+ # that can be used by the middleware to alter behavior. +lock+ is
14
+ # true by default and will prohibit the request from switching shards once
15
+ # inside the block. If +lock+ is false, then shard swapping will be allowed.
16
+ # For tenant based sharding, +lock+ should always be true to prevent application
17
+ # code from mistakenly switching between tenants.
18
+ #
19
+ # Options can be set in the config:
20
+ #
21
+ # config.active_record.shard_selector = { lock: true }
22
+ #
23
+ # Applications must also provide the code for the resolver as it depends on application
24
+ # specific models. An example resolver would look like this:
25
+ #
26
+ # config.active_record.shard_resolver = ->(request) {
27
+ # subdomain = request.subdomain
28
+ # tenant = Tenant.find_by_subdomain!(subdomain)
29
+ # tenant.shard
30
+ # }
31
+ class ShardSelector
32
+ def initialize(app, resolver, options = {})
33
+ @app = app
34
+ @resolver = resolver
35
+ @options = options
36
+ end
37
+
38
+ attr_reader :resolver, :options
39
+
40
+ def call(env)
41
+ request = ActionDispatch::Request.new(env)
42
+
43
+ shard = selected_shard(request)
44
+
45
+ set_shard(shard) do
46
+ @app.call(env)
47
+ end
48
+ end
49
+
50
+ private
51
+ def selected_shard(request)
52
+ resolver.call(request)
53
+ end
54
+
55
+ def set_shard(shard, &block)
56
+ ActiveRecord::Base.connected_to(shard: shard.to_sym) do
57
+ ActiveRecord::Base.prohibit_shard_swapping(options.fetch(:lock, true), &block)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -2,35 +2,44 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class Migration
5
- # <tt>ActiveRecord::Migration::CommandRecorder</tt> records commands done during
5
+ # = \Migration Command Recorder
6
+ #
7
+ # +ActiveRecord::Migration::CommandRecorder+ records commands done during
6
8
  # a migration and knows how to reverse those commands. The CommandRecorder
7
9
  # knows how to invert the following commands:
8
10
  #
9
11
  # * add_column
10
12
  # * add_foreign_key
11
13
  # * add_check_constraint
14
+ # * add_exclusion_constraint
15
+ # * add_unique_constraint
12
16
  # * add_index
13
17
  # * add_reference
14
18
  # * add_timestamps
15
- # * change_column
16
- # * change_column_default (must supply a :from and :to option)
19
+ # * change_column_default (must supply a +:from+ and +:to+ option)
17
20
  # * 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)
21
+ # * change_column_comment (must supply a +:from+ and +:to+ option)
22
+ # * change_table_comment (must supply a +:from+ and +:to+ option)
23
+ # * create_enum
20
24
  # * create_join_table
21
25
  # * create_table
22
26
  # * disable_extension
27
+ # * drop_enum (must supply a list of values)
23
28
  # * drop_join_table
24
29
  # * drop_table (must supply a block)
25
30
  # * enable_extension
26
31
  # * remove_column (must supply a type)
27
- # * remove_columns (must specify at least one column name or more)
32
+ # * remove_columns (must supply a +:type+ option)
28
33
  # * remove_foreign_key (must supply a second table)
29
34
  # * remove_check_constraint
35
+ # * remove_exclusion_constraint
36
+ # * remove_unique_constraint
30
37
  # * remove_index
31
38
  # * remove_reference
32
39
  # * remove_timestamps
33
40
  # * rename_column
41
+ # * rename_enum (must supply a +:to+ option)
42
+ # * rename_enum_value (must supply a +:from+ and +:to+ option)
34
43
  # * rename_index
35
44
  # * rename_table
36
45
  class CommandRecorder
@@ -42,7 +51,10 @@ module ActiveRecord
42
51
  :change_column, :execute, :remove_columns, :change_column_null,
43
52
  :add_foreign_key, :remove_foreign_key,
44
53
  :change_column_comment, :change_table_comment,
45
- :add_check_constraint, :remove_check_constraint
54
+ :add_check_constraint, :remove_check_constraint,
55
+ :add_exclusion_constraint, :remove_exclusion_constraint,
56
+ :add_unique_constraint, :remove_unique_constraint,
57
+ :create_enum, :drop_enum, :rename_enum, :add_enum_value, :rename_enum_value,
46
58
  ]
47
59
  include JoinTable
48
60
 
@@ -112,13 +124,21 @@ module ActiveRecord
112
124
  record(:"#{method}", args, &block) # record(:create_table, args, &block)
113
125
  end # end
114
126
  EOV
115
- ruby2_keywords(method) if respond_to?(:ruby2_keywords, true)
127
+ ruby2_keywords(method)
116
128
  end
117
129
  alias :add_belongs_to :add_reference
118
130
  alias :remove_belongs_to :remove_reference
119
131
 
120
132
  def change_table(table_name, **options) # :nodoc:
121
- yield delegate.update_table_definition(table_name, self)
133
+ if delegate.supports_bulk_alter? && options[:bulk]
134
+ recorder = self.class.new(self.delegate)
135
+ recorder.reverting = @reverting
136
+ yield recorder.delegate.update_table_definition(table_name, recorder)
137
+ commands = recorder.commands
138
+ @commands << [:change_table, [table_name], -> t { bulk_change_table(table_name, commands) }]
139
+ else
140
+ yield delegate.update_table_definition(table_name, self)
141
+ end
122
142
  end
123
143
 
124
144
  def replay(migration)
@@ -140,7 +160,10 @@ module ActiveRecord
140
160
  add_reference: :remove_reference,
141
161
  add_foreign_key: :remove_foreign_key,
142
162
  add_check_constraint: :remove_check_constraint,
143
- enable_extension: :disable_extension
163
+ add_exclusion_constraint: :remove_exclusion_constraint,
164
+ add_unique_constraint: :remove_unique_constraint,
165
+ enable_extension: :disable_extension,
166
+ create_enum: :drop_enum
144
167
  }.each do |cmd, inv|
145
168
  [[inv, cmd], [cmd, inv]].uniq.each do |method, inverse|
146
169
  class_eval <<-EOV, __FILE__, __LINE__ + 1
@@ -154,9 +177,9 @@ module ActiveRecord
154
177
 
155
178
  include StraightReversions
156
179
 
157
- def invert_transaction(args)
180
+ def invert_transaction(args, &block)
158
181
  sub_recorder = CommandRecorder.new(delegate)
159
- sub_recorder.revert { yield }
182
+ sub_recorder.revert(&block)
160
183
 
161
184
  invertions_proc = proc {
162
185
  sub_recorder.replay(self)
@@ -165,7 +188,17 @@ module ActiveRecord
165
188
  [:transaction, args, invertions_proc]
166
189
  end
167
190
 
191
+ def invert_create_table(args, &block)
192
+ if args.last.is_a?(Hash)
193
+ args.last.delete(:if_not_exists)
194
+ end
195
+ super
196
+ end
197
+
168
198
  def invert_drop_table(args, &block)
199
+ if args.last.is_a?(Hash)
200
+ args.last.delete(:if_exists)
201
+ end
169
202
  if args.size == 1 && block == nil
170
203
  raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)."
171
204
  end
@@ -173,7 +206,10 @@ module ActiveRecord
173
206
  end
174
207
 
175
208
  def invert_rename_table(args)
176
- [:rename_table, args.reverse]
209
+ old_name, new_name, options = args
210
+ args = [new_name, old_name]
211
+ args << options if options
212
+ [:rename_table, args]
177
213
  end
178
214
 
179
215
  def invert_remove_column(args)
@@ -235,6 +271,11 @@ module ActiveRecord
235
271
  [:change_column_null, args]
236
272
  end
237
273
 
274
+ def invert_add_foreign_key(args)
275
+ args.last.delete(:validate) if args.last.is_a?(Hash)
276
+ super
277
+ end
278
+
238
279
  def invert_remove_foreign_key(args)
239
280
  options = args.extract_options!
240
281
  from_table, to_table = args
@@ -269,11 +310,68 @@ module ActiveRecord
269
310
  [:change_table_comment, [table, from: options[:to], to: options[:from]]]
270
311
  end
271
312
 
313
+ def invert_add_check_constraint(args)
314
+ if (options = args.last).is_a?(Hash)
315
+ options.delete(:validate)
316
+ options[:if_exists] = options.delete(:if_not_exists) if options.key?(:if_not_exists)
317
+ end
318
+ super
319
+ end
320
+
272
321
  def invert_remove_check_constraint(args)
273
322
  raise ActiveRecord::IrreversibleMigration, "remove_check_constraint is only reversible if given an expression." if args.size < 2
323
+
324
+ if (options = args.last).is_a?(Hash)
325
+ options[:if_not_exists] = options.delete(:if_exists) if options.key?(:if_exists)
326
+ end
327
+ super
328
+ end
329
+
330
+ def invert_remove_exclusion_constraint(args)
331
+ raise ActiveRecord::IrreversibleMigration, "remove_exclusion_constraint is only reversible if given an expression." if args.size < 2
332
+ super
333
+ end
334
+
335
+ def invert_add_unique_constraint(args)
336
+ options = args.dup.extract_options!
337
+
338
+ raise ActiveRecord::IrreversibleMigration, "add_unique_constraint is not reversible if given an using_index." if options[:using_index]
274
339
  super
275
340
  end
276
341
 
342
+ def invert_remove_unique_constraint(args)
343
+ _table, columns = args.dup.tap(&:extract_options!)
344
+
345
+ raise ActiveRecord::IrreversibleMigration, "remove_unique_constraint is only reversible if given an column_name." if columns.blank?
346
+ super
347
+ end
348
+
349
+ def invert_drop_enum(args)
350
+ _enum, values = args.dup.tap(&:extract_options!)
351
+ raise ActiveRecord::IrreversibleMigration, "drop_enum is only reversible if given a list of enum values." unless values
352
+ super
353
+ end
354
+
355
+ def invert_rename_enum(args)
356
+ name, options = args
357
+
358
+ unless options.is_a?(Hash) && options.has_key?(:to)
359
+ raise ActiveRecord::IrreversibleMigration, "rename_enum is only reversible if given a :to option."
360
+ end
361
+
362
+ [:rename_enum, [options[:to], to: name]]
363
+ end
364
+
365
+ def invert_rename_enum_value(args)
366
+ type_name, options = args
367
+
368
+ unless options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
369
+ raise ActiveRecord::IrreversibleMigration, "rename_enum_value is only reversible if given a :from and :to option."
370
+ end
371
+
372
+ [:rename_enum_value, [type_name, from: options[:to], to: options[:from]]]
373
+ end
374
+
277
375
  def respond_to_missing?(method, _)
278
376
  super || delegate.respond_to?(method)
279
377
  end
@@ -286,7 +384,7 @@ module ActiveRecord
286
384
  super
287
385
  end
288
386
  end
289
- ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
387
+ ruby2_keywords(:method_missing)
290
388
  end
291
389
  end
292
390
  end