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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +455 -9
- data/README.rdoc +3 -1
- data/lib/active_record/associations/association.rb +18 -1
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +5 -2
- data/lib/active_record/associations/builder/collection_association.rb +5 -15
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_association.rb +5 -6
- data/lib/active_record/associations/collection_proxy.rb +13 -42
- data/lib/active_record/associations/has_many_association.rb +1 -9
- data/lib/active_record/associations/has_many_through_association.rb +4 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
- data/lib/active_record/associations/join_dependency.rb +10 -9
- data/lib/active_record/associations/preloader/association.rb +37 -34
- data/lib/active_record/associations/preloader/through_association.rb +48 -39
- data/lib/active_record/associations/preloader.rb +11 -6
- data/lib/active_record/associations.rb +3 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +47 -14
- data/lib/active_record/attribute_methods/primary_key.rb +7 -15
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +3 -9
- data/lib/active_record/attribute_methods/write.rb +6 -12
- data/lib/active_record/attribute_methods.rb +3 -53
- data/lib/active_record/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +15 -5
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/callbacks.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +124 -23
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +101 -70
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +11 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
- data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
- data/lib/active_record/connection_adapters/abstract_adapter.rb +108 -39
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +93 -134
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +1 -1
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +91 -24
- data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +69 -118
- data/lib/active_record/connection_handling.rb +32 -16
- data/lib/active_record/core.rb +27 -20
- data/lib/active_record/database_configurations/hash_config.rb +11 -11
- data/lib/active_record/database_configurations/url_config.rb +21 -16
- data/lib/active_record/database_configurations.rb +99 -50
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +15 -0
- data/lib/active_record/errors.rb +18 -13
- data/lib/active_record/fixtures.rb +11 -6
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +13 -1
- data/lib/active_record/internal_metadata.rb +5 -1
- data/lib/active_record/locking/optimistic.rb +3 -4
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +75 -0
- data/lib/active_record/migration/command_recorder.rb +28 -14
- data/lib/active_record/migration/compatibility.rb +72 -63
- data/lib/active_record/migration.rb +62 -44
- data/lib/active_record/persistence.rb +212 -19
- data/lib/active_record/querying.rb +18 -14
- data/lib/active_record/railtie.rb +9 -1
- data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
- data/lib/active_record/railties/databases.rake +124 -25
- data/lib/active_record/reflection.rb +18 -32
- data/lib/active_record/relation/calculations.rb +40 -44
- data/lib/active_record/relation/delegation.rb +23 -31
- data/lib/active_record/relation/finder_methods.rb +13 -13
- data/lib/active_record/relation/merger.rb +11 -16
- data/lib/active_record/relation/query_attribute.rb +5 -3
- data/lib/active_record/relation/query_methods.rb +217 -68
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +10 -10
- data/lib/active_record/relation.rb +184 -35
- data/lib/active_record/sanitization.rb +33 -4
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +10 -1
- data/lib/active_record/schema_migration.rb +1 -1
- data/lib/active_record/scoping/default.rb +7 -15
- data/lib/active_record/scoping/named.rb +10 -2
- data/lib/active_record/scoping.rb +6 -7
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/store.rb +48 -0
- data/lib/active_record/table_metadata.rb +9 -13
- data/lib/active_record/tasks/database_tasks.rb +109 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
- data/lib/active_record/test_databases.rb +1 -16
- data/lib/active_record/test_fixtures.rb +2 -2
- data/lib/active_record/timestamp.rb +35 -19
- data/lib/active_record/touch_later.rb +4 -2
- data/lib/active_record/transactions.rb +55 -45
- data/lib/active_record/type_caster/connection.rb +16 -10
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record.rb +7 -1
- data/lib/arel/insert_manager.rb +3 -3
- data/lib/arel/nodes/and.rb +1 -1
- data/lib/arel/nodes/case.rb +1 -1
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/select_core.rb +16 -12
- data/lib/arel/nodes/unary.rb +1 -0
- data/lib/arel/nodes/values_list.rb +2 -17
- data/lib/arel/nodes.rb +2 -1
- data/lib/arel/select_manager.rb +10 -10
- data/lib/arel/visitors/depth_first.rb +7 -2
- data/lib/arel/visitors/dot.rb +7 -2
- data/lib/arel/visitors/ibm_db.rb +13 -0
- data/lib/arel/visitors/informix.rb +6 -0
- data/lib/arel/visitors/mssql.rb +15 -1
- data/lib/arel/visitors/oracle12.rb +4 -5
- data/lib/arel/visitors/postgresql.rb +4 -10
- data/lib/arel/visitors/to_sql.rb +107 -131
- data/lib/arel/visitors/visitor.rb +9 -5
- data/lib/arel.rb +7 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +17 -13
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/arel/nodes/values.rb +0 -16
@@ -7,8 +7,10 @@ require "active_record/database_configurations/url_config"
|
|
7
7
|
module ActiveRecord
|
8
8
|
# ActiveRecord::DatabaseConfigurations returns an array of DatabaseConfig
|
9
9
|
# objects (either a HashConfig or UrlConfig) that are constructed from the
|
10
|
-
# application's database configuration hash or
|
10
|
+
# application's database configuration hash or URL string.
|
11
11
|
class DatabaseConfigurations
|
12
|
+
class InvalidConfigurationError < StandardError; end
|
13
|
+
|
12
14
|
attr_reader :configurations
|
13
15
|
delegate :any?, to: :configurations
|
14
16
|
|
@@ -17,22 +19,22 @@ module ActiveRecord
|
|
17
19
|
end
|
18
20
|
|
19
21
|
# Collects the configs for the environment and optionally the specification
|
20
|
-
# name passed in. To include replica configurations pass
|
22
|
+
# name passed in. To include replica configurations pass <tt>include_replicas: true</tt>.
|
21
23
|
#
|
22
24
|
# If a spec name is provided a single DatabaseConfig object will be
|
23
25
|
# returned, otherwise an array of DatabaseConfig objects will be
|
24
26
|
# returned that corresponds with the environment and type requested.
|
25
27
|
#
|
26
|
-
# Options
|
28
|
+
# ==== Options
|
27
29
|
#
|
28
|
-
# <tt>env_name:</tt> The environment name. Defaults to nil which will collect
|
29
|
-
#
|
30
|
-
# <tt>spec_name:</tt> The specification name (
|
31
|
-
#
|
32
|
-
# <tt>include_replicas:</tt> Determines whether to include replicas in
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
30
|
+
# * <tt>env_name:</tt> The environment name. Defaults to +nil+ which will collect
|
31
|
+
# configs for all environments.
|
32
|
+
# * <tt>spec_name:</tt> The specification name (i.e. primary, animals, etc.). Defaults
|
33
|
+
# to +nil+.
|
34
|
+
# * <tt>include_replicas:</tt> Determines whether to include replicas in
|
35
|
+
# the returned list. Most of the time we're only iterating over the write
|
36
|
+
# connection (i.e. migrations don't need to run for the write and read connection).
|
37
|
+
# Defaults to +false+.
|
36
38
|
def configs_for(env_name: nil, spec_name: nil, include_replicas: false)
|
37
39
|
configs = env_with_configs(env_name)
|
38
40
|
|
@@ -53,7 +55,7 @@ module ActiveRecord
|
|
53
55
|
|
54
56
|
# Returns the config hash that corresponds with the environment
|
55
57
|
#
|
56
|
-
# If the application has multiple databases
|
58
|
+
# If the application has multiple databases +default_hash+ will
|
57
59
|
# return the first config hash for the environment.
|
58
60
|
#
|
59
61
|
# { database: "my_db", adapter: "mysql2" }
|
@@ -65,7 +67,7 @@ module ActiveRecord
|
|
65
67
|
|
66
68
|
# Returns a single DatabaseConfig object based on the requested environment.
|
67
69
|
#
|
68
|
-
# If the application has multiple databases
|
70
|
+
# If the application has multiple databases +find_db_config+ will return
|
69
71
|
# the first DatabaseConfig for the environment.
|
70
72
|
def find_db_config(env)
|
71
73
|
configurations.find do |db_config|
|
@@ -91,6 +93,19 @@ module ActiveRecord
|
|
91
93
|
end
|
92
94
|
alias :blank? :empty?
|
93
95
|
|
96
|
+
def each
|
97
|
+
throw_getter_deprecation(:each)
|
98
|
+
configurations.each { |config|
|
99
|
+
yield [config.env_name, config.config]
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
def first
|
104
|
+
throw_getter_deprecation(:first)
|
105
|
+
config = configurations.first
|
106
|
+
[config.env_name, config.config]
|
107
|
+
end
|
108
|
+
|
94
109
|
private
|
95
110
|
def env_with_configs(env = nil)
|
96
111
|
if env
|
@@ -102,83 +117,117 @@ module ActiveRecord
|
|
102
117
|
|
103
118
|
def build_configs(configs)
|
104
119
|
return configs.configurations if configs.is_a?(DatabaseConfigurations)
|
120
|
+
return configs if configs.is_a?(Array)
|
105
121
|
|
106
|
-
|
107
|
-
|
108
|
-
|
122
|
+
db_configs = configs.flat_map do |env_name, config|
|
123
|
+
if config.is_a?(Hash) && config.all? { |_, v| v.is_a?(Hash) }
|
124
|
+
walk_configs(env_name.to_s, config)
|
125
|
+
else
|
126
|
+
build_db_config_from_raw_config(env_name.to_s, "primary", config)
|
127
|
+
end
|
128
|
+
end
|
109
129
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
130
|
+
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
|
131
|
+
|
132
|
+
unless db_configs.find(&:for_current_env?)
|
133
|
+
db_configs << environment_url_config(current_env, "primary", {})
|
114
134
|
end
|
135
|
+
|
136
|
+
merge_db_environment_variables(current_env, db_configs.compact)
|
115
137
|
end
|
116
138
|
|
117
|
-
def walk_configs(env_name,
|
139
|
+
def walk_configs(env_name, config)
|
140
|
+
config.map do |spec_name, sub_config|
|
141
|
+
build_db_config_from_raw_config(env_name, spec_name.to_s, sub_config)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def build_db_config_from_raw_config(env_name, spec_name, config)
|
118
146
|
case config
|
119
147
|
when String
|
120
148
|
build_db_config_from_string(env_name, spec_name, config)
|
121
149
|
when Hash
|
122
150
|
build_db_config_from_hash(env_name, spec_name, config.stringify_keys)
|
151
|
+
else
|
152
|
+
raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
|
123
153
|
end
|
124
154
|
end
|
125
155
|
|
126
156
|
def build_db_config_from_string(env_name, spec_name, config)
|
127
157
|
url = config
|
128
158
|
uri = URI.parse(url)
|
129
|
-
if uri.
|
159
|
+
if uri.scheme
|
130
160
|
ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url)
|
161
|
+
else
|
162
|
+
raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
|
131
163
|
end
|
132
|
-
rescue URI::InvalidURIError
|
133
|
-
ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
|
134
164
|
end
|
135
165
|
|
136
166
|
def build_db_config_from_hash(env_name, spec_name, config)
|
137
|
-
if
|
167
|
+
if config.has_key?("url")
|
168
|
+
url = config["url"]
|
138
169
|
config_without_url = config.dup
|
139
170
|
config_without_url.delete "url"
|
171
|
+
|
140
172
|
ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url, config_without_url)
|
141
|
-
elsif config["database"] || (config.size == 1 && config.values.all? { |v| v.is_a? String })
|
142
|
-
ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
|
143
173
|
else
|
144
|
-
|
145
|
-
walk_configs(env_name, sub_spec_name, sub_config)
|
146
|
-
end
|
174
|
+
ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
|
147
175
|
end
|
148
176
|
end
|
149
177
|
|
150
|
-
def
|
151
|
-
|
178
|
+
def merge_db_environment_variables(current_env, configs)
|
179
|
+
configs.map do |config|
|
180
|
+
next config if config.url_config? || config.env_name != current_env
|
152
181
|
|
153
|
-
|
154
|
-
|
155
|
-
configs
|
156
|
-
else
|
157
|
-
configs.map do |config|
|
158
|
-
ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, config.spec_name, url, config.config)
|
159
|
-
end
|
160
|
-
end
|
161
|
-
else
|
162
|
-
configs + [ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, "primary", url)]
|
182
|
+
url_config = environment_url_config(current_env, config.spec_name, config.config)
|
183
|
+
url_config || config
|
163
184
|
end
|
164
185
|
end
|
165
186
|
|
166
|
-
def
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
187
|
+
def environment_url_config(env, spec_name, config)
|
188
|
+
url = environment_value_for(spec_name)
|
189
|
+
return unless url
|
190
|
+
|
191
|
+
ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, spec_name, url, config)
|
192
|
+
end
|
193
|
+
|
194
|
+
def environment_value_for(spec_name)
|
195
|
+
spec_env_key = "#{spec_name.upcase}_DATABASE_URL"
|
196
|
+
url = ENV[spec_env_key]
|
197
|
+
url ||= ENV["DATABASE_URL"] if spec_name == "primary"
|
198
|
+
url
|
199
|
+
end
|
171
200
|
|
201
|
+
def method_missing(method, *args, &blk)
|
172
202
|
case method
|
173
|
-
when :each, :first
|
174
|
-
configurations.send(method, *args, &blk)
|
175
203
|
when :fetch
|
204
|
+
throw_getter_deprecation(method)
|
176
205
|
configs_for(env_name: args.first)
|
177
206
|
when :values
|
207
|
+
throw_getter_deprecation(method)
|
178
208
|
configurations.map(&:config)
|
209
|
+
when :[]=
|
210
|
+
throw_setter_deprecation(method)
|
211
|
+
|
212
|
+
env_name = args[0]
|
213
|
+
config = args[1]
|
214
|
+
|
215
|
+
remaining_configs = configurations.reject { |db_config| db_config.env_name == env_name }
|
216
|
+
new_config = build_configs(env_name => config)
|
217
|
+
new_configs = remaining_configs + new_config
|
218
|
+
|
219
|
+
ActiveRecord::Base.configurations = new_configs
|
179
220
|
else
|
180
|
-
|
221
|
+
raise NotImplementedError, "`ActiveRecord::Base.configurations` in Rails 6 now returns an object instead of a hash. The `#{method}` method is not supported. Please use `configs_for` or consult the documentation for supported methods."
|
181
222
|
end
|
182
223
|
end
|
224
|
+
|
225
|
+
def throw_setter_deprecation(method)
|
226
|
+
ActiveSupport::Deprecation.warn("Setting `ActiveRecord::Base.configurations` with `#{method}` is deprecated. Use `ActiveRecord::Base.configurations=` directly to set the configurations instead.")
|
227
|
+
end
|
228
|
+
|
229
|
+
def throw_getter_deprecation(method)
|
230
|
+
ActiveSupport::Deprecation.warn("`ActiveRecord::Base.configurations` no longer returns a hash. Methods that act on the hash like `#{method}` are deprecated and will be removed in Rails 6.1. Use the `configs_for` method to collect and iterate over the database configurations.")
|
231
|
+
end
|
183
232
|
end
|
184
233
|
end
|
@@ -53,7 +53,7 @@ module ActiveRecord
|
|
53
53
|
@model = model
|
54
54
|
@name = name.to_s
|
55
55
|
@attribute_names = @name.match(self.class.pattern)[1].split("_and_")
|
56
|
-
@attribute_names.map! { |
|
56
|
+
@attribute_names.map! { |name| @model.attribute_aliases[name] || name }
|
57
57
|
end
|
58
58
|
|
59
59
|
def valid?
|
data/lib/active_record/enum.rb
CHANGED
@@ -31,7 +31,9 @@ module ActiveRecord
|
|
31
31
|
# as well. With the above example:
|
32
32
|
#
|
33
33
|
# Conversation.active
|
34
|
+
# Conversation.not_active
|
34
35
|
# Conversation.archived
|
36
|
+
# Conversation.not_archived
|
35
37
|
#
|
36
38
|
# Of course, you can also query them directly if the scopes don't fit your
|
37
39
|
# needs:
|
@@ -196,9 +198,15 @@ module ActiveRecord
|
|
196
198
|
define_method("#{value_method_name}!") { update!(attr => value) }
|
197
199
|
|
198
200
|
# scope :active, -> { where(status: 0) }
|
201
|
+
# scope :not_active, -> { where.not(status: 0) }
|
199
202
|
if enum_scopes != false
|
203
|
+
klass.send(:detect_negative_condition!, value_method_name)
|
204
|
+
|
200
205
|
klass.send(:detect_enum_conflict!, name, value_method_name, true)
|
201
206
|
klass.scope value_method_name, -> { where(attr => value) }
|
207
|
+
|
208
|
+
klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
|
209
|
+
klass.scope "not_#{value_method_name}", -> { where.not(attr => value) }
|
202
210
|
end
|
203
211
|
end
|
204
212
|
end
|
@@ -255,5 +263,12 @@ module ActiveRecord
|
|
255
263
|
source: source
|
256
264
|
}
|
257
265
|
end
|
266
|
+
|
267
|
+
def detect_negative_condition!(method_name)
|
268
|
+
if method_name.start_with?("not_") && logger
|
269
|
+
logger.warn "An enum element in #{self.name} uses the prefix 'not_'." \
|
270
|
+
" This will cause a conflict with auto generated negative scopes."
|
271
|
+
end
|
272
|
+
end
|
258
273
|
end
|
259
274
|
end
|
data/lib/active_record/errors.rb
CHANGED
@@ -68,7 +68,7 @@ module ActiveRecord
|
|
68
68
|
|
69
69
|
# Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
|
70
70
|
# {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
|
71
|
-
# methods when a record is invalid and
|
71
|
+
# methods when a record is invalid and cannot be saved.
|
72
72
|
class RecordNotSaved < ActiveRecordError
|
73
73
|
attr_reader :record
|
74
74
|
|
@@ -126,16 +126,26 @@ module ActiveRecord
|
|
126
126
|
|
127
127
|
# Raised when a foreign key constraint cannot be added because the column type does not match the referenced column type.
|
128
128
|
class MismatchedForeignKey < StatementInvalid
|
129
|
-
def initialize(
|
130
|
-
|
129
|
+
def initialize(
|
130
|
+
message: nil,
|
131
|
+
sql: nil,
|
132
|
+
binds: nil,
|
133
|
+
table: nil,
|
134
|
+
foreign_key: nil,
|
135
|
+
target_table: nil,
|
136
|
+
primary_key: nil,
|
137
|
+
primary_key_column: nil
|
138
|
+
)
|
131
139
|
if table
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
140
|
+
type = primary_key_column.bigint? ? :bigint : primary_key_column.type
|
141
|
+
msg = <<~EOM.squish
|
142
|
+
Column `#{foreign_key}` on table `#{table}` does not match column `#{primary_key}` on `#{target_table}`,
|
143
|
+
which has type `#{primary_key_column.sql_type}`.
|
144
|
+
To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :#{type}.
|
145
|
+
(For example `t.#{type} :#{foreign_key}`).
|
136
146
|
EOM
|
137
147
|
else
|
138
|
-
msg =
|
148
|
+
msg = <<~EOM.squish
|
139
149
|
There is a mismatch between the foreign key and primary key column types.
|
140
150
|
Verify that the foreign key column type and the primary key of the associated table match types.
|
141
151
|
EOM
|
@@ -145,11 +155,6 @@ module ActiveRecord
|
|
145
155
|
end
|
146
156
|
super(msg, sql: sql, binds: binds)
|
147
157
|
end
|
148
|
-
|
149
|
-
private
|
150
|
-
def column_type(table, column)
|
151
|
-
@adapter.columns(table).detect { |c| c.name == column }.sql_type
|
152
|
-
end
|
153
158
|
end
|
154
159
|
|
155
160
|
# Raised when a record cannot be inserted or updated because it would violate a not null constraint.
|
@@ -531,15 +531,15 @@ module ActiveRecord
|
|
531
531
|
end
|
532
532
|
end
|
533
533
|
|
534
|
-
def create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base)
|
534
|
+
def create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base, &block)
|
535
535
|
fixture_set_names = Array(fixture_set_names).map(&:to_s)
|
536
536
|
class_names = ClassCache.new class_names, config
|
537
537
|
|
538
538
|
# FIXME: Apparently JK uses this.
|
539
|
-
connection = block_given? ?
|
539
|
+
connection = block_given? ? block : lambda { ActiveRecord::Base.connection }
|
540
540
|
|
541
541
|
fixture_files_to_read = fixture_set_names.reject do |fs_name|
|
542
|
-
fixture_is_cached?(connection, fs_name)
|
542
|
+
fixture_is_cached?(connection.call, fs_name)
|
543
543
|
end
|
544
544
|
|
545
545
|
if fixture_files_to_read.any?
|
@@ -549,9 +549,9 @@ module ActiveRecord
|
|
549
549
|
class_names,
|
550
550
|
connection,
|
551
551
|
)
|
552
|
-
cache_fixtures(connection, fixtures_map)
|
552
|
+
cache_fixtures(connection.call, fixtures_map)
|
553
553
|
end
|
554
|
-
cached_fixtures(connection, fixture_set_names)
|
554
|
+
cached_fixtures(connection.call, fixture_set_names)
|
555
555
|
end
|
556
556
|
|
557
557
|
# Returns a consistent, platform-independent identifier for +label+.
|
@@ -591,7 +591,11 @@ module ActiveRecord
|
|
591
591
|
|
592
592
|
def insert(fixture_sets, connection) # :nodoc:
|
593
593
|
fixture_sets_by_connection = fixture_sets.group_by do |fixture_set|
|
594
|
-
fixture_set.model_class
|
594
|
+
if fixture_set.model_class
|
595
|
+
fixture_set.model_class.connection
|
596
|
+
else
|
597
|
+
connection.call
|
598
|
+
end
|
595
599
|
end
|
596
600
|
|
597
601
|
fixture_sets_by_connection.each do |conn, set|
|
@@ -602,6 +606,7 @@ module ActiveRecord
|
|
602
606
|
table_rows_for_connection[table].unshift(*rows)
|
603
607
|
end
|
604
608
|
end
|
609
|
+
|
605
610
|
conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys)
|
606
611
|
|
607
612
|
# Cap primary key sequences to max(pk).
|
@@ -249,7 +249,7 @@ module ActiveRecord
|
|
249
249
|
sti_column = arel_attribute(inheritance_column, table)
|
250
250
|
sti_names = ([self] + descendants).map(&:sti_name)
|
251
251
|
|
252
|
-
|
252
|
+
predicate_builder.build(sti_column, sti_names)
|
253
253
|
end
|
254
254
|
|
255
255
|
# Detect the subclass from the inheritance column of attrs. If the inheritance column value
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class InsertAll # :nodoc:
|
5
|
+
attr_reader :model, :connection, :inserts, :keys
|
6
|
+
attr_reader :on_duplicate, :returning, :unique_by
|
7
|
+
|
8
|
+
def initialize(model, inserts, on_duplicate:, returning: nil, unique_by: nil)
|
9
|
+
raise ArgumentError, "Empty list of attributes passed" if inserts.blank?
|
10
|
+
|
11
|
+
@model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s).to_set
|
12
|
+
@on_duplicate, @returning, @unique_by = on_duplicate, returning, unique_by
|
13
|
+
|
14
|
+
@returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
|
15
|
+
@returning = false if @returning == []
|
16
|
+
|
17
|
+
@unique_by = find_unique_index_for(unique_by) if unique_by
|
18
|
+
@on_duplicate = :skip if @on_duplicate == :update && updatable_columns.empty?
|
19
|
+
|
20
|
+
ensure_valid_options_for_connection!
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute
|
24
|
+
message = +"#{model} "
|
25
|
+
message << "Bulk " if inserts.many?
|
26
|
+
message << (on_duplicate == :update ? "Upsert" : "Insert")
|
27
|
+
connection.exec_query to_sql, message
|
28
|
+
end
|
29
|
+
|
30
|
+
def updatable_columns
|
31
|
+
keys - readonly_columns - unique_by_columns
|
32
|
+
end
|
33
|
+
|
34
|
+
def primary_keys
|
35
|
+
Array(model.primary_key)
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def skip_duplicates?
|
40
|
+
on_duplicate == :skip
|
41
|
+
end
|
42
|
+
|
43
|
+
def update_duplicates?
|
44
|
+
on_duplicate == :update
|
45
|
+
end
|
46
|
+
|
47
|
+
def map_key_with_value
|
48
|
+
inserts.map do |attributes|
|
49
|
+
attributes = attributes.stringify_keys
|
50
|
+
verify_attributes(attributes)
|
51
|
+
|
52
|
+
keys.map do |key|
|
53
|
+
yield key, attributes[key]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def find_unique_index_for(unique_by)
|
60
|
+
match = Array(unique_by).map(&:to_s)
|
61
|
+
|
62
|
+
if index = unique_indexes.find { |i| match.include?(i.name) || i.columns == match }
|
63
|
+
index
|
64
|
+
else
|
65
|
+
raise ArgumentError, "No unique index found for #{unique_by}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def unique_indexes
|
70
|
+
connection.schema_cache.indexes(model.table_name).select(&:unique)
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def ensure_valid_options_for_connection!
|
75
|
+
if returning && !connection.supports_insert_returning?
|
76
|
+
raise ArgumentError, "#{connection.class} does not support :returning"
|
77
|
+
end
|
78
|
+
|
79
|
+
if skip_duplicates? && !connection.supports_insert_on_duplicate_skip?
|
80
|
+
raise ArgumentError, "#{connection.class} does not support skipping duplicates"
|
81
|
+
end
|
82
|
+
|
83
|
+
if update_duplicates? && !connection.supports_insert_on_duplicate_update?
|
84
|
+
raise ArgumentError, "#{connection.class} does not support upsert"
|
85
|
+
end
|
86
|
+
|
87
|
+
if unique_by && !connection.supports_insert_conflict_target?
|
88
|
+
raise ArgumentError, "#{connection.class} does not support :unique_by"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def to_sql
|
94
|
+
connection.build_insert_sql(ActiveRecord::InsertAll::Builder.new(self))
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def readonly_columns
|
99
|
+
primary_keys + model.readonly_attributes.to_a
|
100
|
+
end
|
101
|
+
|
102
|
+
def unique_by_columns
|
103
|
+
Array(unique_by&.columns)
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
def verify_attributes(attributes)
|
108
|
+
if keys != attributes.keys.to_set
|
109
|
+
raise ArgumentError, "All objects being inserted must have the same keys"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class Builder # :nodoc:
|
114
|
+
attr_reader :model
|
115
|
+
|
116
|
+
delegate :skip_duplicates?, :update_duplicates?, :keys, to: :insert_all
|
117
|
+
|
118
|
+
def initialize(insert_all)
|
119
|
+
@insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
|
120
|
+
end
|
121
|
+
|
122
|
+
def into
|
123
|
+
"INTO #{model.quoted_table_name}(#{columns_list})"
|
124
|
+
end
|
125
|
+
|
126
|
+
def values_list
|
127
|
+
types = extract_types_from_columns_on(model.table_name, keys: keys)
|
128
|
+
|
129
|
+
values_list = insert_all.map_key_with_value do |key, value|
|
130
|
+
connection.with_yaml_fallback(types[key].serialize(value))
|
131
|
+
end
|
132
|
+
|
133
|
+
Arel::InsertManager.new.create_values_list(values_list).to_sql
|
134
|
+
end
|
135
|
+
|
136
|
+
def returning
|
137
|
+
format_columns(insert_all.returning) if insert_all.returning
|
138
|
+
end
|
139
|
+
|
140
|
+
def conflict_target
|
141
|
+
if index = insert_all.unique_by
|
142
|
+
sql = +"(#{format_columns(index.columns)})"
|
143
|
+
sql << " WHERE #{index.where}" if index.where
|
144
|
+
sql
|
145
|
+
elsif update_duplicates?
|
146
|
+
"(#{format_columns(insert_all.primary_keys)})"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def updatable_columns
|
151
|
+
quote_columns(insert_all.updatable_columns)
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
attr_reader :connection, :insert_all
|
156
|
+
|
157
|
+
def columns_list
|
158
|
+
format_columns(insert_all.keys)
|
159
|
+
end
|
160
|
+
|
161
|
+
def extract_types_from_columns_on(table_name, keys:)
|
162
|
+
columns = connection.schema_cache.columns_hash(table_name)
|
163
|
+
|
164
|
+
unknown_column = (keys - columns.keys).first
|
165
|
+
raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column
|
166
|
+
|
167
|
+
keys.map { |key| [ key, connection.lookup_cast_type_from_column(columns[key]) ] }.to_h
|
168
|
+
end
|
169
|
+
|
170
|
+
def format_columns(columns)
|
171
|
+
quote_columns(columns).join(",")
|
172
|
+
end
|
173
|
+
|
174
|
+
def quote_columns(columns)
|
175
|
+
columns.map(&connection.method(:quote_column_name))
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -22,6 +22,14 @@ module ActiveRecord
|
|
22
22
|
#
|
23
23
|
# This is +true+, by default on Rails 5.2 and above.
|
24
24
|
class_attribute :cache_versioning, instance_writer: false, default: false
|
25
|
+
|
26
|
+
##
|
27
|
+
# :singleton-method:
|
28
|
+
# Indicates whether to use a stable #cache_key method that is accompanied
|
29
|
+
# by a changing version in the #cache_version method on collections.
|
30
|
+
#
|
31
|
+
# This is +false+, by default until Rails 6.1.
|
32
|
+
class_attribute :collection_cache_versioning, instance_writer: false, default: false
|
25
33
|
end
|
26
34
|
|
27
35
|
# Returns a +String+, which Action Pack uses for constructing a URL to this
|
@@ -152,6 +160,10 @@ module ActiveRecord
|
|
152
160
|
end
|
153
161
|
end
|
154
162
|
end
|
163
|
+
|
164
|
+
def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
|
165
|
+
collection.send(:compute_cache_key, timestamp_column)
|
166
|
+
end
|
155
167
|
end
|
156
168
|
|
157
169
|
private
|
@@ -180,7 +192,7 @@ module ActiveRecord
|
|
180
192
|
# raw_timestamp_to_cache_version(timestamp)
|
181
193
|
# # => "20181015200215266505"
|
182
194
|
#
|
183
|
-
#
|
195
|
+
# PostgreSQL truncates trailing zeros,
|
184
196
|
# https://github.com/postgres/postgres/commit/3e1beda2cde3495f41290e1ece5d544525810214
|
185
197
|
# to account for this we pad the output with zeros
|
186
198
|
def raw_timestamp_to_cache_version(timestamp)
|
@@ -17,7 +17,7 @@ module ActiveRecord
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def table_name
|
20
|
-
"#{table_name_prefix}#{
|
20
|
+
"#{table_name_prefix}#{internal_metadata_table_name}#{table_name_suffix}"
|
21
21
|
end
|
22
22
|
|
23
23
|
def []=(key, value)
|
@@ -44,6 +44,10 @@ module ActiveRecord
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
47
|
+
|
48
|
+
def drop_table
|
49
|
+
connection.drop_table table_name, if_exists: true
|
50
|
+
end
|
47
51
|
end
|
48
52
|
end
|
49
53
|
end
|
@@ -71,9 +71,8 @@ module ActiveRecord
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def _touch_row(attribute_names, time)
|
74
|
+
@_touch_attr_names << self.class.locking_column if locking_enabled?
|
74
75
|
super
|
75
|
-
ensure
|
76
|
-
clear_attribute_change(self.class.locking_column) if locking_enabled?
|
77
76
|
end
|
78
77
|
|
79
78
|
def _update_row(attribute_names, attempted_action = "update")
|
@@ -88,7 +87,7 @@ module ActiveRecord
|
|
88
87
|
|
89
88
|
affected_rows = self.class._update_record(
|
90
89
|
attributes_with_values(attribute_names),
|
91
|
-
|
90
|
+
@primary_key => id_in_database,
|
92
91
|
locking_column => previous_lock_value
|
93
92
|
)
|
94
93
|
|
@@ -111,7 +110,7 @@ module ActiveRecord
|
|
111
110
|
locking_column = self.class.locking_column
|
112
111
|
|
113
112
|
affected_rows = self.class._delete_record(
|
114
|
-
|
113
|
+
@primary_key => id_in_database,
|
115
114
|
locking_column => read_attribute_before_type_cast(locking_column)
|
116
115
|
)
|
117
116
|
|