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.

Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +97 -0
  3. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  4. data/lib/active_record/associations/collection_association.rb +4 -5
  5. data/lib/active_record/associations/collection_proxy.rb +8 -34
  6. data/lib/active_record/associations/has_many_association.rb +1 -0
  7. data/lib/active_record/associations/has_many_through_association.rb +6 -11
  8. data/lib/active_record/associations/join_dependency/join_association.rb +28 -7
  9. data/lib/active_record/associations/preloader.rb +1 -1
  10. data/lib/active_record/autosave_association.rb +20 -6
  11. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +33 -10
  12. data/lib/active_record/connection_adapters/abstract/database_statements.rb +17 -9
  13. data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -3
  14. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -3
  15. data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
  16. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +18 -8
  17. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +1 -1
  18. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  19. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +6 -24
  20. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -0
  21. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -3
  22. data/lib/active_record/core.rb +2 -1
  23. data/lib/active_record/errors.rb +18 -12
  24. data/lib/active_record/gem_version.rb +2 -2
  25. data/lib/active_record/migration/compatibility.rb +15 -15
  26. data/lib/active_record/persistence.rb +3 -1
  27. data/lib/active_record/querying.rb +1 -2
  28. data/lib/active_record/reflection.rb +10 -14
  29. data/lib/active_record/relation/calculations.rb +16 -12
  30. data/lib/active_record/relation/finder_methods.rb +6 -2
  31. data/lib/active_record/relation/merger.rb +6 -3
  32. data/lib/active_record/relation/predicate_builder.rb +14 -9
  33. data/lib/active_record/relation/query_attribute.rb +5 -3
  34. data/lib/active_record/relation/query_methods.rb +35 -10
  35. data/lib/active_record/scoping/default.rb +2 -2
  36. data/lib/active_record/statement_cache.rb +2 -2
  37. data/lib/active_record/transactions.rb +1 -1
  38. 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
- sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value
24
- [sql.freeze, binds || []]
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 !prepared_statements || (arel.is_a?(String) && preparable.nil?)
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(Thread.current)] = true
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(Thread.current)
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(Thread.current)]
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, **options, if_exists: true)
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, **foreign_key_options)
902
+ remove_foreign_key(table_name, foreign_key_options)
903
903
  end
904
904
 
905
905
  remove_column(table_name, "#{ref_name}_id")
@@ -81,7 +81,9 @@ module ActiveRecord
81
81
  alias :in_use? :owner
82
82
 
83
83
  def self.type_cast_config_to_integer(config)
84
- if config =~ SIMPLE_INT
84
+ if config.is_a?(Integer)
85
+ config
86
+ elsif config =~ SIMPLE_INT
85
87
  config.to_i
86
88
  else
87
89
  config
@@ -804,15 +804,25 @@ module ActiveRecord
804
804
  end
805
805
 
806
806
  def mismatched_foreign_key(message)
807
- parts = message.scan(/`(\w+)`[ $)]/).flatten
808
- MismatchedForeignKey.new(
809
- self,
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
- table: parts[0],
812
- foreign_key: parts[1],
813
- target_table: parts[2],
814
- primary_key: parts[3],
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:
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module DetermineIfPreparableVisitor
6
- attr_reader :preparable
6
+ attr_accessor :preparable
7
7
 
8
8
  def accept(*)
9
9
  @preparable = true
@@ -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
- sqls = [change_column_sql(table_name, column_name, type, options)]
710
- sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default)
711
- sqls << change_column_null_for_alter(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
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
- # Changes the default value of a table column.
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) #:nodoc:
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 picking up last will return all
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
@@ -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
 
@@ -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(adapter = nil, message: nil, table: nil, foreign_key: nil, target_table: nil, primary_key: nil)
121
- @adapter = adapter
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
- msg = <<-EOM.strip_heredoc
124
- Column `#{foreign_key}` on table `#{table}` has a type of `#{column_type(table, foreign_key)}`.
125
- This does not match column `#{primary_key}` on `#{target_table}`, which has type `#{column_type(target_table, primary_key)}`.
126
- To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :integer. (For example `t.integer #{foreign_key}`).
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.strip_heredoc
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.
@@ -9,8 +9,8 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 5
11
11
  MINOR = 2
12
- TINY = 2
13
- PRE = nil
12
+ TINY = 4
13
+ PRE = "4"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -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
- clear_cache!
22
- sql = connection.send(:change_column_sql, table_name, column_name, type, options)
23
- execute "ALTER TABLE #{quote_table_name(table_name)} #{sql}"
24
- change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
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
- return index_name_without_column if index_name_exists?(table_name, index_name_without_column)
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
- cached_columns_hash = connection.schema_cache.columns_hash(table_name)
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 = {