activerecord 6.0.3.3 → 6.0.4
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 +138 -0
- data/README.rdoc +1 -1
- data/lib/active_record/associations/association.rb +2 -3
- data/lib/active_record/associations/association_scope.rb +7 -1
- data/lib/active_record/associations/collection_association.rb +7 -0
- data/lib/active_record/associations/collection_proxy.rb +1 -0
- data/lib/active_record/associations/join_dependency.rb +14 -6
- data/lib/active_record/associations/join_dependency/join_association.rb +7 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
- data/lib/active_record/associations/preloader.rb +6 -2
- data/lib/active_record/associations/preloader/association.rb +41 -23
- data/lib/active_record/associations/preloader/through_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/autosave_association.rb +10 -10
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +6 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +6 -1
- data/lib/active_record/connection_adapters/abstract_adapter.rb +6 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +9 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -4
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -5
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -1
- data/lib/active_record/core.rb +5 -6
- data/lib/active_record/enum.rb +13 -6
- data/lib/active_record/fixture_set/render_context.rb +1 -1
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/insert_all.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +9 -0
- data/lib/active_record/model_schema.rb +29 -0
- data/lib/active_record/reflection.rb +11 -13
- data/lib/active_record/relation.rb +6 -3
- data/lib/active_record/relation/calculations.rb +1 -1
- data/lib/active_record/relation/delegation.rb +2 -1
- data/lib/active_record/relation/finder_methods.rb +1 -1
- data/lib/active_record/relation/merger.rb +7 -2
- data/lib/active_record/relation/query_methods.rb +23 -11
- data/lib/active_record/scoping/named.rb +5 -0
- data/lib/active_record/test_fixtures.rb +19 -1
- data/lib/active_record/type/time.rb +10 -0
- data/lib/active_record/validations/associated.rb +1 -1
- metadata +9 -9
@@ -29,7 +29,7 @@ module ActiveRecord
|
|
29
29
|
# == Callbacks
|
30
30
|
#
|
31
31
|
# Association with autosave option defines several callbacks on your
|
32
|
-
# model (before_save, after_create, after_update). Please note that
|
32
|
+
# model (around_save, before_save, after_create, after_update). Please note that
|
33
33
|
# callbacks are executed in the order they were defined in
|
34
34
|
# model. You should avoid modifying the association content, before
|
35
35
|
# autosave callbacks are executed. Placing your callbacks after
|
@@ -180,8 +180,7 @@ module ActiveRecord
|
|
180
180
|
save_method = :"autosave_associated_records_for_#{reflection.name}"
|
181
181
|
|
182
182
|
if reflection.collection?
|
183
|
-
|
184
|
-
after_save :after_save_collection_association
|
183
|
+
around_save :around_save_collection_association
|
185
184
|
|
186
185
|
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
|
187
186
|
# Doesn't use after_save as that would save associations added in after_create/after_update twice
|
@@ -358,14 +357,15 @@ module ActiveRecord
|
|
358
357
|
end
|
359
358
|
end
|
360
359
|
|
361
|
-
# Is used as
|
360
|
+
# Is used as an around_save callback to check while saving a collection
|
362
361
|
# association whether or not the parent was a new record before saving.
|
363
|
-
def
|
364
|
-
@new_record_before_save ||=
|
365
|
-
|
362
|
+
def around_save_collection_association
|
363
|
+
previously_new_record_before_save = (@new_record_before_save ||= false)
|
364
|
+
@new_record_before_save = !previously_new_record_before_save && new_record?
|
366
365
|
|
367
|
-
|
368
|
-
|
366
|
+
yield
|
367
|
+
ensure
|
368
|
+
@new_record_before_save = previously_new_record_before_save
|
369
369
|
end
|
370
370
|
|
371
371
|
# Saves any new associated records, or all loaded autosave associations if
|
@@ -444,7 +444,7 @@ module ActiveRecord
|
|
444
444
|
unless reflection.through_reflection
|
445
445
|
record[reflection.foreign_key] = key
|
446
446
|
if inverse_reflection = reflection.inverse_of
|
447
|
-
record.association(inverse_reflection.name).
|
447
|
+
record.association(inverse_reflection.name).inversed_from(self)
|
448
448
|
end
|
449
449
|
end
|
450
450
|
|
@@ -1035,6 +1035,12 @@ module ActiveRecord
|
|
1035
1035
|
# In some cases you may want to prevent writes to the database
|
1036
1036
|
# even if you are on a database that can write. `while_preventing_writes`
|
1037
1037
|
# will prevent writes to the database for the duration of the block.
|
1038
|
+
#
|
1039
|
+
# This method does not provide the same protection as a readonly
|
1040
|
+
# user and is meant to be a safeguard against accidental writes.
|
1041
|
+
#
|
1042
|
+
# See `READ_QUERY` for the queries that are blocked by this
|
1043
|
+
# method.
|
1038
1044
|
def while_preventing_writes(enabled = true)
|
1039
1045
|
original, self.prevent_writes = self.prevent_writes, enabled
|
1040
1046
|
yield
|
@@ -63,6 +63,10 @@ module ActiveRecord
|
|
63
63
|
end
|
64
64
|
CODE
|
65
65
|
end
|
66
|
+
|
67
|
+
def aliased_types(name, fallback)
|
68
|
+
"timestamp" == name ? :datetime : fallback
|
69
|
+
end
|
66
70
|
end
|
67
71
|
|
68
72
|
AddColumnDefinition = Struct.new(:column) # :nodoc:
|
@@ -105,8 +109,9 @@ module ActiveRecord
|
|
105
109
|
!ActiveRecord::SchemaDumper.fk_ignore_pattern.match?(name) if name
|
106
110
|
end
|
107
111
|
|
108
|
-
def defined_for?(to_table: nil, **options)
|
112
|
+
def defined_for?(to_table: nil, validate: nil, **options)
|
109
113
|
(to_table.nil? || to_table.to_s == self.to_table) &&
|
114
|
+
(validate.nil? || validate == options.fetch(:validate, validate)) &&
|
110
115
|
options.all? { |k, v| self.options[k].to_s == v.to_s }
|
111
116
|
end
|
112
117
|
|
@@ -103,7 +103,11 @@ module ActiveRecord
|
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
106
|
+
DEFAULT_READ_QUERY = [:begin, :commit, :explain, :release, :rollback, :savepoint, :select, :with] # :nodoc:
|
107
|
+
private_constant :DEFAULT_READ_QUERY
|
108
|
+
|
106
109
|
def self.build_read_query_regexp(*parts) # :nodoc:
|
110
|
+
parts += DEFAULT_READ_QUERY
|
107
111
|
parts = parts.map { |part| /#{part}/i }
|
108
112
|
/\A(?:[\(\s]|#{COMMENT_REGEX})*#{Regexp.union(*parts)}/
|
109
113
|
end
|
@@ -168,6 +172,8 @@ module ActiveRecord
|
|
168
172
|
spec_name = conn.pool.spec.name
|
169
173
|
name = "#{spec_name}::SchemaMigration"
|
170
174
|
|
175
|
+
return ActiveRecord::SchemaMigration if spec_name == "primary"
|
176
|
+
|
171
177
|
Class.new(ActiveRecord::SchemaMigration) do
|
172
178
|
define_singleton_method(:name) { name }
|
173
179
|
define_singleton_method(:to_s) { name }
|
@@ -675,9 +675,10 @@ module ActiveRecord
|
|
675
675
|
end
|
676
676
|
|
677
677
|
def add_index_for_alter(table_name, column_name, options = {})
|
678
|
-
index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, **options)
|
678
|
+
index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, **options)
|
679
679
|
index_algorithm[0, 0] = ", " if index_algorithm.present?
|
680
|
-
"ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
|
680
|
+
sql = +"ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
|
681
|
+
add_sql_comment!(sql, comment)
|
681
682
|
end
|
682
683
|
|
683
684
|
def remove_index_for_alter(table_name, options = {})
|
@@ -686,7 +687,11 @@ module ActiveRecord
|
|
686
687
|
end
|
687
688
|
|
688
689
|
def supports_rename_index?
|
689
|
-
mariadb?
|
690
|
+
if mariadb?
|
691
|
+
database_version >= "10.5.2"
|
692
|
+
else
|
693
|
+
database_version >= "5.7.6"
|
694
|
+
end
|
690
695
|
end
|
691
696
|
|
692
697
|
def configure_connection
|
@@ -739,7 +744,7 @@ module ActiveRecord
|
|
739
744
|
end.compact.join(", ")
|
740
745
|
|
741
746
|
# ...and send them all in one query
|
742
|
-
execute
|
747
|
+
execute("SET #{encoding} #{sql_mode_assignment} #{variable_assignments}", "SCHEMA")
|
743
748
|
end
|
744
749
|
|
745
750
|
def column_definitions(table_name) # :nodoc:
|
@@ -20,7 +20,7 @@ module ActiveRecord
|
|
20
20
|
end
|
21
21
|
|
22
22
|
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
23
|
-
:
|
23
|
+
:desc, :describe, :set, :show, :use
|
24
24
|
) # :nodoc:
|
25
25
|
private_constant :READ_QUERY
|
26
26
|
|
@@ -203,10 +203,14 @@ module ActiveRecord
|
|
203
203
|
def data_source_sql(name = nil, type: nil)
|
204
204
|
scope = quoted_scope(name, type: type)
|
205
205
|
|
206
|
-
sql = +"SELECT table_name FROM information_schema.tables"
|
207
|
-
sql << " WHERE table_schema = #{scope[:schema]}"
|
208
|
-
|
209
|
-
|
206
|
+
sql = +"SELECT table_name FROM (SELECT * FROM information_schema.tables "
|
207
|
+
sql << " WHERE table_schema = #{scope[:schema]}) _subquery"
|
208
|
+
if scope[:type] || scope[:name]
|
209
|
+
conditions = []
|
210
|
+
conditions << "_subquery.table_type = #{scope[:type]}" if scope[:type]
|
211
|
+
conditions << "_subquery.table_name = #{scope[:name]}" if scope[:name]
|
212
|
+
sql << " WHERE #{conditions.join(" AND ")}"
|
213
|
+
end
|
210
214
|
sql
|
211
215
|
end
|
212
216
|
|
@@ -68,7 +68,7 @@ module ActiveRecord
|
|
68
68
|
end
|
69
69
|
|
70
70
|
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
71
|
-
:
|
71
|
+
:close, :declare, :fetch, :move, :set, :show
|
72
72
|
) # :nodoc:
|
73
73
|
private_constant :READ_QUERY
|
74
74
|
|
@@ -16,6 +16,14 @@ module ActiveRecord
|
|
16
16
|
super
|
17
17
|
end
|
18
18
|
end
|
19
|
+
|
20
|
+
def type_cast_for_schema(value)
|
21
|
+
case value
|
22
|
+
when ::Float::INFINITY then "::Float::INFINITY"
|
23
|
+
when -::Float::INFINITY then "-::Float::INFINITY"
|
24
|
+
else super
|
25
|
+
end
|
26
|
+
end
|
19
27
|
end
|
20
28
|
end
|
21
29
|
end
|
@@ -26,9 +26,9 @@ module ActiveRecord
|
|
26
26
|
|
27
27
|
value = value.sub(/^\((.+)\)$/, '-\1') # (4)
|
28
28
|
case value
|
29
|
-
when /^-?\D
|
29
|
+
when /^-?\D*+[\d,]+\.\d{2}$/ # (1)
|
30
30
|
value.gsub!(/[^-\d.]/, "")
|
31
|
-
when /^-?\D
|
31
|
+
when /^-?\D*+[\d.]+,\d{2}$/ # (2)
|
32
32
|
value.gsub!(/[^-\d,]/, "").sub!(/,/, ".")
|
33
33
|
end
|
34
34
|
|
@@ -67,15 +67,34 @@ module ActiveRecord
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def extract_bounds(value)
|
70
|
-
from, to = value[1..-2].split(",")
|
70
|
+
from, to = value[1..-2].split(",", 2)
|
71
71
|
{
|
72
|
-
from: (
|
73
|
-
to: (
|
74
|
-
exclude_start: (
|
75
|
-
exclude_end: (
|
72
|
+
from: (from == "" || from == "-infinity") ? infinity(negative: true) : unquote(from),
|
73
|
+
to: (to == "" || to == "infinity") ? infinity : unquote(to),
|
74
|
+
exclude_start: value.start_with?("("),
|
75
|
+
exclude_end: value.end_with?(")")
|
76
76
|
}
|
77
77
|
end
|
78
78
|
|
79
|
+
# When formatting the bound values of range types, PostgreSQL quotes
|
80
|
+
# the bound value using double-quotes in certain conditions. Within
|
81
|
+
# a double-quoted string, literal " and \ characters are themselves
|
82
|
+
# escaped. In input, PostgreSQL accepts multiple escape styles for "
|
83
|
+
# (either \" or "") but in output always uses "".
|
84
|
+
# See:
|
85
|
+
# * https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-IO
|
86
|
+
# * https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-IO-SYNTAX
|
87
|
+
def unquote(value)
|
88
|
+
if value.start_with?('"') && value.end_with?('"')
|
89
|
+
unquoted_value = value[1..-2]
|
90
|
+
unquoted_value.gsub!('""', '"')
|
91
|
+
unquoted_value.gsub!('\\\\', '\\')
|
92
|
+
unquoted_value
|
93
|
+
else
|
94
|
+
value
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
79
98
|
def infinity(negative: false)
|
80
99
|
if subtype.respond_to?(:infinity)
|
81
100
|
subtype.infinity(negative: negative)
|
@@ -5,7 +5,7 @@ module ActiveRecord
|
|
5
5
|
module SQLite3
|
6
6
|
module DatabaseStatements
|
7
7
|
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
8
|
-
:
|
8
|
+
:pragma
|
9
9
|
) # :nodoc:
|
10
10
|
private_constant :READ_QUERY
|
11
11
|
|
@@ -61,7 +61,7 @@ module ActiveRecord
|
|
61
61
|
|
62
62
|
def remove_foreign_key(from_table, to_table = nil, **options)
|
63
63
|
to_table ||= options[:to_table]
|
64
|
-
options = options.except(:name, :to_table)
|
64
|
+
options = options.except(:name, :to_table, :validate)
|
65
65
|
foreign_keys = foreign_keys(from_table)
|
66
66
|
|
67
67
|
fkey = foreign_keys.detect do |fk|
|
@@ -283,13 +283,14 @@ module ActiveRecord
|
|
283
283
|
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
284
284
|
alter_table(table_name) do |definition|
|
285
285
|
definition[column_name].instance_eval do
|
286
|
-
self.type = type
|
286
|
+
self.type = aliased_types(type.to_s, type)
|
287
287
|
self.limit = options[:limit] if options.include?(:limit)
|
288
288
|
self.default = options[:default] if options.include?(:default)
|
289
289
|
self.null = options[:null] if options.include?(:null)
|
290
290
|
self.precision = options[:precision] if options.include?(:precision)
|
291
291
|
self.scale = options[:scale] if options.include?(:scale)
|
292
292
|
self.collation = options[:collation] if options.include?(:collation)
|
293
|
+
self.options.merge!(options)
|
293
294
|
end
|
294
295
|
end
|
295
296
|
end
|
data/lib/active_record/core.rb
CHANGED
@@ -285,18 +285,17 @@ module ActiveRecord
|
|
285
285
|
false
|
286
286
|
end
|
287
287
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
end
|
288
|
+
def cached_find_by_statement(key, &block) # :nodoc:
|
289
|
+
cache = @find_by_statement_cache[connection.prepared_statements]
|
290
|
+
cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
|
291
|
+
end
|
293
292
|
|
293
|
+
private
|
294
294
|
def relation
|
295
295
|
relation = Relation.create(self)
|
296
296
|
|
297
297
|
if finder_needs_type_condition? && !ignore_default_scope?
|
298
298
|
relation.where!(type_condition)
|
299
|
-
relation.create_with!(inheritance_column.to_s => sti_name)
|
300
299
|
else
|
301
300
|
relation
|
302
301
|
end
|
data/lib/active_record/enum.rb
CHANGED
@@ -173,6 +173,7 @@ module ActiveRecord
|
|
173
173
|
|
174
174
|
_enum_methods_module.module_eval do
|
175
175
|
pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
|
176
|
+
value_method_names = []
|
176
177
|
pairs.each do |label, value|
|
177
178
|
if enum_prefix == true
|
178
179
|
prefix = "#{name}_"
|
@@ -186,6 +187,7 @@ module ActiveRecord
|
|
186
187
|
end
|
187
188
|
|
188
189
|
value_method_name = "#{prefix}#{label}#{suffix}"
|
190
|
+
value_method_names << value_method_name
|
189
191
|
enum_values[label] = value
|
190
192
|
label = label.to_s
|
191
193
|
|
@@ -200,8 +202,6 @@ module ActiveRecord
|
|
200
202
|
# scope :active, -> { where(status: 0) }
|
201
203
|
# scope :not_active, -> { where.not(status: 0) }
|
202
204
|
if enum_scopes != false
|
203
|
-
klass.send(:detect_negative_condition!, value_method_name)
|
204
|
-
|
205
205
|
klass.send(:detect_enum_conflict!, name, value_method_name, true)
|
206
206
|
klass.scope value_method_name, -> { where(attr => value) }
|
207
207
|
|
@@ -209,6 +209,7 @@ module ActiveRecord
|
|
209
209
|
klass.scope "not_#{value_method_name}", -> { where.not(attr => value) }
|
210
210
|
end
|
211
211
|
end
|
212
|
+
klass.send(:detect_negative_enum_conditions!, value_method_names) if enum_scopes != false
|
212
213
|
end
|
213
214
|
enum_values.freeze
|
214
215
|
end
|
@@ -264,10 +265,16 @@ module ActiveRecord
|
|
264
265
|
}
|
265
266
|
end
|
266
267
|
|
267
|
-
def
|
268
|
-
|
269
|
-
|
270
|
-
|
268
|
+
def detect_negative_enum_conditions!(method_names)
|
269
|
+
return unless logger
|
270
|
+
|
271
|
+
method_names.select { |m| m.start_with?("not_") }.each do |potential_not|
|
272
|
+
inverted_form = potential_not.sub("not_", "")
|
273
|
+
if method_names.include?(inverted_form)
|
274
|
+
logger.warn "Enum element '#{potential_not}' in #{self.name} uses the prefix 'not_'." \
|
275
|
+
" This has caused a conflict with auto generated negative scopes." \
|
276
|
+
" Avoid using enum elements starting with 'not' where the positive form is also an element."
|
277
|
+
end
|
271
278
|
end
|
272
279
|
end
|
273
280
|
end
|
@@ -60,6 +60,15 @@ module ActiveRecord
|
|
60
60
|
self.class.locking_enabled?
|
61
61
|
end
|
62
62
|
|
63
|
+
def increment!(*, **) #:nodoc:
|
64
|
+
super.tap do
|
65
|
+
if locking_enabled?
|
66
|
+
self[self.class.locking_column] += 1
|
67
|
+
clear_attribute_change(self.class.locking_column)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
63
72
|
private
|
64
73
|
def _create_record(attribute_names = self.attribute_names)
|
65
74
|
if locking_enabled?
|
@@ -287,6 +287,35 @@ module ActiveRecord
|
|
287
287
|
|
288
288
|
# Sets the columns names the model should ignore. Ignored columns won't have attribute
|
289
289
|
# accessors defined, and won't be referenced in SQL queries.
|
290
|
+
#
|
291
|
+
# A common usage pattern for this method is to ensure all references to an attribute
|
292
|
+
# have been removed and deployed, before a migration to drop the column from the database
|
293
|
+
# has been deployed and run. Using this two step approach to dropping columns ensures there
|
294
|
+
# is no code that raises errors due to having a cached schema in memory at the time the
|
295
|
+
# schema migration is run.
|
296
|
+
#
|
297
|
+
# For example, given a model where you want to drop the "category" attribute, first mark it
|
298
|
+
# as ignored:
|
299
|
+
#
|
300
|
+
# class Project < ActiveRecord::Base
|
301
|
+
# # schema:
|
302
|
+
# # id :bigint
|
303
|
+
# # name :string, limit: 255
|
304
|
+
# # category :string, limit: 255
|
305
|
+
#
|
306
|
+
# self.ignored_columns = [:category]
|
307
|
+
# end
|
308
|
+
#
|
309
|
+
# The schema still contains `category`, but now the model omits it, so any meta-driven code or
|
310
|
+
# schema caching will not attempt to use the column:
|
311
|
+
#
|
312
|
+
# Project.columns_hash["category"] => nil
|
313
|
+
#
|
314
|
+
# You will get an error if accessing that attribute directly, so ensure all usages of the
|
315
|
+
# column are removed (automated tests can help you find any usages).
|
316
|
+
#
|
317
|
+
# user = Project.create!(name: "First Project")
|
318
|
+
# user.category # => raises NoMethodError
|
290
319
|
def ignored_columns=(columns)
|
291
320
|
@ignored_columns = columns.map(&:to_s)
|
292
321
|
end
|