activerecord 5.2.2 → 5.2.4.5

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +112 -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/oid/money.rb +2 -2
  19. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  20. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +6 -24
  21. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -0
  22. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -3
  23. data/lib/active_record/core.rb +2 -1
  24. data/lib/active_record/errors.rb +18 -12
  25. data/lib/active_record/gem_version.rb +2 -2
  26. data/lib/active_record/migration/compatibility.rb +15 -15
  27. data/lib/active_record/persistence.rb +3 -1
  28. data/lib/active_record/querying.rb +1 -2
  29. data/lib/active_record/reflection.rb +10 -14
  30. data/lib/active_record/relation/calculations.rb +16 -12
  31. data/lib/active_record/relation/finder_methods.rb +6 -2
  32. data/lib/active_record/relation/merger.rb +6 -3
  33. data/lib/active_record/relation/predicate_builder.rb +14 -9
  34. data/lib/active_record/relation/query_attribute.rb +5 -3
  35. data/lib/active_record/relation/query_methods.rb +35 -10
  36. data/lib/active_record/scoping/default.rb +2 -2
  37. data/lib/active_record/statement_cache.rb +2 -2
  38. data/lib/active_record/transactions.rb +1 -1
  39. metadata +9 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38bf52e024134bc78c1daa8bf44ee21d685adcc62ddc9ded7feae3111c08a8e7
4
- data.tar.gz: 89f9dbe14887a77a660722bb1a09ffbde6b11a4ab600b9a724458ac369548f09
3
+ metadata.gz: f7fc834af19132b6ad9d70b3809c7a19ae4e83c133e8332681ccbf6d7fd1f48d
4
+ data.tar.gz: 6257ed9838f4a3cae375ad8834f849a3178f8c571b09b525b60118f350018920
5
5
  SHA512:
6
- metadata.gz: f0a17e48f08ba5082caa787efabf99f58bf0738de05a53f0fc4979ed27285f42f4f28e9406089f74845573f43d7a78718d5323bce8826a48d7bb673a77717055
7
- data.tar.gz: '03572796340d73d6e9b1cfa04c1d10d84a528615e76d9e0f700af85086d4dad11c58e21ca96b8c44bddd303dd090a25e68489391046e6cd0b8cc7565d2393da8'
6
+ metadata.gz: ac8563d07cd073721ec55daaefa751752efb9d10657cb8900d0416bf072be299001452b7d59f133a09810abf4bd080d8b9a89250e1741c00eff7a65d2f275ec3
7
+ data.tar.gz: '0868ed81dbdac5476819f3cf7002778b42cbe1683ee73168f48c90743d684616d10b303e2fc222670613d59493051e3eca87beaf09ad28652d81b377a4f81fbc'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,115 @@
1
+ ## Rails 5.2.4.5 (February 10, 2021) ##
2
+
3
+ * Fix possible DoS vector in PostgreSQL money type
4
+
5
+ Carefully crafted input can cause a DoS via the regular expressions used
6
+ for validating the money format in the PostgreSQL adapter. This patch
7
+ fixes the regexp.
8
+
9
+ Thanks to @dee-see from Hackerone for this patch!
10
+
11
+ [CVE-2021-22880]
12
+
13
+ *Aaron Patterson*
14
+
15
+
16
+ ## Rails 5.2.4.4 (September 09, 2020) ##
17
+
18
+ * No changes.
19
+
20
+
21
+ ## Rails 5.2.4.3 (May 18, 2020) ##
22
+
23
+ * No changes.
24
+
25
+
26
+ ## Rails 5.2.4.1 (December 18, 2019) ##
27
+
28
+ * No changes.
29
+
30
+
31
+ ## Rails 5.2.4 (November 27, 2019) ##
32
+
33
+ * Fix circular `autosave: true` causes invalid records to be saved.
34
+
35
+ Prior to the fix, when there was a circular series of `autosave: true`
36
+ associations, the callback for a `has_many` association was run while
37
+ another instance of the same callback on the same association hadn't
38
+ finished running. When control returned to the first instance of the
39
+ callback, the instance variable had changed, and subsequent associated
40
+ records weren't saved correctly. Specifically, the ID field for the
41
+ `belongs_to` corresponding to the `has_many` was `nil`.
42
+
43
+ Fixes #28080.
44
+
45
+ *Larry Reid*
46
+
47
+ * PostgreSQL: Fix GROUP BY with ORDER BY virtual count attribute.
48
+
49
+ Fixes #36022.
50
+
51
+ *Ryuta Kamizono*
52
+
53
+ * Fix sqlite3 collation parsing when using decimal columns.
54
+
55
+ *Martin R. Schuster*
56
+
57
+ * Make ActiveRecord `ConnectionPool.connections` method thread-safe.
58
+
59
+ Fixes #36465.
60
+
61
+ *Jeff Doering*
62
+
63
+ * Assign all attributes before calling `build` to ensure the child record is visible in
64
+ `before_add` and `after_add` callbacks for `has_many :through` associations.
65
+
66
+ Fixes #33249.
67
+
68
+ *Ryan H. Kerr*
69
+
70
+
71
+ ## Rails 5.2.3 (March 27, 2019) ##
72
+
73
+ * Fix different `count` calculation when using `size` with manual `select` with DISTINCT.
74
+
75
+ Fixes #35214.
76
+
77
+ *Juani Villarejo*
78
+
79
+ * Fix prepared statements caching to be enabled even when query caching is enabled.
80
+
81
+ *Ryuta Kamizono*
82
+
83
+ * Don't allow `where` with invalid value matches to nil values.
84
+
85
+ Fixes #33624.
86
+
87
+ *Ryuta Kamizono*
88
+
89
+ * Restore an ability that class level `update` without giving ids.
90
+
91
+ Fixes #34743.
92
+
93
+ *Ryuta Kamizono*
94
+
95
+ * Fix join table column quoting with SQLite.
96
+
97
+ *Gannon McGibbon*
98
+
99
+ * Ensure that `delete_all` on collection proxy returns affected count.
100
+
101
+ *Ryuta Kamizono*
102
+
103
+ * Reset scope after delete on collection association to clear stale offsets of removed records.
104
+
105
+ *Gannon McGibbon*
106
+
107
+
108
+ ## Rails 5.2.2.1 (March 11, 2019) ##
109
+
110
+ * No changes.
111
+
112
+
1
113
  ## Rails 5.2.2 (December 04, 2018) ##
2
114
 
3
115
  * Do not ignore the scoping with query methods in the scope block.
@@ -20,10 +20,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
20
20
  }
21
21
  end
22
22
 
23
- def self.define_extensions(model, name)
23
+ def self.define_extensions(model, name, &block)
24
24
  if block_given?
25
25
  extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
26
- extension = Module.new(&Proc.new)
26
+ extension = Module.new(&block)
27
27
  model.parent.const_set(extension_module_name, extension)
28
28
  end
29
29
  end
@@ -109,9 +109,8 @@ module ActiveRecord
109
109
  end
110
110
  end
111
111
 
112
- # Add +records+ to this association. Returns +self+ so method calls may
113
- # be chained. Since << flattens its argument list and inserts each record,
114
- # +push+ and +concat+ behave identically.
112
+ # Add +records+ to this association. Since +<<+ flattens its argument list
113
+ # and inserts each record, +push+ and +concat+ behave identically.
115
114
  def concat(*records)
116
115
  records = records.flatten
117
116
  if owner.new_record?
@@ -362,7 +361,6 @@ module ActiveRecord
362
361
  add_to_target(record) do
363
362
  result = insert_record(record, true, raise) {
364
363
  @_was_loaded = loaded?
365
- @association_ids = nil
366
364
  }
367
365
  end
368
366
  raise ActiveRecord::Rollback unless result
@@ -399,6 +397,7 @@ module ActiveRecord
399
397
 
400
398
  delete_records(existing_records, method) if existing_records.any?
401
399
  records.each { |record| target.delete(record) }
400
+ @association_ids = nil
402
401
 
403
402
  records.each { |record| callback(:after_remove, record) }
404
403
  end
@@ -439,7 +438,6 @@ module ActiveRecord
439
438
  unless owner.new_record?
440
439
  result &&= insert_record(record, true, raise) {
441
440
  @_was_loaded = loaded?
442
- @association_ids = nil
443
441
  }
444
442
  end
445
443
  end
@@ -462,6 +460,7 @@ module ActiveRecord
462
460
  if index
463
461
  target[index] = record
464
462
  elsif @_was_loaded || !loaded?
463
+ @association_ids = nil
465
464
  target << record
466
465
  end
467
466
 
@@ -366,34 +366,6 @@ module ActiveRecord
366
366
  @association.create!(attributes, &block)
367
367
  end
368
368
 
369
- # Add one or more records to the collection by setting their foreign keys
370
- # to the association's primary key. Since #<< flattens its argument list and
371
- # inserts each record, +push+ and #concat behave identically. Returns +self+
372
- # so method calls may be chained.
373
- #
374
- # class Person < ActiveRecord::Base
375
- # has_many :pets
376
- # end
377
- #
378
- # person.pets.size # => 0
379
- # person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
380
- # person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
381
- # person.pets.size # => 3
382
- #
383
- # person.id # => 1
384
- # person.pets
385
- # # => [
386
- # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
387
- # # #<Pet id: 2, name: "Spook", person_id: 1>,
388
- # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
389
- # # ]
390
- #
391
- # person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
392
- # person.pets.size # => 5
393
- def concat(*records)
394
- @association.concat(*records)
395
- end
396
-
397
369
  # Replaces this collection with +other_array+. This will perform a diff
398
370
  # and delete/add only records that have changed.
399
371
  #
@@ -500,7 +472,7 @@ module ActiveRecord
500
472
  # Pet.find(1, 2, 3)
501
473
  # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
502
474
  def delete_all(dependent = nil)
503
- @association.delete_all(dependent)
475
+ @association.delete_all(dependent).tap { reset_scope }
504
476
  end
505
477
 
506
478
  # Deletes the records of the collection directly from the database
@@ -527,7 +499,7 @@ module ActiveRecord
527
499
  #
528
500
  # Pet.find(1) # => Couldn't find Pet with id=1
529
501
  def destroy_all
530
- @association.destroy_all
502
+ @association.destroy_all.tap { reset_scope }
531
503
  end
532
504
 
533
505
  # Deletes the +records+ supplied from the collection according to the strategy
@@ -646,7 +618,7 @@ module ActiveRecord
646
618
  # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
647
619
  # # ]
648
620
  def delete(*records)
649
- @association.delete(*records)
621
+ @association.delete(*records).tap { reset_scope }
650
622
  end
651
623
 
652
624
  # Destroys the +records+ supplied and removes them from the collection.
@@ -718,7 +690,7 @@ module ActiveRecord
718
690
  #
719
691
  # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (4, 5, 6)
720
692
  def destroy(*records)
721
- @association.destroy(*records)
693
+ @association.destroy(*records).tap { reset_scope }
722
694
  end
723
695
 
724
696
  ##
@@ -1033,8 +1005,9 @@ module ActiveRecord
1033
1005
  end
1034
1006
 
1035
1007
  # Adds one or more +records+ to the collection by setting their foreign keys
1036
- # to the association's primary key. Returns +self+, so several appends may be
1037
- # chained together.
1008
+ # to the association's primary key. Since +<<+ flattens its argument list and
1009
+ # inserts each record, +push+ and +concat+ behave identically. Returns +self+
1010
+ # so several appends may be chained together.
1038
1011
  #
1039
1012
  # class Person < ActiveRecord::Base
1040
1013
  # has_many :pets
@@ -1057,6 +1030,7 @@ module ActiveRecord
1057
1030
  end
1058
1031
  alias_method :push, :<<
1059
1032
  alias_method :append, :<<
1033
+ alias_method :concat, :<<
1060
1034
 
1061
1035
  def prepend(*args)
1062
1036
  raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
@@ -99,6 +99,7 @@ module ActiveRecord
99
99
  def delete_or_nullify_all_records(method)
100
100
  count = delete_count(method, scope)
101
101
  update_counter(-count)
102
+ count
102
103
  end
103
104
 
104
105
  # Deletes the records according to the <tt>:dependent</tt> option.
@@ -57,21 +57,14 @@ module ActiveRecord
57
57
  @through_records[record.object_id] ||= begin
58
58
  ensure_mutable
59
59
 
60
- through_record = through_association.build(*options_for_through_record)
61
- through_record.send("#{source_reflection.name}=", record)
60
+ attributes = through_scope_attributes
61
+ attributes[source_reflection.name] = record
62
+ attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type]
62
63
 
63
- if options[:source_type]
64
- through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
65
- end
66
-
67
- through_record
64
+ through_association.build(attributes)
68
65
  end
69
66
  end
70
67
 
71
- def options_for_through_record
72
- [through_scope_attributes]
73
- end
74
-
75
68
  def through_scope_attributes
76
69
  scope.where_values_hash(through_association.reflection.name.to_s).
77
70
  except!(through_association.reflection.foreign_key,
@@ -161,6 +154,8 @@ module ActiveRecord
161
154
  else
162
155
  update_counter(-count)
163
156
  end
157
+
158
+ count
164
159
  end
165
160
 
166
161
  def difference(a, b)
@@ -30,17 +30,21 @@ module ActiveRecord
30
30
  table = tables[-i]
31
31
  klass = reflection.klass
32
32
 
33
- constraint = reflection.build_join_constraint(table, foreign_table)
33
+ join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
34
34
 
35
- joins << table.create_join(table, table.create_on(constraint), join_type)
36
-
37
- join_scope = reflection.join_scope(table, foreign_klass)
38
35
  arel = join_scope.arel(alias_tracker.aliases)
36
+ nodes = arel.constraints.first
37
+
38
+ others, children = nodes.children.partition do |node|
39
+ !fetch_arel_attribute(node) { |attr| attr.relation.name == table.name }
40
+ end
41
+ nodes = table.create_and(children)
39
42
 
40
- if arel.constraints.any?
43
+ joins << table.create_join(table, table.create_on(nodes), join_type)
44
+
45
+ unless others.empty?
41
46
  joins.concat arel.join_sources
42
- right = joins.last.right
43
- right.expr = right.expr.and(arel.constraints)
47
+ append_constraints(joins.last, others)
44
48
  end
45
49
 
46
50
  # The current table in this iteration becomes the foreign table in the next
@@ -54,6 +58,23 @@ module ActiveRecord
54
58
  @tables = tables
55
59
  @table = tables.first
56
60
  end
61
+
62
+ private
63
+ def fetch_arel_attribute(value)
64
+ case value
65
+ when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
66
+ yield value.left.is_a?(Arel::Attributes::Attribute) ? value.left : value.right
67
+ end
68
+ end
69
+
70
+ def append_constraints(join, constraints)
71
+ if join.is_a?(Arel::Nodes::StringJoin)
72
+ join_string = table.create_and(constraints.unshift(join.left))
73
+ join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
74
+ else
75
+ join.right.expr.children.concat(constraints)
76
+ end
77
+ end
57
78
  end
58
79
  end
59
80
  end
@@ -177,7 +177,7 @@ module ActiveRecord
177
177
  # and attach it to a relation. The class returned implements a `run` method
178
178
  # that accepts a preloader.
179
179
  def preloader_for(reflection, owners)
180
- if owners.first.association(reflection.name).loaded?
180
+ if owners.all? { |o| o.association(reflection.name).loaded? }
181
181
  return AlreadyLoaded
182
182
  end
183
183
  reflection.check_preloadable!
@@ -272,7 +272,7 @@ module ActiveRecord
272
272
  # or saved. If +autosave+ is +false+ only new records will be returned,
273
273
  # unless the parent is/was a new record itself.
274
274
  def associated_records_to_validate_or_save(association, new_record, autosave)
275
- if new_record
275
+ if new_record || custom_validation_context?
276
276
  association && association.target
277
277
  elsif autosave
278
278
  association.target.find_all(&:changed_for_autosave?)
@@ -304,7 +304,7 @@ module ActiveRecord
304
304
  def validate_single_association(reflection)
305
305
  association = association_instance_get(reflection.name)
306
306
  record = association && association.reader
307
- association_valid?(reflection, record) if record
307
+ association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?)
308
308
  end
309
309
 
310
310
  # Validate the associated records if <tt>:validate</tt> or
@@ -324,7 +324,7 @@ module ActiveRecord
324
324
  def association_valid?(reflection, record, index = nil)
325
325
  return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
326
326
 
327
- context = validation_context unless [:create, :update].include?(validation_context)
327
+ context = validation_context if custom_validation_context?
328
328
 
329
329
  unless valid = record.valid?(context)
330
330
  if reflection.options[:autosave]
@@ -382,10 +382,14 @@ module ActiveRecord
382
382
  if association = association_instance_get(reflection.name)
383
383
  autosave = reflection.options[:autosave]
384
384
 
385
+ # By saving the instance variable in a local variable,
386
+ # we make the whole callback re-entrant.
387
+ new_record_before_save = @new_record_before_save
388
+
385
389
  # reconstruct the scope now that we know the owner's id
386
390
  association.reset_scope
387
391
 
388
- if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
392
+ if records = associated_records_to_validate_or_save(association, new_record_before_save, autosave)
389
393
  if autosave
390
394
  records_to_destroy = records.select(&:marked_for_destruction?)
391
395
  records_to_destroy.each { |record| association.destroy(record) }
@@ -397,7 +401,7 @@ module ActiveRecord
397
401
 
398
402
  saved = true
399
403
 
400
- if autosave != false && (@new_record_before_save || record.new_record?)
404
+ if autosave != false && (new_record_before_save || record.new_record?)
401
405
  if autosave
402
406
  saved = association.insert_record(record, false)
403
407
  elsif !reflection.nested?
@@ -457,10 +461,16 @@ module ActiveRecord
457
461
  # If the record is new or it has changed, returns true.
458
462
  def record_changed?(reflection, record, key)
459
463
  record.new_record? ||
460
- (record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key) ||
464
+ association_foreign_key_changed?(reflection, record, key) ||
461
465
  record.will_save_change_to_attribute?(reflection.foreign_key)
462
466
  end
463
467
 
468
+ def association_foreign_key_changed?(reflection, record, key)
469
+ return false if reflection.through_reflection?
470
+
471
+ record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key
472
+ end
473
+
464
474
  # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
465
475
  #
466
476
  # In addition, it will destroy the association if it was marked for destruction.
@@ -489,6 +499,10 @@ module ActiveRecord
489
499
  end
490
500
  end
491
501
 
502
+ def custom_validation_context?
503
+ validation_context && [:create, :update].exclude?(validation_context)
504
+ end
505
+
492
506
  def _ensure_no_duplicate_errors
493
507
  errors.messages.each_key do |attribute|
494
508
  errors[attribute].uniq!