activerecord 5.2.2 → 5.2.4.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 +97 -0
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/collection_association.rb +4 -5
- data/lib/active_record/associations/collection_proxy.rb +8 -34
- data/lib/active_record/associations/has_many_association.rb +1 -0
- data/lib/active_record/associations/has_many_through_association.rb +6 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +28 -7
- data/lib/active_record/associations/preloader.rb +1 -1
- data/lib/active_record/autosave_association.rb +20 -6
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +33 -10
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +17 -9
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +18 -8
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +6 -24
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -3
- data/lib/active_record/core.rb +2 -1
- data/lib/active_record/errors.rb +18 -12
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/migration/compatibility.rb +15 -15
- data/lib/active_record/persistence.rb +3 -1
- data/lib/active_record/querying.rb +1 -2
- data/lib/active_record/reflection.rb +10 -14
- data/lib/active_record/relation/calculations.rb +16 -12
- data/lib/active_record/relation/finder_methods.rb +6 -2
- data/lib/active_record/relation/merger.rb +6 -3
- data/lib/active_record/relation/predicate_builder.rb +14 -9
- data/lib/active_record/relation/query_attribute.rb +5 -3
- data/lib/active_record/relation/query_methods.rb +35 -10
- data/lib/active_record/scoping/default.rb +2 -2
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/transactions.rb +1 -1
- metadata +9 -10
@@ -20,9 +20,22 @@ module ActiveRecord
|
|
20
20
|
raise "Passing bind parameters with an arel AST is forbidden. " \
|
21
21
|
"The values must be stored on the AST directly"
|
22
22
|
end
|
23
|
-
|
24
|
-
|
23
|
+
|
24
|
+
if prepared_statements
|
25
|
+
sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value
|
26
|
+
|
27
|
+
if binds.length > bind_params_length
|
28
|
+
unprepared_statement do
|
29
|
+
sql, binds = to_sql_and_binds(arel_or_sql_string)
|
30
|
+
visitor.preparable = false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
else
|
34
|
+
sql = visitor.accept(arel_or_sql_string.ast, collector).value
|
35
|
+
end
|
36
|
+
[sql.freeze, binds]
|
25
37
|
else
|
38
|
+
visitor.preparable = false if prepared_statements
|
26
39
|
[arel_or_sql_string.dup.freeze, binds]
|
27
40
|
end
|
28
41
|
end
|
@@ -47,13 +60,8 @@ module ActiveRecord
|
|
47
60
|
arel = arel_from_relation(arel)
|
48
61
|
sql, binds = to_sql_and_binds(arel, binds)
|
49
62
|
|
50
|
-
if
|
51
|
-
preparable = false
|
52
|
-
elsif binds.length > bind_params_length
|
53
|
-
sql, binds = unprepared_statement { to_sql_and_binds(arel) }
|
54
|
-
preparable = false
|
55
|
-
else
|
56
|
-
preparable = visitor.preparable
|
63
|
+
if preparable.nil?
|
64
|
+
preparable = prepared_statements ? visitor.preparable : false
|
57
65
|
end
|
58
66
|
|
59
67
|
if prepared_statements && preparable
|
@@ -32,17 +32,17 @@ module ActiveRecord
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def enable_query_cache!
|
35
|
-
@query_cache_enabled[connection_cache_key(
|
35
|
+
@query_cache_enabled[connection_cache_key(current_thread)] = true
|
36
36
|
connection.enable_query_cache! if active_connection?
|
37
37
|
end
|
38
38
|
|
39
39
|
def disable_query_cache!
|
40
|
-
@query_cache_enabled.delete connection_cache_key(
|
40
|
+
@query_cache_enabled.delete connection_cache_key(current_thread)
|
41
41
|
connection.disable_query_cache! if active_connection?
|
42
42
|
end
|
43
43
|
|
44
44
|
def query_cache_enabled
|
45
|
-
@query_cache_enabled[connection_cache_key(
|
45
|
+
@query_cache_enabled[connection_cache_key(current_thread)]
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
@@ -96,6 +96,11 @@ module ActiveRecord
|
|
96
96
|
if @query_cache_enabled && !locked?(arel)
|
97
97
|
arel = arel_from_relation(arel)
|
98
98
|
sql, binds = to_sql_and_binds(arel, binds)
|
99
|
+
|
100
|
+
if preparable.nil?
|
101
|
+
preparable = prepared_statements ? visitor.preparable : false
|
102
|
+
end
|
103
|
+
|
99
104
|
cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) }
|
100
105
|
else
|
101
106
|
super
|
@@ -100,7 +100,7 @@ module ActiveRecord
|
|
100
100
|
def index_exists?(table_name, column_name, options = {})
|
101
101
|
column_names = Array(column_name).map(&:to_s)
|
102
102
|
checks = []
|
103
|
-
checks << lambda { |i| i.columns == column_names }
|
103
|
+
checks << lambda { |i| Array(i.columns) == column_names }
|
104
104
|
checks << lambda { |i| i.unique } if options[:unique]
|
105
105
|
checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
|
106
106
|
|
@@ -305,7 +305,7 @@ module ActiveRecord
|
|
305
305
|
yield td if block_given?
|
306
306
|
|
307
307
|
if options[:force]
|
308
|
-
drop_table(table_name,
|
308
|
+
drop_table(table_name, options.merge(if_exists: true))
|
309
309
|
end
|
310
310
|
|
311
311
|
result = execute schema_creation.accept td
|
@@ -899,7 +899,7 @@ module ActiveRecord
|
|
899
899
|
foreign_key_options = { to_table: reference_name }
|
900
900
|
end
|
901
901
|
foreign_key_options[:column] ||= "#{ref_name}_id"
|
902
|
-
remove_foreign_key(table_name,
|
902
|
+
remove_foreign_key(table_name, foreign_key_options)
|
903
903
|
end
|
904
904
|
|
905
905
|
remove_column(table_name, "#{ref_name}_id")
|
@@ -804,15 +804,25 @@ module ActiveRecord
|
|
804
804
|
end
|
805
805
|
|
806
806
|
def mismatched_foreign_key(message)
|
807
|
-
|
808
|
-
|
809
|
-
|
807
|
+
match = %r/
|
808
|
+
(?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
|
809
|
+
FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
|
810
|
+
REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
|
811
|
+
/xmi.match(message)
|
812
|
+
|
813
|
+
options = {
|
810
814
|
message: message,
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
815
|
+
}
|
816
|
+
|
817
|
+
if match
|
818
|
+
options[:table] = match[:table]
|
819
|
+
options[:foreign_key] = match[:foreign_key]
|
820
|
+
options[:target_table] = match[:target_table]
|
821
|
+
options[:primary_key] = match[:primary_key]
|
822
|
+
options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
|
823
|
+
end
|
824
|
+
|
825
|
+
MismatchedForeignKey.new(options)
|
816
826
|
end
|
817
827
|
|
818
828
|
def integer_to_sql(limit) # :nodoc:
|
@@ -17,6 +17,42 @@ module ActiveRecord
|
|
17
17
|
"VALIDATE CONSTRAINT #{quote_column_name(name)}"
|
18
18
|
end
|
19
19
|
|
20
|
+
def visit_ChangeColumnDefinition(o)
|
21
|
+
column = o.column
|
22
|
+
column.sql_type = type_to_sql(column.type, column.options)
|
23
|
+
quoted_column_name = quote_column_name(o.name)
|
24
|
+
|
25
|
+
change_column_sql = "ALTER COLUMN #{quoted_column_name} TYPE #{column.sql_type}".dup
|
26
|
+
|
27
|
+
options = column_options(column)
|
28
|
+
|
29
|
+
if options[:collation]
|
30
|
+
change_column_sql << " COLLATE \"#{options[:collation]}\""
|
31
|
+
end
|
32
|
+
|
33
|
+
if options[:using]
|
34
|
+
change_column_sql << " USING #{options[:using]}"
|
35
|
+
elsif options[:cast_as]
|
36
|
+
cast_as_type = type_to_sql(options[:cast_as], options)
|
37
|
+
change_column_sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
|
38
|
+
end
|
39
|
+
|
40
|
+
if options.key?(:default)
|
41
|
+
if options[:default].nil?
|
42
|
+
change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
|
43
|
+
else
|
44
|
+
quoted_default = quote_default_expression(options[:default], column)
|
45
|
+
change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
if options.key?(:null)
|
50
|
+
change_column_sql << ", ALTER COLUMN #{quoted_column_name} #{options[:null] ? 'DROP' : 'SET'} NOT NULL"
|
51
|
+
end
|
52
|
+
|
53
|
+
change_column_sql
|
54
|
+
end
|
55
|
+
|
20
56
|
def add_column_options!(sql, options)
|
21
57
|
if options[:collation]
|
22
58
|
sql << " COLLATE \"#{options[:collation]}\""
|
@@ -683,38 +683,20 @@ module ActiveRecord
|
|
683
683
|
end
|
684
684
|
end
|
685
685
|
|
686
|
-
def change_column_sql(table_name, column_name, type, options = {})
|
687
|
-
quoted_column_name = quote_column_name(column_name)
|
688
|
-
sql_type = type_to_sql(type, options)
|
689
|
-
sql = "ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
|
690
|
-
if options[:collation]
|
691
|
-
sql << " COLLATE \"#{options[:collation]}\""
|
692
|
-
end
|
693
|
-
if options[:using]
|
694
|
-
sql << " USING #{options[:using]}"
|
695
|
-
elsif options[:cast_as]
|
696
|
-
cast_as_type = type_to_sql(options[:cast_as], options)
|
697
|
-
sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
|
698
|
-
end
|
699
|
-
|
700
|
-
sql
|
701
|
-
end
|
702
|
-
|
703
686
|
def add_column_for_alter(table_name, column_name, type, options = {})
|
704
687
|
return super unless options.key?(:comment)
|
705
688
|
[super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
|
706
689
|
end
|
707
690
|
|
708
691
|
def change_column_for_alter(table_name, column_name, type, options = {})
|
709
|
-
|
710
|
-
|
711
|
-
sqls
|
692
|
+
td = create_table_definition(table_name)
|
693
|
+
cd = td.new_column_definition(column_name, type, options)
|
694
|
+
sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
|
712
695
|
sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
|
713
696
|
sqls
|
714
697
|
end
|
715
698
|
|
716
|
-
|
717
|
-
def change_column_default_for_alter(table_name, column_name, default_or_changes) # :nodoc:
|
699
|
+
def change_column_default_for_alter(table_name, column_name, default_or_changes)
|
718
700
|
column = column_for(table_name, column_name)
|
719
701
|
return unless column
|
720
702
|
|
@@ -729,8 +711,8 @@ module ActiveRecord
|
|
729
711
|
end
|
730
712
|
end
|
731
713
|
|
732
|
-
def change_column_null_for_alter(table_name, column_name, null, default = nil)
|
733
|
-
"ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
714
|
+
def change_column_null_for_alter(table_name, column_name, null, default = nil)
|
715
|
+
"ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
|
734
716
|
end
|
735
717
|
|
736
718
|
def add_timestamps_for_alter(table_name, options = {})
|
@@ -12,6 +12,10 @@ module ActiveRecord
|
|
12
12
|
quote_column_name(attr)
|
13
13
|
end
|
14
14
|
|
15
|
+
def quote_table_name(name)
|
16
|
+
@quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
|
17
|
+
end
|
18
|
+
|
15
19
|
def quote_column_name(name)
|
16
20
|
@quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}").freeze
|
17
21
|
end
|
@@ -9,7 +9,7 @@ require "active_record/connection_adapters/sqlite3/schema_definitions"
|
|
9
9
|
require "active_record/connection_adapters/sqlite3/schema_dumper"
|
10
10
|
require "active_record/connection_adapters/sqlite3/schema_statements"
|
11
11
|
|
12
|
-
gem "sqlite3", "~> 1.3.6"
|
12
|
+
gem "sqlite3", "~> 1.3", ">= 1.3.6"
|
13
13
|
require "sqlite3"
|
14
14
|
|
15
15
|
module ActiveRecord
|
@@ -525,9 +525,9 @@ module ActiveRecord
|
|
525
525
|
result = exec_query(sql, "SCHEMA").first
|
526
526
|
|
527
527
|
if result
|
528
|
-
# Splitting with left parentheses and
|
528
|
+
# Splitting with left parentheses and discarding the first part will return all
|
529
529
|
# columns separated with comma(,).
|
530
|
-
columns_string = result["sql"].split("(").last
|
530
|
+
columns_string = result["sql"].split("(", 2).last
|
531
531
|
|
532
532
|
columns_string.split(",").each do |column_string|
|
533
533
|
# This regex will match the column name and collation type and will save
|
data/lib/active_record/core.rb
CHANGED
@@ -184,7 +184,8 @@ module ActiveRecord
|
|
184
184
|
end
|
185
185
|
|
186
186
|
def find_by(*args) # :nodoc:
|
187
|
-
return super if scope_attributes? || reflect_on_all_aggregations.any?
|
187
|
+
return super if scope_attributes? || reflect_on_all_aggregations.any? ||
|
188
|
+
columns_hash.key?(inheritance_column) && base_class != self
|
188
189
|
|
189
190
|
hash = args.first
|
190
191
|
|
data/lib/active_record/errors.rb
CHANGED
@@ -117,16 +117,27 @@ module ActiveRecord
|
|
117
117
|
|
118
118
|
# Raised when a foreign key constraint cannot be added because the column type does not match the referenced column type.
|
119
119
|
class MismatchedForeignKey < StatementInvalid
|
120
|
-
def initialize(
|
121
|
-
|
120
|
+
def initialize(
|
121
|
+
adapter = nil,
|
122
|
+
message: nil,
|
123
|
+
sql: nil,
|
124
|
+
binds: nil,
|
125
|
+
table: nil,
|
126
|
+
foreign_key: nil,
|
127
|
+
target_table: nil,
|
128
|
+
primary_key: nil,
|
129
|
+
primary_key_column: nil
|
130
|
+
)
|
122
131
|
if table
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
132
|
+
type = primary_key_column.bigint? ? :bigint : primary_key_column.type
|
133
|
+
msg = <<-EOM.squish
|
134
|
+
Column `#{foreign_key}` on table `#{table}` does not match column `#{primary_key}` on `#{target_table}`,
|
135
|
+
which has type `#{primary_key_column.sql_type}`.
|
136
|
+
To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :#{type}.
|
137
|
+
(For example `t.#{type} :#{foreign_key}`).
|
127
138
|
EOM
|
128
139
|
else
|
129
|
-
msg = <<-EOM.
|
140
|
+
msg = <<-EOM.squish
|
130
141
|
There is a mismatch between the foreign key and primary key column types.
|
131
142
|
Verify that the foreign key column type and the primary key of the associated table match types.
|
132
143
|
EOM
|
@@ -136,11 +147,6 @@ module ActiveRecord
|
|
136
147
|
end
|
137
148
|
super(msg)
|
138
149
|
end
|
139
|
-
|
140
|
-
private
|
141
|
-
def column_type(table, column)
|
142
|
-
@adapter.columns(table).detect { |c| c.name == column }.sql_type
|
143
|
-
end
|
144
150
|
end
|
145
151
|
|
146
152
|
# Raised when a record cannot be inserted or updated because it would violate a not null constraint.
|
@@ -17,20 +17,18 @@ module ActiveRecord
|
|
17
17
|
|
18
18
|
class V5_1 < V5_2
|
19
19
|
def change_column(table_name, column_name, type, options = {})
|
20
|
-
if adapter_name == "PostgreSQL"
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
26
|
-
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
|
20
|
+
if connection.adapter_name == "PostgreSQL"
|
21
|
+
super(table_name, column_name, type, options.except(:default, :null, :comment))
|
22
|
+
connection.change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
|
23
|
+
connection.change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
24
|
+
connection.change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
|
27
25
|
else
|
28
26
|
super
|
29
27
|
end
|
30
28
|
end
|
31
29
|
|
32
30
|
def create_table(table_name, options = {})
|
33
|
-
if adapter_name == "Mysql2"
|
31
|
+
if connection.adapter_name == "Mysql2"
|
34
32
|
super(table_name, options: "ENGINE=InnoDB", **options)
|
35
33
|
else
|
36
34
|
super
|
@@ -52,13 +50,13 @@ module ActiveRecord
|
|
52
50
|
end
|
53
51
|
|
54
52
|
def create_table(table_name, options = {})
|
55
|
-
if adapter_name == "PostgreSQL"
|
53
|
+
if connection.adapter_name == "PostgreSQL"
|
56
54
|
if options[:id] == :uuid && !options.key?(:default)
|
57
55
|
options[:default] = "uuid_generate_v4()"
|
58
56
|
end
|
59
57
|
end
|
60
58
|
|
61
|
-
unless adapter_name == "Mysql2" && options[:id] == :bigint
|
59
|
+
unless connection.adapter_name == "Mysql2" && options[:id] == :bigint
|
62
60
|
if [:integer, :bigint].include?(options[:id]) && !options.key?(:default)
|
63
61
|
options[:default] = nil
|
64
62
|
end
|
@@ -175,7 +173,7 @@ module ActiveRecord
|
|
175
173
|
if options[:name].present?
|
176
174
|
options[:name].to_s
|
177
175
|
else
|
178
|
-
index_name(table_name, column: column_names)
|
176
|
+
connection.index_name(table_name, column: column_names)
|
179
177
|
end
|
180
178
|
super
|
181
179
|
end
|
@@ -195,15 +193,17 @@ module ActiveRecord
|
|
195
193
|
end
|
196
194
|
|
197
195
|
def index_name_for_remove(table_name, options = {})
|
198
|
-
index_name = index_name(table_name, options)
|
196
|
+
index_name = connection.index_name(table_name, options)
|
199
197
|
|
200
|
-
unless index_name_exists?(table_name, index_name)
|
198
|
+
unless connection.index_name_exists?(table_name, index_name)
|
201
199
|
if options.is_a?(Hash) && options.has_key?(:name)
|
202
200
|
options_without_column = options.dup
|
203
201
|
options_without_column.delete :column
|
204
|
-
index_name_without_column = index_name(table_name, options_without_column)
|
202
|
+
index_name_without_column = connection.index_name(table_name, options_without_column)
|
205
203
|
|
206
|
-
|
204
|
+
if connection.index_name_exists?(table_name, index_name_without_column)
|
205
|
+
return index_name_without_column
|
206
|
+
end
|
207
207
|
end
|
208
208
|
|
209
209
|
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
|
@@ -97,11 +97,13 @@ module ActiveRecord
|
|
97
97
|
# When running callbacks is not needed for each record update,
|
98
98
|
# it is preferred to use {update_all}[rdoc-ref:Relation#update_all]
|
99
99
|
# for updating all records in a single query.
|
100
|
-
def update(id, attributes)
|
100
|
+
def update(id = :all, attributes)
|
101
101
|
if id.is_a?(Array)
|
102
102
|
id.map { |one_id| find(one_id) }.each_with_index { |object, idx|
|
103
103
|
object.update(attributes[idx])
|
104
104
|
}
|
105
|
+
elsif id == :all
|
106
|
+
all.each { |record| record.update(attributes) }
|
105
107
|
else
|
106
108
|
if ActiveRecord::Base === id
|
107
109
|
raise ArgumentError,
|
@@ -40,8 +40,7 @@ module ActiveRecord
|
|
40
40
|
def find_by_sql(sql, binds = [], preparable: nil, &block)
|
41
41
|
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
|
42
42
|
column_types = result_set.column_types.dup
|
43
|
-
|
44
|
-
cached_columns_hash.each_key { |k| column_types.delete k }
|
43
|
+
attribute_types.each_key { |k| column_types.delete k }
|
45
44
|
message_bus = ActiveSupport::Notifications.instrumenter
|
46
45
|
|
47
46
|
payload = {
|