activerecord 7.1.5.2 → 8.1.2.1

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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -1,53 +1,55 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActiveRecord
4
6
  class DatabaseConfigurations
5
- # = Active Record Database Hash Config
7
+ # # Active Record Database Hash Config
6
8
  #
7
- # A +HashConfig+ object is created for each database configuration entry that
8
- # is created from a hash.
9
+ # A `HashConfig` object is created for each database configuration entry that is
10
+ # created from a hash.
9
11
  #
10
12
  # A hash config:
11
13
  #
12
- # { "development" => { "database" => "db_name" } }
14
+ # { "development" => { "database" => "db_name" } }
13
15
  #
14
16
  # Becomes:
15
17
  #
16
- # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
17
- # @env_name="development", @name="primary", @config={database: "db_name"}>
18
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
19
+ # @env_name="development", @name="primary", @config={database: "db_name"}>
18
20
  #
19
21
  # See ActiveRecord::DatabaseConfigurations for more info.
20
22
  class HashConfig < DatabaseConfig
21
23
  attr_reader :configuration_hash
22
24
 
23
-
24
- # Initialize a new +HashConfig+ object
25
+ # Initialize a new `HashConfig` object
25
26
  #
26
- # ==== Options
27
+ # #### Parameters
28
+ #
29
+ # * `env_name` - The Rails environment, i.e. "development".
30
+ # * `name` - The db config name. In a standard two-tier database configuration
31
+ # this will default to "primary". In a multiple database three-tier database
32
+ # configuration this corresponds to the name used in the second tier, for
33
+ # example "primary_readonly".
34
+ # * `configuration_hash` - The config hash. This is the hash that contains the
35
+ # database adapter, name, and other important information for database
36
+ # connections.
27
37
  #
28
- # * <tt>:env_name</tt> - The \Rails environment, i.e. "development".
29
- # * <tt>:name</tt> - The db config name. In a standard two-tier
30
- # database configuration this will default to "primary". In a multiple
31
- # database three-tier database configuration this corresponds to the name
32
- # used in the second tier, for example "primary_readonly".
33
- # * <tt>:config</tt> - The config hash. This is the hash that contains the
34
- # database adapter, name, and other important information for database
35
- # connections.
36
38
  def initialize(env_name, name, configuration_hash)
37
39
  super(env_name, name)
38
40
  @configuration_hash = configuration_hash.symbolize_keys.freeze
41
+ validate_configuration!
39
42
  end
40
43
 
41
44
  # Determines whether a database configuration is for a replica / readonly
42
- # connection. If the +replica+ key is present in the config, +replica?+ will
43
- # return +true+.
45
+ # connection. If the `replica` key is present in the config, `replica?` will
46
+ # return `true`.
44
47
  def replica?
45
48
  configuration_hash[:replica]
46
49
  end
47
50
 
48
- # The migrations paths for a database configuration. If the
49
- # +migrations_paths+ key is present in the config, +migrations_paths+
50
- # will return its value.
51
+ # The migrations paths for a database configuration. If the `migrations_paths`
52
+ # key is present in the config, `migrations_paths` will return its value.
51
53
  def migrations_paths
52
54
  configuration_hash[:migrations_paths]
53
55
  end
@@ -68,16 +70,35 @@ module ActiveRecord
68
70
  @configuration_hash = configuration_hash.merge(database: database).freeze
69
71
  end
70
72
 
71
- def pool
72
- (configuration_hash[:pool] || 5).to_i
73
+ def max_connections
74
+ max_connections = configuration_hash.fetch(:max_connections) {
75
+ configuration_hash.fetch(:pool, 5)
76
+ }&.to_i
77
+ max_connections if max_connections && max_connections >= 0
73
78
  end
74
79
 
80
+ def min_connections
81
+ (configuration_hash[:min_connections] || 0).to_i
82
+ end
83
+
84
+ alias :pool :max_connections
85
+ deprecate pool: :max_connections, deprecator: ActiveRecord.deprecator
86
+
75
87
  def min_threads
76
88
  (configuration_hash[:min_threads] || 0).to_i
77
89
  end
78
90
 
79
91
  def max_threads
80
- (configuration_hash[:max_threads] || pool).to_i
92
+ (configuration_hash[:max_threads] || (max_connections || 5).clamp(0, 5)).to_i
93
+ end
94
+
95
+ def max_age
96
+ v = configuration_hash[:max_age]&.to_i
97
+ if v && v > 0
98
+ v
99
+ else
100
+ Float::INFINITY
101
+ end
81
102
  end
82
103
 
83
104
  def query_cache
@@ -92,10 +113,8 @@ module ActiveRecord
92
113
  (configuration_hash[:checkout_timeout] || 5).to_f
93
114
  end
94
115
 
95
- # +reaping_frequency+ is configurable mostly for historical reasons, but it could
96
- # also be useful if someone wants a very low +idle_timeout+.
97
- def reaping_frequency
98
- configuration_hash.fetch(:reaping_frequency, 60)&.to_f
116
+ def reaping_frequency # :nodoc:
117
+ configuration_hash.fetch(:reaping_frequency, default_reaping_frequency)&.to_f
99
118
  end
100
119
 
101
120
  def idle_timeout
@@ -103,13 +122,17 @@ module ActiveRecord
103
122
  timeout if timeout > 0
104
123
  end
105
124
 
125
+ def keepalive
126
+ keepalive = (configuration_hash[:keepalive] || 600).to_f
127
+ keepalive if keepalive > 0
128
+ end
129
+
106
130
  def adapter
107
- configuration_hash[:adapter]
131
+ configuration_hash[:adapter]&.to_s
108
132
  end
109
133
 
110
- # The path to the schema cache dump file for a database.
111
- # If omitted, the filename will be read from ENV or a
112
- # default will be derived.
134
+ # The path to the schema cache dump file for a database. If omitted, the
135
+ # filename will be read from ENV or a default will be derived.
113
136
  def schema_cache_path
114
137
  configuration_hash[:schema_cache_path]
115
138
  end
@@ -130,15 +153,23 @@ module ActiveRecord
130
153
  Base.configurations.primary?(name)
131
154
  end
132
155
 
133
- # Determines whether to dump the schema/structure files and the
134
- # filename that should be used.
156
+ # Determines whether the db:prepare task should seed the database from db/seeds.rb.
135
157
  #
136
- # If +configuration_hash[:schema_dump]+ is set to +false+ or +nil+
137
- # the schema will not be dumped.
158
+ # If the `seeds` key is present in the config, `seeds?` will return its value. Otherwise, it
159
+ # will return `true` for the primary database and `false` for all other configs.
160
+ def seeds?
161
+ configuration_hash.fetch(:seeds, primary?)
162
+ end
163
+
164
+ # Determines whether to dump the schema/structure files and the filename that
165
+ # should be used.
138
166
  #
139
- # If the config option is set that will be used. Otherwise \Rails
140
- # will generate the filename from the database config name.
141
- def schema_dump(format = ActiveRecord.schema_format)
167
+ # If `configuration_hash[:schema_dump]` is set to `false` or `nil` the schema
168
+ # will not be dumped.
169
+ #
170
+ # If the config option is set that will be used. Otherwise Rails will generate
171
+ # the filename from the database config name.
172
+ def schema_dump(format = schema_format)
142
173
  if configuration_hash.key?(:schema_dump)
143
174
  if config = configuration_hash[:schema_dump]
144
175
  config
@@ -150,19 +181,50 @@ module ActiveRecord
150
181
  end
151
182
  end
152
183
 
184
+ def schema_format # :nodoc:
185
+ format = configuration_hash.fetch(:schema_format, ActiveRecord.schema_format).to_sym
186
+ raise "Invalid schema format" unless [:ruby, :sql].include?(format)
187
+ format
188
+ end
189
+
153
190
  def database_tasks? # :nodoc:
154
191
  !replica? && !!configuration_hash.fetch(:database_tasks, true)
155
192
  end
156
193
 
194
+ def use_metadata_table? # :nodoc:
195
+ configuration_hash.fetch(:use_metadata_table, true)
196
+ end
197
+
157
198
  private
158
199
  def schema_file_type(format)
159
- case format
200
+ case format.to_sym
160
201
  when :ruby
161
202
  "schema.rb"
162
203
  when :sql
163
204
  "structure.sql"
164
205
  end
165
206
  end
207
+
208
+ def default_reaping_frequency
209
+ # Reap every 20 seconds by default, but run more often as necessary to
210
+ # meet other configured timeouts.
211
+ [20, idle_timeout, max_age, keepalive].compact.min
212
+ end
213
+
214
+ def validate_configuration!
215
+ if configuration_hash[:pool] && configuration_hash[:max_connections]
216
+ pool_val = configuration_hash[:pool].to_i
217
+ max_conn_val = configuration_hash[:max_connections].to_i
218
+
219
+ if pool_val != max_conn_val
220
+ raise "Ambiguous configuration: 'pool' (#{pool_val}) and 'max_connections' (#{max_conn_val}) are set to different values. Prefer just 'max_connections'."
221
+ end
222
+ end
223
+
224
+ if configuration_hash[:pool] && configuration_hash[:min_connections]
225
+ raise "Ambiguous configuration: when setting 'min_connections', use 'max_connections' instead of 'pool'."
226
+ end
227
+ end
166
228
  end
167
229
  end
168
230
  end
@@ -41,10 +41,39 @@ module ActiveRecord
41
41
  super(env_name, name, configuration_hash)
42
42
 
43
43
  @url = url
44
- @configuration_hash = @configuration_hash.merge(build_url_hash).freeze
44
+ @configuration_hash = @configuration_hash.merge(build_url_hash)
45
+
46
+ if @configuration_hash[:schema_dump] == "false"
47
+ @configuration_hash[:schema_dump] = false
48
+ end
49
+
50
+ query_cache = parse_query_cache
51
+ @configuration_hash[:query_cache] = query_cache unless query_cache.nil?
52
+
53
+ to_boolean!(@configuration_hash, :replica)
54
+ to_boolean!(@configuration_hash, :database_tasks)
55
+
56
+ @configuration_hash.freeze
45
57
  end
46
58
 
47
59
  private
60
+ def parse_query_cache
61
+ case value = @configuration_hash[:query_cache]
62
+ when /\A\d+\z/
63
+ value.to_i
64
+ when "false"
65
+ false
66
+ else
67
+ value
68
+ end
69
+ end
70
+
71
+ def to_boolean!(configuration_hash, key)
72
+ if configuration_hash[key].is_a?(String)
73
+ configuration_hash[key] = configuration_hash[key] != "false"
74
+ end
75
+ end
76
+
48
77
  # Return a Hash that can be merged into the main config that represents
49
78
  # the passed in url
50
79
  def build_url_hash
@@ -36,9 +36,11 @@ module ActiveRecord
36
36
  # to respond to `sharded?`. To implement this define the following in an
37
37
  # initializer:
38
38
  #
39
- # ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config|
40
- # next unless config.key?(:vitess)
41
- # VitessConfig.new(env_name, name, config)
39
+ # ActiveSupport.on_load(:active_record_database_configurations) do
40
+ # ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config|
41
+ # next unless config.key?(:vitess)
42
+ # VitessConfig.new(env_name, name, config)
43
+ # end
42
44
  # end
43
45
  #
44
46
  # Note: applications must handle the condition in which custom config should be
@@ -113,7 +115,7 @@ module ActiveRecord
113
115
 
114
116
  if name
115
117
  configs.find do |db_config|
116
- db_config.name == name
118
+ db_config.name == name.to_s
117
119
  end
118
120
  else
119
121
  configs
@@ -306,4 +308,6 @@ module ActiveRecord
306
308
  url
307
309
  end
308
310
  end
311
+
312
+ ActiveSupport.run_load_hooks(:active_record_database_configurations, DatabaseConfigurations)
309
313
  end
@@ -113,6 +113,26 @@ module ActiveRecord
113
113
  # end
114
114
  # end
115
115
  #
116
+ # == Querying across records
117
+ #
118
+ # A consequence of delegated types is that querying attributes spread across multiple classes becomes slightly more
119
+ # tricky, but not impossible.
120
+ #
121
+ # The simplest method is to join the "superclass" to the "subclass" and apply the query parameters (i.e. <tt>#where</tt>)
122
+ # in appropriate places:
123
+ #
124
+ # Comment.joins(:entry).where(comments: { content: 'Hello!' }, entry: { creator: Current.user } )
125
+ #
126
+ # For convenience, add a scope on the concern. Now all classes that implement the concern will automatically include
127
+ # the method:
128
+ #
129
+ # # app/models/concerns/entryable.rb
130
+ # scope :with_entry, ->(attrs) { joins(:entry).where(entry: attrs) }
131
+ #
132
+ # Now the query can be shortened significantly:
133
+ #
134
+ # Comment.where(content: 'Hello!').with_entry(creator: Current.user)
135
+ #
116
136
  # == Adding further delegation
117
137
  #
118
138
  # The delegated type shouldn't just answer the question of what the underlying class is called. In fact, that's
@@ -161,16 +181,16 @@ module ActiveRecord
161
181
  # delegated_type :entryable, types: %w[ Message Comment ], dependent: :destroy
162
182
  # end
163
183
  #
164
- # Entry#entryable_class # => +Message+ or +Comment+
165
- # Entry#entryable_name # => "message" or "comment"
166
- # Entry.messages # => Entry.where(entryable_type: "Message")
167
- # Entry#message? # => true when entryable_type == "Message"
168
- # Entry#message # => returns the message record, when entryable_type == "Message", otherwise nil
169
- # Entry#message_id # => returns entryable_id, when entryable_type == "Message", otherwise nil
170
- # Entry.comments # => Entry.where(entryable_type: "Comment")
171
- # Entry#comment? # => true when entryable_type == "Comment"
172
- # Entry#comment # => returns the comment record, when entryable_type == "Comment", otherwise nil
173
- # Entry#comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil
184
+ # @entry.entryable_class # => Message or Comment
185
+ # @entry.entryable_name # => "message" or "comment"
186
+ # Entry.messages # => Entry.where(entryable_type: "Message")
187
+ # @entry.message? # => true when entryable_type == "Message"
188
+ # @entry.message # => returns the message record, when entryable_type == "Message", otherwise nil
189
+ # @entry.message_id # => returns entryable_id, when entryable_type == "Message", otherwise nil
190
+ # Entry.comments # => Entry.where(entryable_type: "Comment")
191
+ # @entry.comment? # => true when entryable_type == "Comment"
192
+ # @entry.comment # => returns the comment record, when entryable_type == "Comment", otherwise nil
193
+ # @entry.comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil
174
194
  #
175
195
  # You can also declare namespaced types:
176
196
  #
@@ -179,25 +199,25 @@ module ActiveRecord
179
199
  # end
180
200
  #
181
201
  # Entry.access_notice_messages
182
- # entry.access_notice_message
183
- # entry.access_notice_message?
202
+ # @entry.access_notice_message
203
+ # @entry.access_notice_message?
184
204
  #
185
- # === Options
205
+ # ==== Options
186
206
  #
187
207
  # The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
188
208
  # The following options can be included to specialize the behavior of the delegated type convenience methods.
189
209
  #
190
- # [:foreign_key]
210
+ # [+:foreign_key+]
191
211
  # Specify the foreign key used for the convenience methods. By default this is guessed to be the passed
192
212
  # +role+ with an "_id" suffix. So a class that defines a
193
213
  # <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_id" as
194
214
  # the default <tt>:foreign_key</tt>.
195
- # [:foreign_type]
215
+ # [+:foreign_type+]
196
216
  # Specify the column used to store the associated object's type. By default this is inferred to be the passed
197
217
  # +role+ with a "_type" suffix. A class that defines a
198
218
  # <tt>delegated_type :entryable, types: %w[ Message Comment ]</tt> association will use "entryable_type" as
199
219
  # the default <tt>:foreign_type</tt>.
200
- # [:primary_key]
220
+ # [+:primary_key+]
201
221
  # Specify the method that returns the primary key of associated object used for the convenience methods.
202
222
  # By default this is +id+.
203
223
  #
@@ -206,10 +226,10 @@ module ActiveRecord
206
226
  # delegated_type :entryable, types: %w[ Message Comment ], primary_key: :uuid, foreign_key: :entryable_uuid
207
227
  # end
208
228
  #
209
- # Entry#message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
210
- # Entry#comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
229
+ # @entry.message_uuid # => returns entryable_uuid, when entryable_type == "Message", otherwise nil
230
+ # @entry.comment_uuid # => returns entryable_uuid, when entryable_type == "Comment", otherwise nil
211
231
  def delegated_type(role, types:, **options)
212
- belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
232
+ belongs_to role, options.delete(:scope), **options, polymorphic: true
213
233
  define_delegated_type_methods role, types: types, options: options
214
234
  end
215
235
 
@@ -219,6 +239,10 @@ module ActiveRecord
219
239
  role_type = options[:foreign_type] || "#{role}_type"
220
240
  role_id = options[:foreign_key] || "#{role}_id"
221
241
 
242
+ define_singleton_method "#{role}_types" do
243
+ types.map(&:to_s)
244
+ end
245
+
222
246
  define_method "#{role}_class" do
223
247
  public_send(role_type).constantize
224
248
  end
@@ -7,114 +7,99 @@ module ActiveRecord
7
7
  if self == Base
8
8
  super
9
9
  else
10
- match = Method.match(self, name)
11
- match && match.valid? || super
10
+ super || begin
11
+ match = Method.match(name)
12
+ match && match.valid?(self, name)
13
+ end
12
14
  end
13
15
  end
14
16
 
15
- def method_missing(name, *arguments, &block)
16
- match = Method.match(self, name)
17
+ def method_missing(name, ...)
18
+ match = Method.match(name)
17
19
 
18
- if match && match.valid?
19
- match.define
20
- send(name, *arguments, &block)
20
+ if match && match.valid?(self, name)
21
+ match.define(self, name)
22
+ send(name, ...)
21
23
  else
22
24
  super
23
25
  end
24
26
  end
25
27
 
26
28
  class Method
27
- @matchers = []
28
-
29
29
  class << self
30
- attr_reader :matchers
31
-
32
- def match(model, name)
33
- klass = matchers.find { |k| k.pattern.match?(name) }
34
- klass.new(model, name) if klass
30
+ def match(name)
31
+ FindBy.match?(name) || FindByBang.match?(name)
35
32
  end
36
33
 
37
- def pattern
38
- @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/
34
+ def valid?(model, name)
35
+ attribute_names(model, name.to_s).all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
39
36
  end
40
37
 
41
- def prefix
42
- raise NotImplementedError
38
+ def define(model, name)
39
+ model.class_eval <<-CODE, __FILE__, __LINE__ + 1
40
+ def self.#{name}(#{signature(model, name)})
41
+ #{body(model, name)}
42
+ end
43
+ CODE
43
44
  end
44
45
 
45
- def suffix
46
- ""
47
- end
48
- end
46
+ private
47
+ def make_pattern(prefix, suffix)
48
+ /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/
49
+ end
49
50
 
50
- attr_reader :model, :name, :attribute_names
51
+ def attribute_names(model, name)
52
+ attribute_names = name.match(pattern)[1].split("_and_")
53
+ attribute_names.map! { |name| model.attribute_aliases[name] || name }
54
+ end
51
55
 
52
- def initialize(model, method_name)
53
- @model = model
54
- @name = method_name.to_s
55
- @attribute_names = @name.match(self.class.pattern)[1].split("_and_")
56
- @attribute_names.map! { |name| @model.attribute_aliases[name] || name }
57
- end
56
+ def body(model, method_name)
57
+ "#{finder}(#{attributes_hash(model, method_name)})"
58
+ end
58
59
 
59
- def valid?
60
- attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
61
- end
60
+ # The parameters in the signature may have reserved Ruby words, in order
61
+ # to prevent errors, we start each param name with `_`.
62
+ def signature(model, method_name)
63
+ attribute_names(model, method_name.to_s).map { |name| "_#{name}" }.join(", ")
64
+ end
62
65
 
63
- def define
64
- model.class_eval <<-CODE, __FILE__, __LINE__ + 1
65
- def self.#{name}(#{signature})
66
- #{body}
66
+ # Given that the parameters starts with `_`, the finder needs to use the
67
+ # same parameter name.
68
+ def attributes_hash(model, method_name)
69
+ "{" + attribute_names(model, method_name).map { |name| ":#{name} => _#{name}" }.join(",") + "}"
67
70
  end
68
- CODE
69
71
  end
72
+ end
70
73
 
71
- private
72
- def body
73
- "#{finder}(#{attributes_hash})"
74
- end
74
+ class FindBy < Method
75
+ @pattern = make_pattern("find_by", "")
75
76
 
76
- # The parameters in the signature may have reserved Ruby words, in order
77
- # to prevent errors, we start each param name with `_`.
78
- def signature
79
- attribute_names.map { |name| "_#{name}" }.join(", ")
80
- end
77
+ class << self
78
+ attr_reader :pattern
81
79
 
82
- # Given that the parameters starts with `_`, the finder needs to use the
83
- # same parameter name.
84
- def attributes_hash
85
- "{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(",") + "}"
80
+ def match?(name)
81
+ pattern.match?(name) && self
86
82
  end
87
83
 
88
84
  def finder
89
- raise NotImplementedError
85
+ "find_by"
90
86
  end
91
- end
92
-
93
- class FindBy < Method
94
- Method.matchers << self
95
-
96
- def self.prefix
97
- "find_by"
98
- end
99
-
100
- def finder
101
- "find_by"
102
87
  end
103
88
  end
104
89
 
105
90
  class FindByBang < Method
106
- Method.matchers << self
91
+ @pattern = make_pattern("find_by", "!")
107
92
 
108
- def self.prefix
109
- "find_by"
110
- end
93
+ class << self
94
+ attr_reader :pattern
111
95
 
112
- def self.suffix
113
- "!"
114
- end
96
+ def match?(name)
97
+ pattern.match?(name) && self
98
+ end
115
99
 
116
- def finder
117
- "find_by!"
100
+ def finder
101
+ "find_by!"
102
+ end
118
103
  end
119
104
  end
120
105
  end
@@ -8,7 +8,8 @@ module ActiveRecord
8
8
  class Config
9
9
  attr_accessor :primary_key, :deterministic_key, :store_key_references, :key_derivation_salt, :hash_digest_class,
10
10
  :support_unencrypted_data, :encrypt_fixtures, :validate_column_size, :add_to_filter_parameters,
11
- :excluded_from_filter_parameters, :extend_queries, :previous_schemes, :forced_encoding_for_deterministic_encryption
11
+ :excluded_from_filter_parameters, :extend_queries, :previous_schemes, :forced_encoding_for_deterministic_encryption,
12
+ :compressor
12
13
 
13
14
  def initialize
14
15
  set_defaults
@@ -55,6 +56,7 @@ module ActiveRecord
55
56
  self.previous_schemes = []
56
57
  self.forced_encoding_for_deterministic_encryption = Encoding::UTF_8
57
58
  self.hash_digest_class = OpenSSL::Digest::SHA1
59
+ self.compressor = Zlib
58
60
 
59
61
  # TODO: Setting to false for now as the implementation is a bit experimental
60
62
  self.extend_queries = false