activerecord 6.0.0.beta1 → 6.0.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 (156) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +455 -9
  3. data/README.rdoc +3 -1
  4. data/lib/active_record/associations/association.rb +18 -1
  5. data/lib/active_record/associations/builder/association.rb +14 -18
  6. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  7. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  8. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  9. data/lib/active_record/associations/builder/has_many.rb +2 -0
  10. data/lib/active_record/associations/builder/has_one.rb +35 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  12. data/lib/active_record/associations/collection_association.rb +5 -6
  13. data/lib/active_record/associations/collection_proxy.rb +13 -42
  14. data/lib/active_record/associations/has_many_association.rb +1 -9
  15. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  16. data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
  17. data/lib/active_record/associations/join_dependency.rb +10 -9
  18. data/lib/active_record/associations/preloader/association.rb +37 -34
  19. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  20. data/lib/active_record/associations/preloader.rb +11 -6
  21. data/lib/active_record/associations.rb +3 -2
  22. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  23. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  24. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  25. data/lib/active_record/attribute_methods/query.rb +2 -3
  26. data/lib/active_record/attribute_methods/read.rb +3 -9
  27. data/lib/active_record/attribute_methods/write.rb +6 -12
  28. data/lib/active_record/attribute_methods.rb +3 -53
  29. data/lib/active_record/attributes.rb +13 -0
  30. data/lib/active_record/autosave_association.rb +15 -5
  31. data/lib/active_record/base.rb +0 -1
  32. data/lib/active_record/callbacks.rb +3 -3
  33. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +124 -23
  34. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  35. data/lib/active_record/connection_adapters/abstract/database_statements.rb +101 -70
  36. data/lib/active_record/connection_adapters/abstract/query_cache.rb +11 -5
  37. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  38. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
  39. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
  40. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
  42. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  43. data/lib/active_record/connection_adapters/abstract_adapter.rb +108 -39
  44. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +93 -134
  45. data/lib/active_record/connection_adapters/column.rb +17 -13
  46. data/lib/active_record/connection_adapters/connection_specification.rb +1 -1
  47. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
  48. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
  49. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  50. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  51. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  52. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
  53. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  54. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  55. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  56. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  58. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  59. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  60. data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
  61. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  62. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
  63. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
  64. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  65. data/lib/active_record/connection_adapters/postgresql_adapter.rb +91 -24
  66. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  67. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  68. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  69. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +69 -118
  72. data/lib/active_record/connection_handling.rb +32 -16
  73. data/lib/active_record/core.rb +27 -20
  74. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  75. data/lib/active_record/database_configurations/url_config.rb +21 -16
  76. data/lib/active_record/database_configurations.rb +99 -50
  77. data/lib/active_record/dynamic_matchers.rb +1 -1
  78. data/lib/active_record/enum.rb +15 -0
  79. data/lib/active_record/errors.rb +18 -13
  80. data/lib/active_record/fixtures.rb +11 -6
  81. data/lib/active_record/gem_version.rb +1 -1
  82. data/lib/active_record/inheritance.rb +1 -1
  83. data/lib/active_record/insert_all.rb +179 -0
  84. data/lib/active_record/integration.rb +13 -1
  85. data/lib/active_record/internal_metadata.rb +5 -1
  86. data/lib/active_record/locking/optimistic.rb +3 -4
  87. data/lib/active_record/log_subscriber.rb +1 -1
  88. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  89. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  90. data/lib/active_record/middleware/database_selector.rb +75 -0
  91. data/lib/active_record/migration/command_recorder.rb +28 -14
  92. data/lib/active_record/migration/compatibility.rb +72 -63
  93. data/lib/active_record/migration.rb +62 -44
  94. data/lib/active_record/persistence.rb +212 -19
  95. data/lib/active_record/querying.rb +18 -14
  96. data/lib/active_record/railtie.rb +9 -1
  97. data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
  98. data/lib/active_record/railties/databases.rake +124 -25
  99. data/lib/active_record/reflection.rb +18 -32
  100. data/lib/active_record/relation/calculations.rb +40 -44
  101. data/lib/active_record/relation/delegation.rb +23 -31
  102. data/lib/active_record/relation/finder_methods.rb +13 -13
  103. data/lib/active_record/relation/merger.rb +11 -16
  104. data/lib/active_record/relation/query_attribute.rb +5 -3
  105. data/lib/active_record/relation/query_methods.rb +217 -68
  106. data/lib/active_record/relation/spawn_methods.rb +1 -1
  107. data/lib/active_record/relation/where_clause.rb +10 -10
  108. data/lib/active_record/relation.rb +184 -35
  109. data/lib/active_record/sanitization.rb +33 -4
  110. data/lib/active_record/schema.rb +1 -1
  111. data/lib/active_record/schema_dumper.rb +10 -1
  112. data/lib/active_record/schema_migration.rb +1 -1
  113. data/lib/active_record/scoping/default.rb +7 -15
  114. data/lib/active_record/scoping/named.rb +10 -2
  115. data/lib/active_record/scoping.rb +6 -7
  116. data/lib/active_record/statement_cache.rb +2 -2
  117. data/lib/active_record/store.rb +48 -0
  118. data/lib/active_record/table_metadata.rb +9 -13
  119. data/lib/active_record/tasks/database_tasks.rb +109 -6
  120. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  121. data/lib/active_record/test_databases.rb +1 -16
  122. data/lib/active_record/test_fixtures.rb +2 -2
  123. data/lib/active_record/timestamp.rb +35 -19
  124. data/lib/active_record/touch_later.rb +4 -2
  125. data/lib/active_record/transactions.rb +55 -45
  126. data/lib/active_record/type_caster/connection.rb +16 -10
  127. data/lib/active_record/validations/uniqueness.rb +4 -4
  128. data/lib/active_record/validations.rb +1 -0
  129. data/lib/active_record.rb +7 -1
  130. data/lib/arel/insert_manager.rb +3 -3
  131. data/lib/arel/nodes/and.rb +1 -1
  132. data/lib/arel/nodes/case.rb +1 -1
  133. data/lib/arel/nodes/comment.rb +29 -0
  134. data/lib/arel/nodes/select_core.rb +16 -12
  135. data/lib/arel/nodes/unary.rb +1 -0
  136. data/lib/arel/nodes/values_list.rb +2 -17
  137. data/lib/arel/nodes.rb +2 -1
  138. data/lib/arel/select_manager.rb +10 -10
  139. data/lib/arel/visitors/depth_first.rb +7 -2
  140. data/lib/arel/visitors/dot.rb +7 -2
  141. data/lib/arel/visitors/ibm_db.rb +13 -0
  142. data/lib/arel/visitors/informix.rb +6 -0
  143. data/lib/arel/visitors/mssql.rb +15 -1
  144. data/lib/arel/visitors/oracle12.rb +4 -5
  145. data/lib/arel/visitors/postgresql.rb +4 -10
  146. data/lib/arel/visitors/to_sql.rb +107 -131
  147. data/lib/arel/visitors/visitor.rb +9 -5
  148. data/lib/arel.rb +7 -0
  149. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  150. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  151. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  152. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  153. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  154. metadata +17 -13
  155. data/lib/active_record/collection_cache_key.rb +0 -53
  156. data/lib/arel/nodes/values.rb +0 -16
@@ -4,12 +4,13 @@ require "active_record/connection_adapters/abstract_adapter"
4
4
  require "active_record/connection_adapters/statement_pool"
5
5
  require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
6
6
  require "active_record/connection_adapters/sqlite3/quoting"
7
+ require "active_record/connection_adapters/sqlite3/database_statements"
7
8
  require "active_record/connection_adapters/sqlite3/schema_creation"
8
9
  require "active_record/connection_adapters/sqlite3/schema_definitions"
9
10
  require "active_record/connection_adapters/sqlite3/schema_dumper"
10
11
  require "active_record/connection_adapters/sqlite3/schema_statements"
11
12
 
12
- gem "sqlite3", "~> 1.3.6"
13
+ gem "sqlite3", "~> 1.4"
13
14
  require "sqlite3"
14
15
 
15
16
  module ActiveRecord
@@ -36,8 +37,6 @@ module ActiveRecord
36
37
  config.merge(results_as_hash: true)
37
38
  )
38
39
 
39
- db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
40
-
41
40
  ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
42
41
  rescue Errno::ENOENT => error
43
42
  if error.message.include?("No such file or directory")
@@ -60,6 +59,7 @@ module ActiveRecord
60
59
 
61
60
  include SQLite3::Quoting
62
61
  include SQLite3::SchemaStatements
62
+ include SQLite3::DatabaseStatements
63
63
 
64
64
  NATIVE_DATABASE_TYPES = {
65
65
  primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
@@ -95,12 +95,19 @@ module ActiveRecord
95
95
 
96
96
  def initialize(connection, logger, connection_options, config)
97
97
  super(connection, logger, config)
98
-
99
- @active = true
100
- @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
101
98
  configure_connection
102
99
  end
103
100
 
101
+ def self.database_exists?(config)
102
+ config = config.symbolize_keys
103
+ if config[:database] == ":memory:"
104
+ return true
105
+ else
106
+ database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
107
+ File.exist?(database_file)
108
+ end
109
+ end
110
+
104
111
  def supports_ddl_transactions?
105
112
  true
106
113
  end
@@ -114,14 +121,14 @@ module ActiveRecord
114
121
  end
115
122
 
116
123
  def supports_expression_index?
117
- sqlite_version >= "3.9.0"
124
+ database_version >= "3.9.0"
118
125
  end
119
126
 
120
127
  def requires_reloading?
121
128
  true
122
129
  end
123
130
 
124
- def supports_foreign_keys_in_create?
131
+ def supports_foreign_keys?
125
132
  true
126
133
  end
127
134
 
@@ -137,23 +144,29 @@ module ActiveRecord
137
144
  true
138
145
  end
139
146
 
147
+ def supports_insert_on_conflict?
148
+ database_version >= "3.24.0"
149
+ end
150
+ alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
151
+ alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
152
+ alias supports_insert_conflict_target? supports_insert_on_conflict?
153
+
140
154
  def active?
141
- @active
155
+ !@connection.closed?
156
+ end
157
+
158
+ def reconnect!
159
+ super
160
+ connect if @connection.closed?
142
161
  end
143
162
 
144
163
  # Disconnects from the database if already connected. Otherwise, this
145
164
  # method does nothing.
146
165
  def disconnect!
147
166
  super
148
- @active = false
149
167
  @connection.close rescue nil
150
168
  end
151
169
 
152
- # Clears the prepared statements cache.
153
- def clear_cache!
154
- @statements.clear
155
- end
156
-
157
170
  def supports_index_sort_order?
158
171
  true
159
172
  end
@@ -201,91 +214,11 @@ module ActiveRecord
201
214
  #--
202
215
  # DATABASE STATEMENTS ======================================
203
216
  #++
204
-
205
- READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback) # :nodoc:
206
- private_constant :READ_QUERY
207
-
208
- def write_query?(sql) # :nodoc:
209
- !READ_QUERY.match?(sql)
210
- end
211
-
212
217
  def explain(arel, binds = [])
213
218
  sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
214
219
  SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
215
220
  end
216
221
 
217
- def exec_query(sql, name = nil, binds = [], prepare: false)
218
- if preventing_writes? && write_query?(sql)
219
- raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
220
- end
221
-
222
- materialize_transactions
223
-
224
- type_casted_binds = type_casted_binds(binds)
225
-
226
- log(sql, name, binds, type_casted_binds) do
227
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
228
- # Don't cache statements if they are not prepared
229
- unless prepare
230
- stmt = @connection.prepare(sql)
231
- begin
232
- cols = stmt.columns
233
- unless without_prepared_statement?(binds)
234
- stmt.bind_params(type_casted_binds)
235
- end
236
- records = stmt.to_a
237
- ensure
238
- stmt.close
239
- end
240
- else
241
- stmt = @statements[sql] ||= @connection.prepare(sql)
242
- cols = stmt.columns
243
- stmt.reset!
244
- stmt.bind_params(type_casted_binds)
245
- records = stmt.to_a
246
- end
247
-
248
- ActiveRecord::Result.new(cols, records)
249
- end
250
- end
251
- end
252
-
253
- def exec_delete(sql, name = "SQL", binds = [])
254
- exec_query(sql, name, binds)
255
- @connection.changes
256
- end
257
- alias :exec_update :exec_delete
258
-
259
- def last_inserted_id(result)
260
- @connection.last_insert_row_id
261
- end
262
-
263
- def execute(sql, name = nil) #:nodoc:
264
- if preventing_writes? && write_query?(sql)
265
- raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
266
- end
267
-
268
- materialize_transactions
269
-
270
- log(sql, name) do
271
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
272
- @connection.execute(sql)
273
- end
274
- end
275
- end
276
-
277
- def begin_db_transaction #:nodoc:
278
- log("begin transaction", nil) { @connection.transaction }
279
- end
280
-
281
- def commit_db_transaction #:nodoc:
282
- log("commit transaction", nil) { @connection.commit }
283
- end
284
-
285
- def exec_rollback_db_transaction #:nodoc:
286
- log("rollback transaction", nil) { @connection.rollback }
287
- end
288
-
289
222
  # SCHEMA STATEMENTS ========================================
290
223
 
291
224
  def primary_keys(table_name) # :nodoc:
@@ -320,6 +253,9 @@ module ActiveRecord
320
253
  def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
321
254
  alter_table(table_name) do |definition|
322
255
  definition.remove_column column_name
256
+ definition.foreign_keys.delete_if do |_, fk_options|
257
+ fk_options[:column] == column_name.to_s
258
+ end
323
259
  end
324
260
  end
325
261
 
@@ -378,15 +314,26 @@ module ActiveRecord
378
314
  end
379
315
  end
380
316
 
381
- def insert_fixtures_set(fixture_set, tables_to_delete = [])
382
- disable_referential_integrity do
383
- transaction(requires_new: true) do
384
- tables_to_delete.each { |table| delete "DELETE FROM #{quote_table_name(table)}", "Fixture Delete" }
317
+ def build_insert_sql(insert) # :nodoc:
318
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
385
319
 
386
- fixture_set.each do |table_name, rows|
387
- rows.each { |row| insert_fixture(row, table_name) }
388
- end
389
- end
320
+ if insert.skip_duplicates?
321
+ sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
322
+ elsif insert.update_duplicates?
323
+ sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
324
+ sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
325
+ end
326
+
327
+ sql
328
+ end
329
+
330
+ def get_database_version # :nodoc:
331
+ SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
332
+ end
333
+
334
+ def check_version # :nodoc:
335
+ if database_version < "3.8.0"
336
+ raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.8."
390
337
  end
391
338
  end
392
339
 
@@ -397,12 +344,6 @@ module ActiveRecord
397
344
  999
398
345
  end
399
346
 
400
- def check_version
401
- if sqlite_version < "3.8.0"
402
- raise "Your version of SQLite (#{sqlite_version}) is too old. Active Record supports SQLite >= 3.8."
403
- end
404
- end
405
-
406
347
  def initialize_type_map(m = type_map)
407
348
  super
408
349
  register_class_with_limit m, %r(int)i, SQLite3Integer
@@ -421,9 +362,8 @@ module ActiveRecord
421
362
  type.to_sym == :primary_key || options[:primary_key]
422
363
  end
423
364
 
424
- def alter_table(table_name, options = {})
365
+ def alter_table(table_name, foreign_keys = foreign_keys(table_name), **options)
425
366
  altered_table_name = "a#{table_name}"
426
- foreign_keys = foreign_keys(table_name)
427
367
 
428
368
  caller = lambda do |definition|
429
369
  rename = options[:rename] || {}
@@ -431,7 +371,8 @@ module ActiveRecord
431
371
  if column = rename[fk.options[:column]]
432
372
  fk.options[:column] = column
433
373
  end
434
- definition.foreign_key(fk.to_table, fk.options)
374
+ to_table = strip_table_name_prefix_and_suffix(fk.to_table)
375
+ definition.foreign_key(to_table, fk.options)
435
376
  end
436
377
 
437
378
  yield definition if block_given?
@@ -520,10 +461,6 @@ module ActiveRecord
520
461
  SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
521
462
  end
522
463
 
523
- def sqlite_version
524
- @sqlite_version ||= SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
525
- end
526
-
527
464
  def translate_exception(exception, message:, sql:, binds:)
528
465
  case exception.message
529
466
  # SQLite 3.8.2 returns a newly formatted error message:
@@ -558,9 +495,9 @@ module ActiveRecord
558
495
  result = exec_query(sql, "SCHEMA").first
559
496
 
560
497
  if result
561
- # Splitting with left parentheses and picking up last will return all
498
+ # Splitting with left parentheses and discarding the first part will return all
562
499
  # columns separated with comma(,).
563
- columns_string = result["sql"].split("(").last
500
+ columns_string = result["sql"].split("(", 2).last
564
501
 
565
502
  columns_string.split(",").each do |column_string|
566
503
  # This regex will match the column name and collation type and will save
@@ -586,7 +523,21 @@ module ActiveRecord
586
523
  Arel::Visitors::SQLite.new(self)
587
524
  end
588
525
 
526
+ def build_statement_pool
527
+ StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
528
+ end
529
+
530
+ def connect
531
+ @connection = ::SQLite3::Database.new(
532
+ @config[:database].to_s,
533
+ @config.merge(results_as_hash: true)
534
+ )
535
+ configure_connection
536
+ end
537
+
589
538
  def configure_connection
539
+ @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
540
+
590
541
  execute("PRAGMA foreign_keys = ON", "SCHEMA")
591
542
  end
592
543
 
@@ -85,14 +85,14 @@ module ActiveRecord
85
85
  # based on the requested role:
86
86
  #
87
87
  # ActiveRecord::Base.connected_to(role: :writing) do
88
- # Dog.create! # creates dog using dog connection
88
+ # Dog.create! # creates dog using dog writing connection
89
89
  # end
90
90
  #
91
91
  # ActiveRecord::Base.connected_to(role: :reading) do
92
92
  # Dog.create! # throws exception because we're on a replica
93
93
  # end
94
94
  #
95
- # ActiveRecord::Base.connected_to(role: :unknown_ode) do
95
+ # ActiveRecord::Base.connected_to(role: :unknown_role) do
96
96
  # # raises exception due to non-existent role
97
97
  # end
98
98
  #
@@ -100,11 +100,20 @@ module ActiveRecord
100
100
  # you can use +connected_to+ with a +database+ argument. The +database+ argument
101
101
  # expects a symbol that corresponds to the database key in your config.
102
102
  #
103
- # This will connect to a new database for the queries inside the block.
104
- #
105
103
  # ActiveRecord::Base.connected_to(database: :animals_slow_replica) do
106
104
  # Dog.run_a_long_query # runs a long query while connected to the +animals_slow_replica+
107
105
  # end
106
+ #
107
+ # This will connect to a new database for the queries inside the block. By
108
+ # default the `:writing` role will be used since all connections must be assigned
109
+ # a role. If you would like to use a different role you can pass a hash to database:
110
+ #
111
+ # ActiveRecord::Base.connected_to(database: { readonly_slow: :animals_slow_replica }) do
112
+ # # runs a long query while connected to the +animals_slow_replica+ using the readonly_slow role.
113
+ # Dog.run_a_long_query
114
+ # end
115
+ #
116
+ # When using the database key a new connection will be established every time.
108
117
  def connected_to(database: nil, role: nil, &blk)
109
118
  if database && role
110
119
  raise ArgumentError, "connected_to can only accept a `database` or a `role` argument, but not both arguments."
@@ -112,17 +121,14 @@ module ActiveRecord
112
121
  if database.is_a?(Hash)
113
122
  role, database = database.first
114
123
  role = role.to_sym
115
- else
116
- role = database.to_sym
117
124
  end
118
125
 
119
126
  config_hash = resolve_config_for_connection(database)
120
127
  handler = lookup_connection_handler(role)
121
128
 
122
- with_handler(role) do
123
- handler.establish_connection(config_hash)
124
- yield
125
- end
129
+ handler.establish_connection(config_hash)
130
+
131
+ with_handler(role, &blk)
126
132
  elsif role
127
133
  with_handler(role.to_sym, &blk)
128
134
  else
@@ -154,14 +160,11 @@ module ActiveRecord
154
160
  end
155
161
 
156
162
  def lookup_connection_handler(handler_key) # :nodoc:
163
+ handler_key ||= ActiveRecord::Base.writing_role
157
164
  connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
158
165
  end
159
166
 
160
167
  def with_handler(handler_key, &blk) # :nodoc:
161
- unless ActiveRecord::Base.connection_handlers.keys.include?(handler_key)
162
- raise ArgumentError, "The #{handler_key} role does not exist. Add it by establishing a connection with `connects_to` or use an existing role (#{ActiveRecord::Base.connection_handlers.keys.join(", ")})."
163
- end
164
-
165
168
  handler = lookup_connection_handler(handler_key)
166
169
  swap_connection_handler(handler, &blk)
167
170
  end
@@ -170,7 +173,7 @@ module ActiveRecord
170
173
  raise "Anonymous class is not allowed." unless name
171
174
 
172
175
  config_or_env ||= DEFAULT_ENV.call.to_sym
173
- pool_name = self == Base ? "primary" : name
176
+ pool_name = primary_class? ? "primary" : name
174
177
  self.connection_specification_name = pool_name
175
178
 
176
179
  resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
@@ -180,6 +183,15 @@ module ActiveRecord
180
183
  config_hash
181
184
  end
182
185
 
186
+ # Clears the query cache for all connections associated with the current thread.
187
+ def clear_query_caches_for_current_thread
188
+ ActiveRecord::Base.connection_handlers.each_value do |handler|
189
+ handler.connection_pool_list.each do |pool|
190
+ pool.connection.clear_query_cache if pool.active_connection?
191
+ end
192
+ end
193
+ end
194
+
183
195
  # Returns the connection currently associated with the class. This can
184
196
  # also be used to "borrow" the connection to do database work unrelated
185
197
  # to any of the specific Active Records.
@@ -192,11 +204,15 @@ module ActiveRecord
192
204
  # Return the specification name from the current class or its parent.
193
205
  def connection_specification_name
194
206
  if !defined?(@connection_specification_name) || @connection_specification_name.nil?
195
- return self == Base ? "primary" : superclass.connection_specification_name
207
+ return primary_class? ? "primary" : superclass.connection_specification_name
196
208
  end
197
209
  @connection_specification_name
198
210
  end
199
211
 
212
+ def primary_class? # :nodoc:
213
+ self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
214
+ end
215
+
200
216
  # Returns the configuration of the associated connection as a hash:
201
217
  #
202
218
  # ActiveRecord::Base.connection_config
@@ -124,6 +124,10 @@ module ActiveRecord
124
124
 
125
125
  mattr_accessor :connection_handlers, instance_accessor: false, default: {}
126
126
 
127
+ mattr_accessor :writing_role, instance_accessor: false, default: :writing
128
+
129
+ mattr_accessor :reading_role, instance_accessor: false, default: :reading
130
+
127
131
  class_attribute :default_connection_handler, instance_writer: false
128
132
 
129
133
  self.filter_attributes = []
@@ -137,7 +141,6 @@ module ActiveRecord
137
141
  end
138
142
 
139
143
  self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
140
- self.connection_handlers = { writing: ActiveRecord::Base.default_connection_handler }
141
144
  end
142
145
 
143
146
  module ClassMethods
@@ -157,7 +160,7 @@ module ActiveRecord
157
160
  return super if block_given? ||
158
161
  primary_key.nil? ||
159
162
  scope_attributes? ||
160
- columns_hash.include?(inheritance_column)
163
+ columns_hash.key?(inheritance_column) && !base_class?
161
164
 
162
165
  id = ids.first
163
166
 
@@ -171,14 +174,14 @@ module ActiveRecord
171
174
 
172
175
  record = statement.execute([id], connection)&.first
173
176
  unless record
174
- raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
175
- name, primary_key, id)
177
+ raise RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
176
178
  end
177
179
  record
178
180
  end
179
181
 
180
182
  def find_by(*args) # :nodoc:
181
- return super if scope_attributes? || reflect_on_all_aggregations.any?
183
+ return super if scope_attributes? || reflect_on_all_aggregations.any? ||
184
+ columns_hash.key?(inheritance_column) && !base_class?
182
185
 
183
186
  hash = args.first
184
187
 
@@ -265,7 +268,8 @@ module ActiveRecord
265
268
  end
266
269
 
267
270
  def arel_attribute(name, table = arel_table) # :nodoc:
268
- name = attribute_alias(name) if attribute_alias?(name)
271
+ name = name.to_s
272
+ name = attribute_aliases[name] || name
269
273
  table[name]
270
274
  end
271
275
 
@@ -313,7 +317,7 @@ module ActiveRecord
313
317
  # # Instantiates a single new object
314
318
  # User.new(first_name: 'Jamie')
315
319
  def initialize(attributes = nil)
316
- self.class.define_attribute_methods
320
+ @new_record = true
317
321
  @attributes = self.class._default_attributes.deep_dup
318
322
 
319
323
  init_internals
@@ -350,12 +354,10 @@ module ActiveRecord
350
354
  # +attributes+ should be an attributes object, and unlike the
351
355
  # `initialize` method, no assignment calls are made per attribute.
352
356
  def init_with_attributes(attributes, new_record = false) # :nodoc:
353
- init_internals
354
-
355
357
  @new_record = new_record
356
358
  @attributes = attributes
357
359
 
358
- self.class.define_attribute_methods
360
+ init_internals
359
361
 
360
362
  yield self if block_given?
361
363
 
@@ -394,13 +396,13 @@ module ActiveRecord
394
396
  ##
395
397
  def initialize_dup(other) # :nodoc:
396
398
  @attributes = @attributes.deep_dup
397
- @attributes.reset(self.class.primary_key)
399
+ @attributes.reset(@primary_key)
398
400
 
399
401
  _run_initialize_callbacks
400
402
 
401
403
  @new_record = true
402
404
  @destroyed = false
403
- @_start_transaction_state = {}
405
+ @_start_transaction_state = nil
404
406
  @transaction_state = nil
405
407
 
406
408
  super
@@ -461,6 +463,7 @@ module ActiveRecord
461
463
 
462
464
  # Returns +true+ if the attributes hash has been frozen.
463
465
  def frozen?
466
+ sync_with_transaction_state if @transaction_state&.finalized?
464
467
  @attributes.frozen?
465
468
  end
466
469
 
@@ -473,6 +476,14 @@ module ActiveRecord
473
476
  end
474
477
  end
475
478
 
479
+ def present? # :nodoc:
480
+ true
481
+ end
482
+
483
+ def blank? # :nodoc:
484
+ false
485
+ end
486
+
476
487
  # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
477
488
  # attributes will be marked as read only since they cannot be saved.
478
489
  def readonly?
@@ -557,22 +568,18 @@ module ActiveRecord
557
568
  end
558
569
 
559
570
  def init_internals
571
+ @primary_key = self.class.primary_key
560
572
  @readonly = false
561
573
  @destroyed = false
562
574
  @marked_for_destruction = false
563
575
  @destroyed_by_association = nil
564
- @new_record = true
565
- @_start_transaction_state = {}
576
+ @_start_transaction_state = nil
566
577
  @transaction_state = nil
567
- end
568
578
 
569
- def initialize_internals_callback
579
+ self.class.define_attribute_methods
570
580
  end
571
581
 
572
- def thaw
573
- if frozen?
574
- @attributes = @attributes.dup
575
- end
582
+ def initialize_internals_callback
576
583
  end
577
584
 
578
585
  def custom_inspect_method_defined?
@@ -14,16 +14,16 @@ module ActiveRecord
14
14
  # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10
15
15
  # @env_name="development", @spec_name="primary", @config={"database"=>"db_name"}>
16
16
  #
17
- # Options are:
17
+ # ==== Options
18
18
  #
19
- # <tt>:env_name</tt> - The Rails environment, ie "development"
20
- # <tt>:spec_name</tt> - The specification name. In a standard two-tier
21
- # database configuration this will default to "primary". In a multiple
22
- # database three-tier database configuration this corresponds to the name
23
- # used in the second tier, for example "primary_readonly".
24
- # <tt>:config</tt> - The config hash. This is the hash that contains the
25
- # database adapter, name, and other important information for database
26
- # connections.
19
+ # * <tt>:env_name</tt> - The Rails environment, i.e. "development".
20
+ # * <tt>:spec_name</tt> - The specification name. In a standard two-tier
21
+ # database configuration this will default to "primary". In a multiple
22
+ # database three-tier database configuration this corresponds to the name
23
+ # used in the second tier, for example "primary_readonly".
24
+ # * <tt>:config</tt> - The config hash. This is the hash that contains the
25
+ # database adapter, name, and other important information for database
26
+ # connections.
27
27
  class HashConfig < DatabaseConfig
28
28
  attr_reader :config
29
29
 
@@ -33,14 +33,14 @@ module ActiveRecord
33
33
  end
34
34
 
35
35
  # Determines whether a database configuration is for a replica / readonly
36
- # connection. If the `replica` key is present in the config, `replica?` will
36
+ # connection. If the +replica+ key is present in the config, +replica?+ will
37
37
  # return +true+.
38
38
  def replica?
39
39
  config["replica"]
40
40
  end
41
41
 
42
42
  # The migrations paths for a database configuration. If the
43
- # `migrations_paths` key is present in the config, `migrations_paths`
43
+ # +migrations_paths+ key is present in the config, +migrations_paths+
44
44
  # will return its value.
45
45
  def migrations_paths
46
46
  config["migrations_paths"]
@@ -17,17 +17,17 @@ module ActiveRecord
17
17
  # @config={"adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost"},
18
18
  # @url="postgres://localhost/foo">
19
19
  #
20
- # Options are:
20
+ # ==== Options
21
21
  #
22
- # <tt>:env_name</tt> - The Rails environment, ie "development"
23
- # <tt>:spec_name</tt> - The specification name. In a standard two-tier
24
- # database configuration this will default to "primary". In a multiple
25
- # database three-tier database configuration this corresponds to the name
26
- # used in the second tier, for example "primary_readonly".
27
- # <tt>:url</tt> - The database URL.
28
- # <tt>:config</tt> - The config hash. This is the hash that contains the
29
- # database adapter, name, and other important information for database
30
- # connections.
22
+ # * <tt>:env_name</tt> - The Rails environment, ie "development".
23
+ # * <tt>:spec_name</tt> - The specification name. In a standard two-tier
24
+ # database configuration this will default to "primary". In a multiple
25
+ # database three-tier database configuration this corresponds to the name
26
+ # used in the second tier, for example "primary_readonly".
27
+ # * <tt>:url</tt> - The database URL.
28
+ # * <tt>:config</tt> - The config hash. This is the hash that contains the
29
+ # database adapter, name, and other important information for database
30
+ # connections.
31
31
  class UrlConfig < DatabaseConfig
32
32
  attr_reader :url, :config
33
33
 
@@ -42,26 +42,31 @@ module ActiveRecord
42
42
  end
43
43
 
44
44
  # Determines whether a database configuration is for a replica / readonly
45
- # connection. If the `replica` key is present in the config, `replica?` will
45
+ # connection. If the +replica+ key is present in the config, +replica?+ will
46
46
  # return +true+.
47
47
  def replica?
48
48
  config["replica"]
49
49
  end
50
50
 
51
51
  # The migrations paths for a database configuration. If the
52
- # `migrations_paths` key is present in the config, `migrations_paths`
52
+ # +migrations_paths+ key is present in the config, +migrations_paths+
53
53
  # will return its value.
54
54
  def migrations_paths
55
55
  config["migrations_paths"]
56
56
  end
57
57
 
58
58
  private
59
- def build_config(original_config, url)
60
- if /^jdbc:/.match?(url)
61
- hash = { "url" => url }
59
+
60
+ def build_url_hash(url)
61
+ if url.nil? || /^jdbc:/.match?(url)
62
+ { "url" => url }
62
63
  else
63
- hash = ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(url).to_hash
64
+ ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(url).to_hash
64
65
  end
66
+ end
67
+
68
+ def build_config(original_config, url)
69
+ hash = build_url_hash(url)
65
70
 
66
71
  if original_config[env_name]
67
72
  original_config[env_name].merge(hash)