activerecord 7.0.0.rc3 → 7.0.2.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +200 -3
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_record/associations/join_dependency.rb +6 -2
  5. data/lib/active_record/associations.rb +29 -8
  6. data/lib/active_record/attribute_methods.rb +1 -1
  7. data/lib/active_record/autosave_association.rb +2 -2
  8. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  9. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -4
  10. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  11. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +9 -4
  12. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1 -1
  13. data/lib/active_record/connection_adapters/mysql/database_statements.rb +3 -1
  14. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  15. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +1 -0
  16. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -0
  17. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -0
  18. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +9 -4
  19. data/lib/active_record/connection_adapters/postgresql_adapter.rb +2 -2
  20. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +2 -0
  21. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
  22. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +12 -12
  23. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +28 -0
  24. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  25. data/lib/active_record/database_configurations.rb +1 -1
  26. data/lib/active_record/encryption/configurable.rb +2 -2
  27. data/lib/active_record/encryption/encryptable_record.rb +1 -1
  28. data/lib/active_record/encryption/extended_deterministic_queries.rb +28 -28
  29. data/lib/active_record/fixtures.rb +1 -1
  30. data/lib/active_record/gem_version.rb +2 -2
  31. data/lib/active_record/integration.rb +2 -2
  32. data/lib/active_record/migration/compatibility.rb +24 -2
  33. data/lib/active_record/migration.rb +1 -1
  34. data/lib/active_record/railtie.rb +2 -2
  35. data/lib/active_record/reflection.rb +1 -1
  36. data/lib/active_record/relation/calculations.rb +3 -2
  37. data/lib/active_record/relation/delegation.rb +1 -1
  38. data/lib/active_record/relation/query_methods.rb +19 -5
  39. data/lib/active_record/relation.rb +17 -4
  40. data/lib/active_record/schema.rb +38 -23
  41. data/lib/active_record/schema_dumper.rb +15 -16
  42. data/lib/active_record/schema_migration.rb +4 -0
  43. data/lib/active_record/tasks/database_tasks.rb +6 -2
  44. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  45. data/lib/active_record.rb +1 -1
  46. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  47. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  48. metadata +14 -12
@@ -389,6 +389,34 @@ module ActiveRecord
389
389
  end
390
390
  alias column_definitions table_structure
391
391
 
392
+ def extract_value_from_default(default)
393
+ case default
394
+ when /^null$/i
395
+ nil
396
+ # Quoted types
397
+ when /^'(.*)'$/m
398
+ $1.gsub("''", "'")
399
+ # Quoted types
400
+ when /^"(.*)"$/m
401
+ $1.gsub('""', '"')
402
+ # Numeric types
403
+ when /\A-?\d+(\.\d*)?\z/
404
+ $&
405
+ else
406
+ # Anything else is blank or some function
407
+ # and we can't know the value of that, so return nil.
408
+ nil
409
+ end
410
+ end
411
+
412
+ def extract_default_function(default_value, default)
413
+ default if has_default_function?(default_value, default)
414
+ end
415
+
416
+ def has_default_function?(default_value, default)
417
+ !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
418
+ end
419
+
392
420
  # See: https://www.sqlite.org/lang_altertable.html
393
421
  # SQLite has an additional restriction on the ALTER TABLE statement
394
422
  def invalid_alter_table_type?(type, options)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "uri"
4
4
  require "active_support/core_ext/enumerable"
5
+ require "active_support/core_ext/hash/reverse_merge"
5
6
 
6
7
  module ActiveRecord
7
8
  class DatabaseConfigurations
@@ -38,7 +38,7 @@ module ActiveRecord
38
38
  # the returned list. Most of the time we're only iterating over the write
39
39
  # connection (i.e. migrations don't need to run for the write and read connection).
40
40
  # Defaults to +false+.
41
- # * <tt>include_hidden:</tte Determines whether to include replicas and configurations
41
+ # * <tt>include_hidden:</tt> Determines whether to include replicas and configurations
42
42
  # hidden by +database_tasks: false+ in the returned list. Most of the time we're only
43
43
  # iterating over the primary connections (i.e. migrations don't need to run for the
44
44
  # write and read connection). Defaults to +false+.
@@ -50,9 +50,9 @@ module ActiveRecord
50
50
  end
51
51
  end
52
52
 
53
- def install_auto_filtered_parameters(application) # :nodoc:
53
+ def install_auto_filtered_parameters_hook(application) # :nodoc:
54
54
  ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, encrypted_attribute_name|
55
- application.config.filter_parameters << encrypted_attribute_name unless ActiveRecord::Encryption.config.excluded_from_filter_parameters.include?(name)
55
+ application.config.filter_parameters << encrypted_attribute_name unless ActiveRecord::Encryption.config.excluded_from_filter_parameters.include?(encrypted_attribute_name)
56
56
  end
57
57
  end
58
58
  end
@@ -37,7 +37,7 @@ module ActiveRecord
37
37
  # in preserving it.
38
38
  # * <tt>:ignore_case</tt> - When true, it behaves like +:downcase+ but, it also preserves the original case in a specially
39
39
  # designated column +original_<name>+. When reading the encrypted content, the version with the original case is
40
- # server. But you can still execute queries that will ignore the case. This option can only be used when +:deterministic+
40
+ # served. But you can still execute queries that will ignore the case. This option can only be used when +:deterministic+
41
41
  # is true.
42
42
  # * <tt>:context_properties</tt> - Additional properties that will override +Context+ settings when this attribute is
43
43
  # encrypted and decrypted. E.g: +encryptor:+, +cipher:+, +message_serializer:+, etc.
@@ -1,35 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Automatically expand encrypted arguments to support querying both encrypted and unencrypted data
4
- #
5
- # Active Record Encryption supports querying the db using deterministic attributes. For example:
6
- #
7
- # Contact.find_by(email_address: "jorge@hey.com")
8
- #
9
- # The value "jorge@hey.com" will get encrypted automatically to perform the query. But there is
10
- # a problem while the data is being encrypted. This won't work. During that time, you need these
11
- # queries to be:
12
- #
13
- # Contact.find_by(email_address: [ "jorge@hey.com", "<encrypted jorge@hey.com>" ])
14
- #
15
- # This patches ActiveRecord to support this automatically. It addresses both:
16
- #
17
- # * ActiveRecord::Base: Used in +Contact.find_by_email_address(...)+
18
- # * ActiveRecord::Relation: Used in +Contact.internal.find_by_email_address(...)+
19
- #
20
- # +ActiveRecord::Base+ relies on +ActiveRecord::Relation+ (+ActiveRecord::QueryMethods+) but it does
21
- # some prepared statements caching. That's why we need to intercept +ActiveRecord::Base+ as soon
22
- # as it's invoked (so that the proper prepared statement is cached).
23
- #
24
- # When modifying this file run performance tests in +test/performance/extended_deterministic_queries_performance_test.rb+ to
25
- # make sure performance overhead is acceptable.
26
- #
27
- # We will extend this to support previous "encryption context" versions in future iterations
28
- #
29
- # @TODO Experimental. Support for every kind of query is pending
30
- # @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
31
3
  module ActiveRecord
32
4
  module Encryption
5
+ # Automatically expand encrypted arguments to support querying both encrypted and unencrypted data
6
+ #
7
+ # Active Record Encryption supports querying the db using deterministic attributes. For example:
8
+ #
9
+ # Contact.find_by(email_address: "jorge@hey.com")
10
+ #
11
+ # The value "jorge@hey.com" will get encrypted automatically to perform the query. But there is
12
+ # a problem while the data is being encrypted. This won't work. During that time, you need these
13
+ # queries to be:
14
+ #
15
+ # Contact.find_by(email_address: [ "jorge@hey.com", "<encrypted jorge@hey.com>" ])
16
+ #
17
+ # This patches ActiveRecord to support this automatically. It addresses both:
18
+ #
19
+ # * ActiveRecord::Base: Used in +Contact.find_by_email_address(...)+
20
+ # * ActiveRecord::Relation: Used in +Contact.internal.find_by_email_address(...)+
21
+ #
22
+ # +ActiveRecord::Base+ relies on +ActiveRecord::Relation+ (+ActiveRecord::QueryMethods+) but it does
23
+ # some prepared statements caching. That's why we need to intercept +ActiveRecord::Base+ as soon
24
+ # as it's invoked (so that the proper prepared statement is cached).
25
+ #
26
+ # When modifying this file run performance tests in +test/performance/extended_deterministic_queries_performance_test.rb+ to
27
+ # make sure performance overhead is acceptable.
28
+ #
29
+ # We will extend this to support previous "encryption context" versions in future iterations
30
+ #
31
+ # @TODO Experimental. Support for every kind of query is pending
32
+ # @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
33
33
  module ExtendedDeterministicQueries
34
34
  def self.install_support
35
35
  ActiveRecord::Relation.prepend(RelationQueries)
@@ -407,7 +407,7 @@ module ActiveRecord
407
407
  # defaults:
408
408
  #
409
409
  # DEFAULTS: &DEFAULTS
410
- # created_on: <%= 3.weeks.ago.to_formatted_s(:db) %>
410
+ # created_on: <%= 3.weeks.ago.to_fs(:db) %>
411
411
  #
412
412
  # first:
413
413
  # name: Smurf
@@ -9,8 +9,8 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 7
11
11
  MINOR = 0
12
- TINY = 0
13
- PRE = "rc3"
12
+ TINY = 2
13
+ PRE = "1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -79,7 +79,7 @@ module ActiveRecord
79
79
  timestamp = max_updated_column_timestamp
80
80
 
81
81
  if timestamp
82
- timestamp = timestamp.utc.to_formatted_s(cache_timestamp_format)
82
+ timestamp = timestamp.utc.to_fs(cache_timestamp_format)
83
83
  "#{model_name.cache_key}/#{id}-#{timestamp}"
84
84
  else
85
85
  "#{model_name.cache_key}/#{id}"
@@ -103,7 +103,7 @@ module ActiveRecord
103
103
  raw_timestamp_to_cache_version(timestamp)
104
104
 
105
105
  elsif timestamp = updated_at
106
- timestamp.utc.to_formatted_s(cache_timestamp_format)
106
+ timestamp.utc.to_fs(cache_timestamp_format)
107
107
  end
108
108
  elsif self.class.has_attribute?("updated_at")
109
109
  raise ActiveModel::MissingAttributeError, "missing attribute: updated_at"
@@ -92,6 +92,22 @@ module ActiveRecord
92
92
  end
93
93
  end
94
94
 
95
+ module SQLite3
96
+ module TableDefinition
97
+ def references(*args, **options)
98
+ args.each do |ref_name|
99
+ ReferenceDefinition.new(ref_name, type: :integer, **options).add_to(self)
100
+ end
101
+ end
102
+ alias :belongs_to :references
103
+
104
+ def column(name, type, index: nil, **options)
105
+ options[:precision] ||= nil
106
+ super
107
+ end
108
+ end
109
+ end
110
+
95
111
  module TableDefinition
96
112
  def references(*args, **options)
97
113
  args.each do |ref_name|
@@ -131,8 +147,13 @@ module ActiveRecord
131
147
  end
132
148
 
133
149
  def add_reference(table_name, ref_name, **options)
134
- ReferenceDefinition.new(ref_name, **options)
135
- .add_to(connection.update_table_definition(table_name, self))
150
+ if connection.adapter_name == "SQLite"
151
+ reference_definition = ReferenceDefinition.new(ref_name, type: :integer, **options)
152
+ else
153
+ reference_definition = ReferenceDefinition.new(ref_name, **options)
154
+ end
155
+
156
+ reference_definition.add_to(connection.update_table_definition(table_name, self))
136
157
  end
137
158
  alias :add_belongs_to :add_reference
138
159
 
@@ -140,6 +161,7 @@ module ActiveRecord
140
161
  def compatible_table_definition(t)
141
162
  class << t
142
163
  prepend TableDefinition
164
+ prepend SQLite3::TableDefinition
143
165
  end
144
166
  t
145
167
  end
@@ -1080,7 +1080,7 @@ module ActiveRecord
1080
1080
  # 0 then an empty array will be returned and no migrations
1081
1081
  # will be run.
1082
1082
  #
1083
- # If the +current_version+ in the schema is less than
1083
+ # If the +current_version+ in the schema is greater than
1084
1084
  # the +target_version+, then +down+ will be run.
1085
1085
  #
1086
1086
  # If none of the conditions are met, +up+ will be run with
@@ -360,9 +360,9 @@ To keep using the current cache store, you can turn off cache versioning entirel
360
360
  end
361
361
 
362
362
  # Filtered params
363
- ActiveSupport.on_load(:action_controller) do
363
+ ActiveSupport.on_load(:action_controller, run_once: true) do
364
364
  if ActiveRecord::Encryption.config.add_to_filter_parameters
365
- ActiveRecord::Encryption.install_auto_filtered_parameters(app)
365
+ ActiveRecord::Encryption.install_auto_filtered_parameters_hook(app)
366
366
  end
367
367
  end
368
368
  end
@@ -1031,7 +1031,7 @@ module ActiveRecord
1031
1031
  end
1032
1032
 
1033
1033
  def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
1034
- scopes = @previous_reflection.join_scopes(table, predicate_builder, record) + super
1034
+ scopes = @previous_reflection.join_scopes(table, predicate_builder, klass, record) + super
1035
1035
  scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope)
1036
1036
  end
1037
1037
 
@@ -155,7 +155,7 @@ module ActiveRecord
155
155
  end
156
156
 
157
157
  # Use #pluck as a shortcut to select one or more attributes without
158
- # loading a bunch of records just to grab the attributes you want.
158
+ # loading an entire record object per row.
159
159
  #
160
160
  # Person.pluck(:name)
161
161
  #
@@ -345,12 +345,13 @@ module ActiveRecord
345
345
  column = aggregate_column(column_name)
346
346
  column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
347
347
  select_value = operation_over_aggregate_column(column, operation, distinct)
348
- select_value.as(column_alias)
348
+ select_value.as(connection.quote_column_name(column_alias))
349
349
 
350
350
  select_values = [select_value]
351
351
  select_values += self.select_values unless having_clause.empty?
352
352
 
353
353
  select_values.concat group_columns.map { |aliaz, field|
354
+ aliaz = connection.quote_column_name(aliaz)
354
355
  if field.respond_to?(:as)
355
356
  field.as(aliaz)
356
357
  else
@@ -87,7 +87,7 @@ module ActiveRecord
87
87
 
88
88
  delegate :to_xml, :encode_with, :length, :each, :join,
89
89
  :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
90
- :to_sentence, :to_formatted_s, :as_json,
90
+ :to_sentence, :to_fs, :to_formatted_s, :as_json,
91
91
  :shuffle, :split, :slice, :index, :rindex, to: :records
92
92
 
93
93
  delegate :primary_key, :connection, to: :klass
@@ -68,7 +68,7 @@ module ActiveRecord
68
68
  # # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
69
69
  def associated(*associations)
70
70
  associations.each do |association|
71
- reflection = @scope.klass._reflect_on_association(association)
71
+ reflection = scope_association_reflection(association)
72
72
  @scope.joins!(association)
73
73
  self.not(reflection.table_name => { reflection.association_primary_key => nil })
74
74
  end
@@ -96,13 +96,22 @@ module ActiveRecord
96
96
  # # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
97
97
  def missing(*associations)
98
98
  associations.each do |association|
99
- reflection = @scope.klass._reflect_on_association(association)
99
+ reflection = scope_association_reflection(association)
100
100
  @scope.left_outer_joins!(association)
101
101
  @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
102
102
  end
103
103
 
104
104
  @scope
105
105
  end
106
+
107
+ private
108
+ def scope_association_reflection(association)
109
+ reflection = @scope.klass._reflect_on_association(association)
110
+ unless reflection
111
+ raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
112
+ end
113
+ reflection
114
+ end
106
115
  end
107
116
 
108
117
  FROZEN_EMPTY_ARRAY = [].freeze
@@ -424,18 +433,23 @@ module ActiveRecord
424
433
  # adapter this will either use a CASE statement or a built-in function.
425
434
  #
426
435
  # User.in_order_of(:id, [1, 5, 3])
427
- # # SELECT "users".* FROM "users" ORDER BY FIELD("users"."id", 1, 5, 3)
436
+ # # SELECT "users".* FROM "users"
437
+ # # ORDER BY FIELD("users"."id", 1, 5, 3)
438
+ # # WHERE "users"."id" IN (1, 5, 3)
428
439
  #
429
440
  def in_order_of(column, values)
430
441
  klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
442
+ return spawn.none! if values.empty?
431
443
 
432
444
  references = column_references([column])
433
445
  self.references_values |= references unless references.empty?
434
446
 
435
447
  values = values.map { |value| type_caster.type_cast_for_database(column, value) }
436
- column = order_column(column.to_s) if column.is_a?(Symbol)
448
+ arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
437
449
 
438
- spawn.order!(connection.field_ordered_value(column, values))
450
+ spawn
451
+ .order!(connection.field_ordered_value(arel_column, values))
452
+ .where!(arel_column.in(values))
439
453
  end
440
454
 
441
455
  # Replaces any existing order defined on the relation with the specified order.
@@ -33,7 +33,6 @@ module ActiveRecord
33
33
  @delegate_to_klass = false
34
34
  @future_result = nil
35
35
  @records = nil
36
- @limited_count = nil
37
36
  end
38
37
 
39
38
  def initialize_copy(other)
@@ -389,7 +388,7 @@ module ActiveRecord
389
388
  end
390
389
 
391
390
  if timestamp
392
- "#{size}-#{timestamp.utc.to_formatted_s(cache_timestamp_format)}"
391
+ "#{size}-#{timestamp.utc.to_fs(cache_timestamp_format)}"
393
392
  else
394
393
  "#{size}"
395
394
  end
@@ -647,6 +646,21 @@ module ActiveRecord
647
646
  # Schedule the query to be performed from a background thread pool.
648
647
  #
649
648
  # Post.where(published: true).load_async # => #<ActiveRecord::Relation>
649
+ #
650
+ # When the +Relation+ is iterated, if the background query wasn't executed yet,
651
+ # it will be performed by the foreground thread.
652
+ #
653
+ # Note that {config.active_record.async_query_executor}[https://guides.rubyonrails.org/configuring.html#config-active-record-async-query-executor] must be configured
654
+ # for queries to actually be executed concurrently. Otherwise it defaults to
655
+ # executing them in the foreground.
656
+ #
657
+ # +load_async+ will also fallback to executing in the foreground in the test environment when transactional
658
+ # fixtures are enabled.
659
+ #
660
+ # If the query was actually executed in the background, the Active Record logs will show
661
+ # it by prefixing the log line with <tt>ASYNC</tt>:
662
+ #
663
+ # ASYNC Post Load (0.0ms) (db time 2ms) SELECT "posts".* FROM "posts" LIMIT 100
650
664
  def load_async
651
665
  return load if !connection.async_enabled?
652
666
 
@@ -699,7 +713,6 @@ module ActiveRecord
699
713
  @offsets = @take = nil
700
714
  @cache_keys = nil
701
715
  @records = nil
702
- @limited_count = nil
703
716
  self
704
717
  end
705
718
 
@@ -974,7 +987,7 @@ module ActiveRecord
974
987
  end
975
988
 
976
989
  def limited_count
977
- @limited_count ||= limit_value ? count : limit(2).count
990
+ limit_value ? count : limit(2).count
978
991
  end
979
992
  end
980
993
  end
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  #
11
11
  # Usage:
12
12
  #
13
- # ActiveRecord::Schema.define do
13
+ # ActiveRecord::Schema[7.0].define do
14
14
  # create_table :authors do |t|
15
15
  # t.string :name, null: false
16
16
  # end
@@ -30,32 +30,47 @@ module ActiveRecord
30
30
  # ActiveRecord::Schema is only supported by database adapters that also
31
31
  # support migrations, the two features being very similar.
32
32
  class Schema < Migration::Current
33
- # Eval the given block. All methods available to the current connection
34
- # adapter are available within the block, so you can easily use the
35
- # database definition DSL to build up your schema (
36
- # {create_table}[rdoc-ref:ConnectionAdapters::SchemaStatements#create_table],
37
- # {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index], etc.).
38
- #
39
- # The +info+ hash is optional, and if given is used to define metadata
40
- # about the current schema (currently, only the schema's version):
41
- #
42
- # ActiveRecord::Schema.define(version: 2038_01_19_000001) do
43
- # ...
44
- # end
45
- def self.define(info = {}, &block)
46
- new.define(info, &block)
47
- end
33
+ module Definition
34
+ extend ActiveSupport::Concern
35
+
36
+ module ClassMethods
37
+ # Eval the given block. All methods available to the current connection
38
+ # adapter are available within the block, so you can easily use the
39
+ # database definition DSL to build up your schema (
40
+ # {create_table}[rdoc-ref:ConnectionAdapters::SchemaStatements#create_table],
41
+ # {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index], etc.).
42
+ #
43
+ # The +info+ hash is optional, and if given is used to define metadata
44
+ # about the current schema (currently, only the schema's version):
45
+ #
46
+ # ActiveRecord::Schema[7.0].define(version: 2038_01_19_000001) do
47
+ # ...
48
+ # end
49
+ def define(info = {}, &block)
50
+ new.define(info, &block)
51
+ end
52
+ end
53
+
54
+ def define(info, &block) # :nodoc:
55
+ instance_eval(&block)
48
56
 
49
- def define(info, &block) # :nodoc:
50
- instance_eval(&block)
57
+ if info[:version].present?
58
+ connection.schema_migration.create_table
59
+ connection.assume_migrated_upto_version(info[:version])
60
+ end
51
61
 
52
- if info[:version].present?
53
- connection.schema_migration.create_table
54
- connection.assume_migrated_upto_version(info[:version])
62
+ ActiveRecord::InternalMetadata.create_table
63
+ ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
55
64
  end
65
+ end
66
+
67
+ include Definition
56
68
 
57
- ActiveRecord::InternalMetadata.create_table
58
- ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
69
+ def self.[](version)
70
+ @class_for_version ||= {}
71
+ @class_for_version[version] ||= Class.new(Migration::Compatibility.find(version)) do
72
+ include Definition
73
+ end
59
74
  end
60
75
  end
61
76
  end
@@ -74,22 +74,21 @@ module ActiveRecord
74
74
  end
75
75
 
76
76
  def header(stream)
77
- stream.puts <<HEADER
78
- # This file is auto-generated from the current state of the database. Instead
79
- # of editing this file, please use the migrations feature of Active Record to
80
- # incrementally modify your database, and then regenerate this schema definition.
81
- #
82
- # This file is the source Rails uses to define your schema when running `bin/rails
83
- # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
84
- # be faster and is potentially less error prone than running all of your
85
- # migrations from scratch. Old migrations may fail to apply correctly if those
86
- # migrations use external dependencies or application code.
87
- #
88
- # It's strongly recommended that you check this file into your version control system.
89
-
90
- ActiveRecord::Schema.define(#{define_params}) do
91
-
92
- HEADER
77
+ stream.puts <<~HEADER
78
+ # This file is auto-generated from the current state of the database. Instead
79
+ # of editing this file, please use the migrations feature of Active Record to
80
+ # incrementally modify your database, and then regenerate this schema definition.
81
+ #
82
+ # This file is the source Rails uses to define your schema when running `bin/rails
83
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
84
+ # be faster and is potentially less error prone than running all of your
85
+ # migrations from scratch. Old migrations may fail to apply correctly if those
86
+ # migrations use external dependencies or application code.
87
+ #
88
+ # It's strongly recommended that you check this file into your version control system.
89
+
90
+ ActiveRecord::Schema[#{ActiveRecord::Migration.current_version}].define(#{define_params}) do
91
+ HEADER
93
92
  end
94
93
 
95
94
  def trailer(stream)
@@ -41,6 +41,10 @@ module ActiveRecord
41
41
  def all_versions
42
42
  order(:version).pluck(:version)
43
43
  end
44
+
45
+ def table_exists?
46
+ connection.data_source_exists?(table_name)
47
+ end
44
48
  end
45
49
 
46
50
  def version
@@ -257,8 +257,12 @@ module ActiveRecord
257
257
  scope = ENV["SCOPE"]
258
258
  verbose_was, Migration.verbose = Migration.verbose, verbose?
259
259
 
260
- Base.connection.migration_context.migrate(target_version || version) do |migration|
261
- scope.blank? || scope == migration.scope
260
+ Base.connection.migration_context.migrate(target_version) do |migration|
261
+ if version.blank?
262
+ scope.blank? || scope == migration.scope
263
+ else
264
+ migration.version == version
265
+ end
262
266
  end.tap do |migrations_ran|
263
267
  Migration.write("No migrations ran. (using #{scope} scope)") if scope.present? && migrations_ran.empty?
264
268
  end
@@ -58,7 +58,7 @@ module ActiveRecord
58
58
  end
59
59
 
60
60
  args = ["--schema-only", "--no-privileges", "--no-owner"]
61
- args << "--no-comment" if connection.database_version >= 110_000
61
+ args << "--no-comments" if connection.database_version >= 110_000
62
62
  args.concat(["--file", filename])
63
63
 
64
64
  args.concat(Array(extra_flags)) if extra_flags
data/lib/active_record.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #--
4
- # Copyright (c) 2004-2021 David Heinemeier Hansson
4
+ # Copyright (c) 2004-2022 David Heinemeier Hansson
5
5
  #
6
6
  # Permission is hereby granted, free of charge, to any person obtaining
7
7
  # a copy of this software and associated documentation files (the
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/active_record"
4
+
5
+ module ActiveRecord
6
+ module Generators # :nodoc:
7
+ class MultiDbGenerator < ::Rails::Generators::Base # :nodoc:
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ def create_multi_db
11
+ filename = "multi_db.rb"
12
+ template filename, "config/initializers/#{filename}"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,44 @@
1
+ # Multi-db Configuration
2
+ #
3
+ # This file is used for configuration settings related to multiple databases.
4
+ #
5
+ # Enable Database Selector
6
+ #
7
+ # Inserts middleware to perform automatic connection switching.
8
+ # The `database_selector` hash is used to pass options to the DatabaseSelector
9
+ # middleware. The `delay` is used to determine how long to wait after a write
10
+ # to send a subsequent read to the primary.
11
+ #
12
+ # The `database_resolver` class is used by the middleware to determine which
13
+ # database is appropriate to use based on the time delay.
14
+ #
15
+ # The `database_resolver_context` class is used by the middleware to set
16
+ # timestamps for the last write to the primary. The resolver uses the context
17
+ # class timestamps to determine how long to wait before reading from the
18
+ # replica.
19
+ #
20
+ # By default Rails will store a last write timestamp in the session. The
21
+ # DatabaseSelector middleware is designed as such you can define your own
22
+ # strategy for connection switching and pass that into the middleware through
23
+ # these configuration options.
24
+ #
25
+ # Rails.application.configure do
26
+ # config.active_record.database_selector = { delay: 2.seconds }
27
+ # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
28
+ # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
29
+ # end
30
+ #
31
+ # Enable Shard Selector
32
+ #
33
+ # Inserts middleware to perform automatic shard swapping. The `shard_selector` hash
34
+ # can be used to pass options to the `ShardSelector` middleware. The `lock` option is
35
+ # used to determine whether shard swapping should be prohibited for the request.
36
+ #
37
+ # The `shard_resolver` option is used by the middleware to determine which shard
38
+ # to switch to. The application must provide a mechanism for finding the shard name
39
+ # in a proc. See guides for an example.
40
+ #
41
+ # Rails.application.configure do
42
+ # config.active_record.shard_selector = { lock: true }
43
+ # config.active_record.shard_resolver = ->(request) { Tenant.find_by!(host: request.host).shard }
44
+ # end