activerecord 5.2.0 → 5.2.8.1

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +361 -0
  3. data/lib/active_record/association_relation.rb +3 -3
  4. data/lib/active_record/associations/alias_tracker.rb +1 -1
  5. data/lib/active_record/associations/association.rb +25 -10
  6. data/lib/active_record/associations/belongs_to_association.rb +14 -5
  7. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +4 -1
  8. data/lib/active_record/associations/builder/belongs_to.rb +11 -2
  9. data/lib/active_record/associations/builder/collection_association.rb +2 -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 +29 -12
  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/join_association.rb +39 -24
  17. data/lib/active_record/associations/join_dependency/join_part.rb +7 -0
  18. data/lib/active_record/associations/join_dependency.rb +39 -64
  19. data/lib/active_record/associations/preloader.rb +1 -1
  20. data/lib/active_record/associations/singular_association.rb +4 -10
  21. data/lib/active_record/associations/through_association.rb +1 -1
  22. data/lib/active_record/associations.rb +9 -9
  23. data/lib/active_record/attribute_methods/dirty.rb +15 -10
  24. data/lib/active_record/attribute_methods/read.rb +1 -1
  25. data/lib/active_record/autosave_association.rb +27 -8
  26. data/lib/active_record/callbacks.rb +4 -0
  27. data/lib/active_record/coders/yaml_column.rb +13 -1
  28. data/lib/active_record/collection_cache_key.rb +2 -2
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +36 -11
  30. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  31. data/lib/active_record/connection_adapters/abstract/database_statements.rb +19 -6
  32. data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -3
  33. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -0
  34. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -4
  35. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +6 -15
  36. data/lib/active_record/connection_adapters/abstract/transaction.rb +23 -14
  37. data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
  38. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +18 -19
  39. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  40. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +11 -2
  41. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -0
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +2 -2
  43. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +11 -1
  44. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  45. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +4 -0
  46. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  47. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -26
  48. data/lib/active_record/connection_adapters/postgresql/utils.rb +1 -1
  49. data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -1
  50. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +5 -0
  51. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +5 -1
  52. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -6
  53. data/lib/active_record/core.rb +12 -1
  54. data/lib/active_record/counter_cache.rb +17 -13
  55. data/lib/active_record/enum.rb +1 -0
  56. data/lib/active_record/errors.rb +18 -12
  57. data/lib/active_record/gem_version.rb +2 -2
  58. data/lib/active_record/log_subscriber.rb +1 -1
  59. data/lib/active_record/migration/compatibility.rb +15 -15
  60. data/lib/active_record/migration.rb +1 -1
  61. data/lib/active_record/model_schema.rb +1 -1
  62. data/lib/active_record/persistence.rb +6 -5
  63. data/lib/active_record/query_cache.rb +4 -11
  64. data/lib/active_record/querying.rb +1 -1
  65. data/lib/active_record/railtie.rb +19 -3
  66. data/lib/active_record/reflection.rb +10 -14
  67. data/lib/active_record/relation/calculations.rb +16 -12
  68. data/lib/active_record/relation/delegation.rb +30 -0
  69. data/lib/active_record/relation/finder_methods.rb +10 -8
  70. data/lib/active_record/relation/merger.rb +10 -11
  71. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  72. data/lib/active_record/relation/predicate_builder.rb +20 -14
  73. data/lib/active_record/relation/query_attribute.rb +5 -3
  74. data/lib/active_record/relation/query_methods.rb +50 -22
  75. data/lib/active_record/relation/spawn_methods.rb +1 -1
  76. data/lib/active_record/relation.rb +39 -20
  77. data/lib/active_record/scoping/default.rb +2 -2
  78. data/lib/active_record/scoping/named.rb +2 -0
  79. data/lib/active_record/statement_cache.rb +2 -2
  80. data/lib/active_record/tasks/database_tasks.rb +1 -1
  81. data/lib/active_record/timestamp.rb +8 -1
  82. data/lib/active_record/transactions.rb +24 -21
  83. data/lib/active_record/type/serialized.rb +4 -0
  84. metadata +12 -13
@@ -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
@@ -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,
@@ -90,7 +83,7 @@ module ActiveRecord
90
83
  def build_record(attributes)
91
84
  ensure_not_nested
92
85
 
93
- record = super(attributes)
86
+ record = super
94
87
 
95
88
  inverse = source_reflection.inverse_of
96
89
  if inverse
@@ -161,6 +154,30 @@ module ActiveRecord
161
154
  else
162
155
  update_counter(-count)
163
156
  end
157
+
158
+ count
159
+ end
160
+
161
+ def difference(a, b)
162
+ distribution = distribution(b)
163
+
164
+ a.reject { |record| mark_occurrence(distribution, record) }
165
+ end
166
+
167
+ def intersection(a, b)
168
+ distribution = distribution(b)
169
+
170
+ a.select { |record| mark_occurrence(distribution, record) }
171
+ end
172
+
173
+ def mark_occurrence(distribution, record)
174
+ distribution[record] > 0 && distribution[record] -= 1
175
+ end
176
+
177
+ def distribution(array)
178
+ array.each_with_object(Hash.new(0)) do |record, distribution|
179
+ distribution[record] += 1
180
+ end
164
181
  end
165
182
 
166
183
  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
@@ -6,17 +6,14 @@ module ActiveRecord
6
6
  module Associations
7
7
  class JoinDependency # :nodoc:
8
8
  class JoinAssociation < JoinPart # :nodoc:
9
- # The reflection of the association represented
10
- attr_reader :reflection
9
+ attr_reader :reflection, :tables
10
+ attr_accessor :table
11
11
 
12
- attr_accessor :tables
13
-
14
- def initialize(reflection, children, alias_tracker)
12
+ def initialize(reflection, children)
15
13
  super(reflection.klass, children)
16
14
 
17
- @alias_tracker = alias_tracker
18
- @reflection = reflection
19
- @tables = nil
15
+ @reflection = reflection
16
+ @tables = nil
20
17
  end
21
18
 
22
19
  def match?(other)
@@ -24,27 +21,30 @@ module ActiveRecord
24
21
  super && reflection == other.reflection
25
22
  end
26
23
 
27
- def join_constraints(foreign_table, foreign_klass, join_type, tables, chain)
28
- joins = []
29
- tables = tables.reverse
24
+ def join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
25
+ joins = []
30
26
 
31
27
  # The chain starts with the target table, but we want to end with it here (makes
32
28
  # more sense in this context), so we reverse
33
- chain.reverse_each do |reflection|
34
- table = tables.shift
29
+ reflection.chain.reverse_each.with_index(1) do |reflection, i|
30
+ table = tables[-i]
35
31
  klass = reflection.klass
36
32
 
37
- constraint = reflection.build_join_constraint(table, foreign_table)
38
-
39
- joins << table.create_join(table, table.create_on(constraint), join_type)
33
+ join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
40
34
 
41
- join_scope = reflection.join_scope(table, foreign_klass)
42
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)
42
+
43
+ joins << table.create_join(table, table.create_on(nodes), join_type)
43
44
 
44
- if arel.constraints.any?
45
+ unless others.empty?
45
46
  joins.concat arel.join_sources
46
- right = joins.last.right
47
- right.expr = right.expr.and(arel.constraints)
47
+ append_constraints(joins.last, others)
48
48
  end
49
49
 
50
50
  # The current table in this iteration becomes the foreign table in the next
@@ -54,12 +54,27 @@ module ActiveRecord
54
54
  joins
55
55
  end
56
56
 
57
- def table
58
- tables.first
57
+ def tables=(tables)
58
+ @tables = tables
59
+ @table = tables.first
59
60
  end
60
61
 
61
- protected
62
- attr_reader :alias_tracker
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
63
78
  end
64
79
  end
65
80
  end
@@ -33,6 +33,13 @@ module ActiveRecord
33
33
  children.each { |child| child.each(&block) }
34
34
  end
35
35
 
36
+ def each_children(&block)
37
+ children.each do |child|
38
+ yield self, child
39
+ child.each_children(&block)
40
+ end
41
+ end
42
+
36
43
  # An Arel::Table for the active_record
37
44
  def table
38
45
  raise NotImplementedError
@@ -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
@@ -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!
@@ -17,9 +17,8 @@ module ActiveRecord
17
17
  replace(record)
18
18
  end
19
19
 
20
- def build(attributes = {})
21
- record = build_record(attributes)
22
- yield(record) if block_given?
20
+ def build(attributes = {}, &block)
21
+ record = build_record(attributes, &block)
23
22
  set_new_record(record)
24
23
  record
25
24
  end
@@ -62,13 +61,8 @@ module ActiveRecord
62
61
  replace(record)
63
62
  end
64
63
 
65
- def _create_record(attributes, raise_error = false)
66
- unless owner.persisted?
67
- raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
68
- end
69
-
70
- record = build_record(attributes)
71
- yield(record) if block_given?
64
+ def _create_record(attributes, raise_error = false, &block)
65
+ record = build_record(attributes, &block)
72
66
  saved = record.save
73
67
  set_new_record(record)
74
68
  raise RecordInvalid.new(record) if !saved && raise_error
@@ -114,7 +114,7 @@ module ActiveRecord
114
114
  attributes[inverse.foreign_key] = target.id
115
115
  end
116
116
 
117
- super(attributes)
117
+ super
118
118
  end
119
119
  end
120
120
  end
@@ -241,7 +241,7 @@ module ActiveRecord
241
241
  association
242
242
  end
243
243
 
244
- def association_cached?(name) # :nodoc
244
+ def association_cached?(name) # :nodoc:
245
245
  @association_cache.key?(name)
246
246
  end
247
247
 
@@ -1232,9 +1232,9 @@ module ActiveRecord
1232
1232
  # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
1233
1233
  # * <tt>Firm#clients.find</tt> (similar to <tt>Client.where(firm_id: id).find(id)</tt>)
1234
1234
  # * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>)
1235
- # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
1236
- # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
1237
- # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>)
1235
+ # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new(firm_id: id)</tt>)
1236
+ # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new(firm_id: id); c.save; c</tt>)
1237
+ # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new(firm_id: id); c.save!</tt>)
1238
1238
  # * <tt>Firm#clients.reload</tt>
1239
1239
  # The declaration can also include an +options+ hash to specialize the behavior of the association.
1240
1240
  #
@@ -1405,9 +1405,9 @@ module ActiveRecord
1405
1405
  # An Account class declares <tt>has_one :beneficiary</tt>, which will add:
1406
1406
  # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.where(account_id: id).first</tt>)
1407
1407
  # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
1408
- # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
1409
- # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
1410
- # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>)
1408
+ # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new(account_id: id)</tt>)
1409
+ # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new(account_id: id); b.save; b</tt>)
1410
+ # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new(account_id: id); b.save!; b</tt>)
1411
1411
  # * <tt>Account#reload_beneficiary</tt>
1412
1412
  #
1413
1413
  # === Scopes
@@ -1746,8 +1746,8 @@ module ActiveRecord
1746
1746
  # * <tt>Developer#projects.size</tt>
1747
1747
  # * <tt>Developer#projects.find(id)</tt>
1748
1748
  # * <tt>Developer#projects.exists?(...)</tt>
1749
- # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>)
1750
- # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>)
1749
+ # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new(developer_id: id)</tt>)
1750
+ # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new(developer_id: id); c.save; c</tt>)
1751
1751
  # * <tt>Developer#projects.reload</tt>
1752
1752
  # The declaration may include an +options+ hash to specialize the behavior of the association.
1753
1753
  #
@@ -16,9 +16,6 @@ module ActiveRecord
16
16
 
17
17
  class_attribute :partial_writes, instance_writer: false, default: true
18
18
 
19
- after_create { changes_applied }
20
- after_update { changes_applied }
21
-
22
19
  # Attribute methods for "changed in last call to save?"
23
20
  attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
24
21
  attribute_method_prefix("saved_change_to_")
@@ -114,27 +111,35 @@ module ActiveRecord
114
111
 
115
112
  # Alias for +changed+
116
113
  def changed_attribute_names_to_save
117
- changes_to_save.keys
114
+ mutations_from_database.changed_attribute_names
118
115
  end
119
116
 
120
117
  # Alias for +changed_attributes+
121
118
  def attributes_in_database
122
- changes_to_save.transform_values(&:first)
119
+ mutations_from_database.changed_values
123
120
  end
124
121
 
125
122
  private
126
- def write_attribute_without_type_cast(attr_name, _)
127
- result = super
128
- clear_attribute_change(attr_name)
123
+ def write_attribute_without_type_cast(attr_name, value)
124
+ name = attr_name.to_s
125
+ if self.class.attribute_alias?(name)
126
+ name = self.class.attribute_alias(name)
127
+ end
128
+ result = super(name, value)
129
+ clear_attribute_change(name)
129
130
  result
130
131
  end
131
132
 
132
133
  def _update_record(*)
133
- partial_writes? ? super(keys_for_partial_write) : super
134
+ affected_rows = partial_writes? ? super(keys_for_partial_write) : super
135
+ changes_applied
136
+ affected_rows
134
137
  end
135
138
 
136
139
  def _create_record(*)
137
- partial_writes? ? super(keys_for_partial_write) : super
140
+ id = partial_writes? ? super(keys_for_partial_write) : super
141
+ changes_applied
142
+ id
138
143
  end
139
144
 
140
145
  def keys_for_partial_write
@@ -69,7 +69,7 @@ module ActiveRecord
69
69
  if defined?(JRUBY_VERSION)
70
70
  # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
71
71
  # https://github.com/jruby/jruby/pull/2562
72
- def _read_attribute(attr_name, &block) # :nodoc
72
+ def _read_attribute(attr_name, &block) # :nodoc:
73
73
  @attributes.fetch_value(attr_name.to_s, &block)
74
74
  end
75
75
  else