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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +361 -0
- data/lib/active_record/association_relation.rb +3 -3
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +25 -10
- data/lib/active_record/associations/belongs_to_association.rb +14 -5
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +4 -1
- data/lib/active_record/associations/builder/belongs_to.rb +11 -2
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/collection_association.rb +19 -15
- data/lib/active_record/associations/collection_proxy.rb +8 -34
- data/lib/active_record/associations/has_many_association.rb +9 -0
- data/lib/active_record/associations/has_many_through_association.rb +29 -12
- data/lib/active_record/associations/has_one_association.rb +8 -0
- data/lib/active_record/associations/has_one_through_association.rb +5 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +39 -24
- data/lib/active_record/associations/join_dependency/join_part.rb +7 -0
- data/lib/active_record/associations/join_dependency.rb +39 -64
- data/lib/active_record/associations/preloader.rb +1 -1
- data/lib/active_record/associations/singular_association.rb +4 -10
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +9 -9
- data/lib/active_record/attribute_methods/dirty.rb +15 -10
- data/lib/active_record/attribute_methods/read.rb +1 -1
- data/lib/active_record/autosave_association.rb +27 -8
- data/lib/active_record/callbacks.rb +4 -0
- data/lib/active_record/coders/yaml_column.rb +13 -1
- data/lib/active_record/collection_cache_key.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +36 -11
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +19 -6
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -3
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +6 -15
- data/lib/active_record/connection_adapters/abstract/transaction.rb +23 -14
- data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +18 -19
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +11 -2
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +11 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -26
- data/lib/active_record/connection_adapters/postgresql/utils.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -1
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +5 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +5 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -6
- data/lib/active_record/core.rb +12 -1
- data/lib/active_record/counter_cache.rb +17 -13
- data/lib/active_record/enum.rb +1 -0
- data/lib/active_record/errors.rb +18 -12
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/migration/compatibility.rb +15 -15
- data/lib/active_record/migration.rb +1 -1
- data/lib/active_record/model_schema.rb +1 -1
- data/lib/active_record/persistence.rb +6 -5
- data/lib/active_record/query_cache.rb +4 -11
- data/lib/active_record/querying.rb +1 -1
- data/lib/active_record/railtie.rb +19 -3
- data/lib/active_record/reflection.rb +10 -14
- data/lib/active_record/relation/calculations.rb +16 -12
- data/lib/active_record/relation/delegation.rb +30 -0
- data/lib/active_record/relation/finder_methods.rb +10 -8
- data/lib/active_record/relation/merger.rb +10 -11
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder.rb +20 -14
- data/lib/active_record/relation/query_attribute.rb +5 -3
- data/lib/active_record/relation/query_methods.rb +50 -22
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation.rb +39 -20
- data/lib/active_record/scoping/default.rb +2 -2
- data/lib/active_record/scoping/named.rb +2 -0
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/tasks/database_tasks.rb +1 -1
- data/lib/active_record/timestamp.rb +8 -1
- data/lib/active_record/transactions.rb +24 -21
- data/lib/active_record/type/serialized.rb +4 -0
- 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.
|
1037
|
-
#
|
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
|
-
|
61
|
-
|
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
|
-
|
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
|
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.
|
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
|
-
|
10
|
-
|
9
|
+
attr_reader :reflection, :tables
|
10
|
+
attr_accessor :table
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
def initialize(reflection, children, alias_tracker)
|
12
|
+
def initialize(reflection, children)
|
15
13
|
super(reflection.klass, children)
|
16
14
|
|
17
|
-
@
|
18
|
-
@
|
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,
|
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
|
29
|
+
reflection.chain.reverse_each.with_index(1) do |reflection, i|
|
30
|
+
table = tables[-i]
|
35
31
|
klass = reflection.klass
|
36
32
|
|
37
|
-
|
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
|
-
|
45
|
+
unless others.empty?
|
45
46
|
joins.concat arel.join_sources
|
46
|
-
|
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
|
58
|
-
tables
|
57
|
+
def tables=(tables)
|
58
|
+
@tables = tables
|
59
|
+
@table = tables.first
|
59
60
|
end
|
60
61
|
|
61
|
-
|
62
|
-
|
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
|
-
|
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
|
-
|
104
|
-
|
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
|
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, :
|
131
|
+
attr_reader :alias_tracker, :join_root
|
160
132
|
|
161
133
|
private
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
171
|
-
|
172
|
-
|
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(
|
176
|
-
|
177
|
-
|
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
|
-
|
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
|
-
|
208
|
-
|
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)
|
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
|
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
|
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.
|
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
|
-
|
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
|
@@ -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(
|
1236
|
-
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new(
|
1237
|
-
# * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new(
|
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(
|
1409
|
-
# * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new(
|
1410
|
-
# * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new(
|
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(
|
1750
|
-
# * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new(
|
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
|
-
|
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
|
-
|
119
|
+
mutations_from_database.changed_values
|
123
120
|
end
|
124
121
|
|
125
122
|
private
|
126
|
-
def write_attribute_without_type_cast(attr_name,
|
127
|
-
|
128
|
-
|
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
|