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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +200 -3
- data/MIT-LICENSE +1 -1
- data/lib/active_record/associations/join_dependency.rb +6 -2
- data/lib/active_record/associations.rb +29 -8
- data/lib/active_record/attribute_methods.rb +1 -1
- data/lib/active_record/autosave_association.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -4
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +9 -4
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +3 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +1 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +9 -4
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +2 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +12 -12
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +28 -0
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/encryption/configurable.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +1 -1
- data/lib/active_record/encryption/extended_deterministic_queries.rb +28 -28
- data/lib/active_record/fixtures.rb +1 -1
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/integration.rb +2 -2
- data/lib/active_record/migration/compatibility.rb +24 -2
- data/lib/active_record/migration.rb +1 -1
- data/lib/active_record/railtie.rb +2 -2
- data/lib/active_record/reflection.rb +1 -1
- data/lib/active_record/relation/calculations.rb +3 -2
- data/lib/active_record/relation/delegation.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +19 -5
- data/lib/active_record/relation.rb +17 -4
- data/lib/active_record/schema.rb +38 -23
- data/lib/active_record/schema_dumper.rb +15 -16
- data/lib/active_record/schema_migration.rb +4 -0
- data/lib/active_record/tasks/database_tasks.rb +6 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record.rb +1 -1
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- 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)
|
@@ -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:</
|
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
|
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?(
|
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
|
-
#
|
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)
|
@@ -79,7 +79,7 @@ module ActiveRecord
|
|
79
79
|
timestamp = max_updated_column_timestamp
|
80
80
|
|
81
81
|
if timestamp
|
82
|
-
timestamp = timestamp.utc.
|
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.
|
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
|
-
|
135
|
-
.
|
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
|
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.
|
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
|
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 =
|
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 =
|
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"
|
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
|
-
|
448
|
+
arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
|
437
449
|
|
438
|
-
spawn
|
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.
|
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
|
-
|
990
|
+
limit_value ? count : limit(2).count
|
978
991
|
end
|
979
992
|
end
|
980
993
|
end
|
data/lib/active_record/schema.rb
CHANGED
@@ -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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
50
|
-
|
57
|
+
if info[:version].present?
|
58
|
+
connection.schema_migration.create_table
|
59
|
+
connection.assume_migrated_upto_version(info[:version])
|
60
|
+
end
|
51
61
|
|
52
|
-
|
53
|
-
connection.
|
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
|
-
|
58
|
-
|
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
|
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)
|
@@ -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
|
261
|
-
|
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-
|
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-
|
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
|