activerecord 5.2.0 → 5.2.3

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +214 -0
  3. data/lib/active_record/association_relation.rb +3 -3
  4. data/lib/active_record/associations.rb +9 -9
  5. data/lib/active_record/associations/alias_tracker.rb +1 -1
  6. data/lib/active_record/associations/association.rb +25 -10
  7. data/lib/active_record/associations/belongs_to_association.rb +14 -5
  8. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +4 -1
  9. data/lib/active_record/associations/builder/belongs_to.rb +11 -2
  10. data/lib/active_record/associations/collection_association.rb +19 -15
  11. data/lib/active_record/associations/collection_proxy.rb +8 -34
  12. data/lib/active_record/associations/has_many_association.rb +9 -0
  13. data/lib/active_record/associations/has_many_through_association.rb +25 -1
  14. data/lib/active_record/associations/has_one_association.rb +8 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +5 -1
  16. data/lib/active_record/associations/join_dependency.rb +39 -64
  17. data/lib/active_record/associations/join_dependency/join_association.rb +12 -18
  18. data/lib/active_record/associations/join_dependency/join_part.rb +7 -0
  19. data/lib/active_record/associations/singular_association.rb +4 -10
  20. data/lib/active_record/associations/through_association.rb +1 -1
  21. data/lib/active_record/attribute_methods/dirty.rb +15 -10
  22. data/lib/active_record/attribute_methods/read.rb +1 -1
  23. data/lib/active_record/autosave_association.rb +7 -2
  24. data/lib/active_record/callbacks.rb +4 -0
  25. data/lib/active_record/collection_cache_key.rb +2 -2
  26. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +14 -8
  27. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +19 -6
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +5 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -4
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +5 -14
  33. data/lib/active_record/connection_adapters/abstract/transaction.rb +23 -14
  34. data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
  35. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +18 -19
  36. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  37. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +11 -2
  38. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -0
  39. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +2 -2
  40. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +11 -1
  41. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +4 -0
  42. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  43. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -26
  44. data/lib/active_record/connection_adapters/postgresql/utils.rb +1 -1
  45. data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -1
  46. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +5 -0
  47. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +5 -1
  48. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -4
  49. data/lib/active_record/core.rb +2 -1
  50. data/lib/active_record/counter_cache.rb +17 -13
  51. data/lib/active_record/enum.rb +1 -0
  52. data/lib/active_record/errors.rb +18 -12
  53. data/lib/active_record/gem_version.rb +1 -1
  54. data/lib/active_record/log_subscriber.rb +1 -1
  55. data/lib/active_record/migration.rb +1 -1
  56. data/lib/active_record/migration/compatibility.rb +15 -15
  57. data/lib/active_record/model_schema.rb +1 -1
  58. data/lib/active_record/persistence.rb +6 -5
  59. data/lib/active_record/query_cache.rb +4 -11
  60. data/lib/active_record/querying.rb +1 -1
  61. data/lib/active_record/railtie.rb +1 -3
  62. data/lib/active_record/relation.rb +39 -20
  63. data/lib/active_record/relation/calculations.rb +11 -8
  64. data/lib/active_record/relation/delegation.rb +30 -0
  65. data/lib/active_record/relation/finder_methods.rb +10 -8
  66. data/lib/active_record/relation/merger.rb +10 -11
  67. data/lib/active_record/relation/predicate_builder.rb +20 -14
  68. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  69. data/lib/active_record/relation/query_attribute.rb +5 -3
  70. data/lib/active_record/relation/query_methods.rb +45 -19
  71. data/lib/active_record/relation/spawn_methods.rb +1 -1
  72. data/lib/active_record/scoping/named.rb +2 -0
  73. data/lib/active_record/tasks/database_tasks.rb +1 -1
  74. data/lib/active_record/timestamp.rb +8 -1
  75. data/lib/active_record/transactions.rb +23 -20
  76. data/lib/active_record/type/serialized.rb +4 -0
  77. metadata +9 -10
@@ -45,6 +45,8 @@ module ActiveRecord
45
45
  def ids_reader
46
46
  if loaded?
47
47
  target.pluck(reflection.association_primary_key)
48
+ elsif !target.empty?
49
+ load_target.pluck(reflection.association_primary_key)
48
50
  else
49
51
  @association_ids ||= scope.pluck(reflection.association_primary_key)
50
52
  end
@@ -103,15 +105,12 @@ module ActiveRecord
103
105
  if attributes.is_a?(Array)
104
106
  attributes.collect { |attr| build(attr, &block) }
105
107
  else
106
- add_to_target(build_record(attributes)) do |record|
107
- yield(record) if block_given?
108
- end
108
+ add_to_target(build_record(attributes, &block))
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?
@@ -356,15 +355,17 @@ module ActiveRecord
356
355
  if attributes.is_a?(Array)
357
356
  attributes.collect { |attr| _create_record(attr, raise, &block) }
358
357
  else
358
+ record = build_record(attributes, &block)
359
359
  transaction do
360
- add_to_target(build_record(attributes)) do |record|
361
- yield(record) if block_given?
362
- insert_record(record, true, raise) {
360
+ result = nil
361
+ add_to_target(record) do
362
+ result = insert_record(record, true, raise) {
363
363
  @_was_loaded = loaded?
364
- @association_ids = nil
365
364
  }
366
365
  end
366
+ raise ActiveRecord::Rollback unless result
367
367
  end
368
+ record
368
369
  end
369
370
  end
370
371
 
@@ -396,6 +397,7 @@ module ActiveRecord
396
397
 
397
398
  delete_records(existing_records, method) if existing_records.any?
398
399
  records.each { |record| target.delete(record) }
400
+ @association_ids = nil
399
401
 
400
402
  records.each { |record| callback(:after_remove, record) }
401
403
  end
@@ -408,9 +410,9 @@ module ActiveRecord
408
410
  end
409
411
 
410
412
  def replace_records(new_target, original_target)
411
- delete(target - new_target)
413
+ delete(difference(target, new_target))
412
414
 
413
- unless concat(new_target - target)
415
+ unless concat(difference(new_target, target))
414
416
  @target = original_target
415
417
  raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
416
418
  "new records could not be saved."
@@ -420,7 +422,7 @@ module ActiveRecord
420
422
  end
421
423
 
422
424
  def replace_common_records_in_memory(new_target, original_target)
423
- common_records = new_target & original_target
425
+ common_records = intersection(new_target, original_target)
424
426
  common_records.each do |record|
425
427
  skip_callbacks = true
426
428
  replace_on_target(record, @target.index(record), skip_callbacks)
@@ -436,13 +438,14 @@ module ActiveRecord
436
438
  unless owner.new_record?
437
439
  result &&= insert_record(record, true, raise) {
438
440
  @_was_loaded = loaded?
439
- @association_ids = nil
440
441
  }
441
442
  end
442
443
  end
443
444
  end
444
445
 
445
- result && records
446
+ raise ActiveRecord::Rollback unless result
447
+
448
+ records
446
449
  end
447
450
 
448
451
  def replace_on_target(record, index, skip_callbacks)
@@ -457,6 +460,7 @@ module ActiveRecord
457
460
  if index
458
461
  target[index] = record
459
462
  elsif @_was_loaded || !loaded?
463
+ @association_ids = nil
460
464
  target << record
461
465
  end
462
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.
@@ -130,6 +131,14 @@ module ActiveRecord
130
131
  end
131
132
  saved_successfully
132
133
  end
134
+
135
+ def difference(a, b)
136
+ a - b
137
+ end
138
+
139
+ def intersection(a, b)
140
+ a & b
141
+ end
133
142
  end
134
143
  end
135
144
  end
@@ -90,7 +90,7 @@ module ActiveRecord
90
90
  def build_record(attributes)
91
91
  ensure_not_nested
92
92
 
93
- record = super(attributes)
93
+ record = super
94
94
 
95
95
  inverse = source_reflection.inverse_of
96
96
  if inverse
@@ -161,6 +161,30 @@ module ActiveRecord
161
161
  else
162
162
  update_counter(-count)
163
163
  end
164
+
165
+ count
166
+ end
167
+
168
+ def difference(a, b)
169
+ distribution = distribution(b)
170
+
171
+ a.reject { |record| mark_occurrence(distribution, record) }
172
+ end
173
+
174
+ def intersection(a, b)
175
+ distribution = distribution(b)
176
+
177
+ a.select { |record| mark_occurrence(distribution, record) }
178
+ end
179
+
180
+ def mark_occurrence(distribution, record)
181
+ distribution[record] > 0 && distribution[record] -= 1
182
+ end
183
+
184
+ def distribution(array)
185
+ array.each_with_object(Hash.new(0)) do |record, distribution|
186
+ distribution[record] += 1
187
+ end
164
188
  end
165
189
 
166
190
  def through_records_for(record)
@@ -107,6 +107,14 @@ module ActiveRecord
107
107
  yield
108
108
  end
109
109
  end
110
+
111
+ def _create_record(attributes, raise_error = false, &block)
112
+ unless owner.persisted?
113
+ raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
114
+ end
115
+
116
+ super
117
+ end
110
118
  end
111
119
  end
112
120
  end
@@ -28,7 +28,11 @@ module ActiveRecord
28
28
  end
29
29
 
30
30
  if through_record
31
- through_record.update(attributes)
31
+ if through_record.new_record?
32
+ through_record.assign_attributes(attributes)
33
+ else
34
+ through_record.update(attributes)
35
+ end
32
36
  elsif owner.new_record? || !save
33
37
  through_proxy.build(attributes)
34
38
  else
@@ -67,63 +67,31 @@ module ActiveRecord
67
67
  end
68
68
  end
69
69
 
70
- # base is the base class on which operation is taking place.
71
- # associations is the list of associations which are joined using hash, symbol or array.
72
- # joins is the list of all string join commands and arel nodes.
73
- #
74
- # Example :
75
- #
76
- # class Physician < ActiveRecord::Base
77
- # has_many :appointments
78
- # has_many :patients, through: :appointments
79
- # end
80
- #
81
- # If I execute `@physician.patients.to_a` then
82
- # base # => Physician
83
- # associations # => []
84
- # joins # => [#<Arel::Nodes::InnerJoin: ...]
85
- #
86
- # However if I execute `Physician.joins(:appointments).to_a` then
87
- # base # => Physician
88
- # associations # => [:appointments]
89
- # joins # => []
90
- #
91
- def initialize(base, table, associations, alias_tracker)
92
- @alias_tracker = alias_tracker
70
+ def initialize(base, table, associations)
93
71
  tree = self.class.make_tree associations
94
72
  @join_root = JoinBase.new(base, table, build(tree, base))
95
- @join_root.children.each { |child| construct_tables! @join_root, child }
96
73
  end
97
74
 
98
75
  def reflections
99
76
  join_root.drop(1).map!(&:reflection)
100
77
  end
101
78
 
102
- def join_constraints(joins_to_add, join_type)
103
- joins = join_root.children.flat_map { |child|
104
- make_join_constraints(join_root, child, join_type)
105
- }
79
+ def join_constraints(joins_to_add, join_type, alias_tracker)
80
+ @alias_tracker = alias_tracker
81
+
82
+ construct_tables!(join_root)
83
+ joins = make_join_constraints(join_root, join_type)
106
84
 
107
85
  joins.concat joins_to_add.flat_map { |oj|
86
+ construct_tables!(oj.join_root)
108
87
  if join_root.match? oj.join_root
109
88
  walk join_root, oj.join_root
110
89
  else
111
- oj.join_root.children.flat_map { |child|
112
- make_join_constraints(oj.join_root, child, join_type)
113
- }
90
+ make_join_constraints(oj.join_root, join_type)
114
91
  end
115
92
  }
116
93
  end
117
94
 
118
- def aliases
119
- @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
120
- columns = join_part.column_names.each_with_index.map { |column_name, j|
121
- Aliases::Column.new column_name, "t#{i}_r#{j}"
122
- }
123
- Aliases::Table.new(join_part, columns)
124
- }
125
- end
126
-
127
95
  def instantiate(result_set, &block)
128
96
  primary_key = aliases.column_alias(join_root, join_root.primary_key)
129
97
 
@@ -155,28 +123,40 @@ module ActiveRecord
155
123
  parents.values
156
124
  end
157
125
 
126
+ def apply_column_aliases(relation)
127
+ relation._select!(-> { aliases.columns })
128
+ end
129
+
158
130
  protected
159
- attr_reader :alias_tracker, :base_klass, :join_root
131
+ attr_reader :alias_tracker, :join_root
160
132
 
161
133
  private
162
-
163
- def make_constraints(parent, child, tables, join_type)
164
- chain = child.reflection.chain
165
- foreign_table = parent.table
166
- foreign_klass = parent.base_klass
167
- child.join_constraints(foreign_table, foreign_klass, join_type, tables, chain)
134
+ def aliases
135
+ @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
136
+ columns = join_part.column_names.each_with_index.map { |column_name, j|
137
+ Aliases::Column.new column_name, "t#{i}_r#{j}"
138
+ }
139
+ Aliases::Table.new(join_part, columns)
140
+ }
168
141
  end
169
142
 
170
- def make_outer_joins(parent, child)
171
- join_type = Arel::Nodes::OuterJoin
172
- make_join_constraints(parent, child, join_type, true)
143
+ def construct_tables!(join_root)
144
+ join_root.each_children do |parent, child|
145
+ child.tables = table_aliases_for(parent, child)
146
+ end
173
147
  end
174
148
 
175
- def make_join_constraints(parent, child, join_type, aliasing = false)
176
- tables = aliasing ? table_aliases_for(parent, child) : child.tables
177
- joins = make_constraints(parent, child, tables, join_type)
149
+ def make_join_constraints(join_root, join_type)
150
+ join_root.children.flat_map do |child|
151
+ make_constraints(join_root, child, join_type)
152
+ end
153
+ end
178
154
 
179
- joins.concat child.children.flat_map { |c| make_join_constraints(child, c, join_type, aliasing) }
155
+ def make_constraints(parent, child, join_type = Arel::Nodes::OuterJoin)
156
+ foreign_table = parent.table
157
+ foreign_klass = parent.base_klass
158
+ joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
159
+ joins.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
180
160
  end
181
161
 
182
162
  def table_aliases_for(parent, node)
@@ -189,11 +169,6 @@ module ActiveRecord
189
169
  }
190
170
  end
191
171
 
192
- def construct_tables!(parent, node)
193
- node.tables = table_aliases_for(parent, node)
194
- node.children.each { |child| construct_tables! node, child }
195
- end
196
-
197
172
  def table_alias_for(reflection, parent, join)
198
173
  name = "#{reflection.plural_name}_#{parent.table_name}"
199
174
  join ? "#{name}_join" : name
@@ -204,8 +179,8 @@ module ActiveRecord
204
179
  [left.children.find { |node2| node1.match? node2 }, node1]
205
180
  }.partition(&:first)
206
181
 
207
- ojs = missing.flat_map { |_, n| make_outer_joins left, n }
208
- intersection.flat_map { |l, r| walk l, r }.concat ojs
182
+ joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r) }
183
+ joins.concat missing.flat_map { |_, n| make_constraints(left, n) }
209
184
  end
210
185
 
211
186
  def find_reflection(klass, name)
@@ -223,7 +198,7 @@ module ActiveRecord
223
198
  raise EagerLoadPolymorphicError.new(reflection)
224
199
  end
225
200
 
226
- JoinAssociation.new(reflection, build(right, reflection.klass), alias_tracker)
201
+ JoinAssociation.new(reflection, build(right, reflection.klass))
227
202
  end
228
203
  end
229
204
 
@@ -248,7 +223,7 @@ module ActiveRecord
248
223
  next
249
224
  end
250
225
 
251
- model = seen[ar_parent.object_id][node.base_klass][id]
226
+ model = seen[ar_parent.object_id][node][id]
252
227
 
253
228
  if model
254
229
  construct(model, node, row, rs, seen, model_cache, aliases)
@@ -260,7 +235,7 @@ module ActiveRecord
260
235
  model.readonly!
261
236
  end
262
237
 
263
- seen[ar_parent.object_id][node.base_klass][id] = model
238
+ seen[ar_parent.object_id][node][id] = model
264
239
  construct(model, node, row, rs, seen, model_cache, aliases)
265
240
  end
266
241
  end