activerecord 4.1.16 → 4.2.11.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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1162 -1801
- data/README.rdoc +15 -10
- data/lib/active_record/aggregations.rb +15 -8
- data/lib/active_record/association_relation.rb +13 -0
- data/lib/active_record/associations/alias_tracker.rb +3 -12
- data/lib/active_record/associations/association.rb +16 -4
- data/lib/active_record/associations/association_scope.rb +83 -38
- data/lib/active_record/associations/belongs_to_association.rb +28 -10
- data/lib/active_record/associations/builder/association.rb +15 -4
- data/lib/active_record/associations/builder/belongs_to.rb +7 -29
- data/lib/active_record/associations/builder/collection_association.rb +5 -1
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
- data/lib/active_record/associations/builder/has_many.rb +1 -1
- data/lib/active_record/associations/builder/has_one.rb +2 -2
- data/lib/active_record/associations/builder/singular_association.rb +8 -1
- data/lib/active_record/associations/collection_association.rb +63 -27
- data/lib/active_record/associations/collection_proxy.rb +29 -35
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +83 -22
- data/lib/active_record/associations/has_many_through_association.rb +49 -26
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
- data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
- data/lib/active_record/associations/join_dependency.rb +26 -13
- data/lib/active_record/associations/preloader/association.rb +14 -11
- data/lib/active_record/associations/preloader/through_association.rb +4 -3
- data/lib/active_record/associations/preloader.rb +36 -26
- data/lib/active_record/associations/singular_association.rb +17 -2
- data/lib/active_record/associations/through_association.rb +5 -12
- data/lib/active_record/associations.rb +158 -49
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +19 -11
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
- data/lib/active_record/attribute_methods/dirty.rb +107 -43
- data/lib/active_record/attribute_methods/primary_key.rb +7 -8
- data/lib/active_record/attribute_methods/query.rb +1 -1
- data/lib/active_record/attribute_methods/read.rb +22 -59
- data/lib/active_record/attribute_methods/serialization.rb +16 -150
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
- data/lib/active_record/attribute_methods/write.rb +9 -24
- data/lib/active_record/attribute_methods.rb +56 -94
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +19 -12
- data/lib/active_record/base.rb +13 -24
- data/lib/active_record/callbacks.rb +6 -6
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
- data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
- data/lib/active_record/connection_adapters/column.rb +29 -240
- data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
- data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +131 -43
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
- data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
- data/lib/active_record/connection_handling.rb +1 -1
- data/lib/active_record/core.rb +163 -39
- data/lib/active_record/counter_cache.rb +60 -6
- data/lib/active_record/enum.rb +9 -11
- data/lib/active_record/errors.rb +53 -30
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixtures.rb +55 -69
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +35 -10
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locking/optimistic.rb +46 -26
- data/lib/active_record/migration/command_recorder.rb +19 -2
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/migration.rb +71 -46
- data/lib/active_record/model_schema.rb +52 -58
- data/lib/active_record/nested_attributes.rb +5 -5
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/persistence.rb +46 -26
- data/lib/active_record/query_cache.rb +3 -3
- data/lib/active_record/querying.rb +10 -7
- data/lib/active_record/railtie.rb +18 -11
- data/lib/active_record/railties/databases.rake +50 -51
- data/lib/active_record/readonly_attributes.rb +0 -1
- data/lib/active_record/reflection.rb +273 -114
- data/lib/active_record/relation/batches.rb +0 -2
- data/lib/active_record/relation/calculations.rb +41 -37
- data/lib/active_record/relation/finder_methods.rb +70 -47
- data/lib/active_record/relation/merger.rb +39 -29
- data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
- data/lib/active_record/relation/predicate_builder.rb +16 -8
- data/lib/active_record/relation/query_methods.rb +114 -65
- data/lib/active_record/relation/spawn_methods.rb +3 -0
- data/lib/active_record/relation.rb +57 -25
- data/lib/active_record/result.rb +18 -7
- data/lib/active_record/sanitization.rb +12 -2
- data/lib/active_record/schema.rb +0 -1
- data/lib/active_record/schema_dumper.rb +59 -28
- data/lib/active_record/schema_migration.rb +5 -4
- data/lib/active_record/scoping/default.rb +6 -4
- data/lib/active_record/scoping/named.rb +4 -0
- data/lib/active_record/serializers/xml_serializer.rb +3 -7
- data/lib/active_record/statement_cache.rb +95 -10
- data/lib/active_record/store.rb +5 -5
- data/lib/active_record/tasks/database_tasks.rb +61 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +20 -11
- data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
- data/lib/active_record/timestamp.rb +9 -7
- data/lib/active_record/transactions.rb +53 -27
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/integer.rb +59 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +62 -0
- data/lib/active_record/type/string.rb +40 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +110 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +5 -3
- data/lib/active_record/validations/presence.rb +5 -3
- data/lib/active_record/validations/uniqueness.rb +25 -29
- data/lib/active_record/validations.rb +25 -19
- data/lib/active_record.rb +4 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
- metadata +66 -11
- data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'active_support/core_ext/string/filters'
|
1
2
|
|
2
3
|
module ActiveRecord
|
3
4
|
# = Active Record Has Many Through Association
|
@@ -12,13 +13,14 @@ module ActiveRecord
|
|
12
13
|
@through_association = nil
|
13
14
|
end
|
14
15
|
|
15
|
-
# Returns the size of the collection by executing a SELECT COUNT(*) query
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
16
|
+
# Returns the size of the collection by executing a SELECT COUNT(*) query
|
17
|
+
# if the collection hasn't been loaded, and by calling collection.size if
|
18
|
+
# it has. If the collection will likely have a size greater than zero,
|
19
|
+
# and if fetching the collection will be needed afterwards, one less
|
20
|
+
# SELECT query will be generated by using #length instead.
|
19
21
|
def size
|
20
22
|
if has_cached_counter?
|
21
|
-
owner.
|
23
|
+
owner._read_attribute cached_counter_attribute_name(reflection)
|
22
24
|
elsif loaded?
|
23
25
|
target.size
|
24
26
|
else
|
@@ -62,7 +64,16 @@ module ActiveRecord
|
|
62
64
|
end
|
63
65
|
|
64
66
|
save_through_record(record)
|
65
|
-
|
67
|
+
if has_cached_counter? && !through_reflection_updates_counter_cache?
|
68
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
69
|
+
Automatic updating of counter caches on through associations has been
|
70
|
+
deprecated, and will be removed in Rails 5. Instead, please set the
|
71
|
+
appropriate `counter_cache` options on the `has_many` and `belongs_to`
|
72
|
+
for your associations to #{through_reflection.name}.
|
73
|
+
MSG
|
74
|
+
|
75
|
+
update_counter_in_database(1)
|
76
|
+
end
|
66
77
|
record
|
67
78
|
end
|
68
79
|
|
@@ -72,19 +83,22 @@ module ActiveRecord
|
|
72
83
|
@through_association ||= owner.association(through_reflection.name)
|
73
84
|
end
|
74
85
|
|
75
|
-
#
|
76
|
-
#
|
77
|
-
# want to use the exact same object.
|
86
|
+
# The through record (built with build_record) is temporarily cached
|
87
|
+
# so that it may be reused if insert_record is subsequently called.
|
78
88
|
#
|
79
|
-
# However, after insert_record has been called,
|
80
|
-
#
|
81
|
-
# association
|
89
|
+
# However, after insert_record has been called, the cache is cleared in
|
90
|
+
# order to allow multiple instances of the same record in an association.
|
82
91
|
def build_through_record(record)
|
83
92
|
@through_records[record.object_id] ||= begin
|
84
93
|
ensure_mutable
|
85
94
|
|
86
95
|
through_record = through_association.build(*options_for_through_record)
|
87
96
|
through_record.send("#{source_reflection.name}=", record)
|
97
|
+
|
98
|
+
if options[:source_type]
|
99
|
+
through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
|
100
|
+
end
|
101
|
+
|
88
102
|
through_record
|
89
103
|
end
|
90
104
|
end
|
@@ -112,9 +126,9 @@ module ActiveRecord
|
|
112
126
|
|
113
127
|
inverse = source_reflection.inverse_of
|
114
128
|
if inverse
|
115
|
-
if inverse.
|
129
|
+
if inverse.collection?
|
116
130
|
record.send(inverse.name) << build_through_record(record)
|
117
|
-
elsif inverse.
|
131
|
+
elsif inverse.has_one?
|
118
132
|
record.send("#{inverse.name}=", build_through_record(record))
|
119
133
|
end
|
120
134
|
end
|
@@ -123,7 +137,7 @@ module ActiveRecord
|
|
123
137
|
end
|
124
138
|
|
125
139
|
def target_reflection_has_associated_record?
|
126
|
-
!(through_reflection.
|
140
|
+
!(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
|
127
141
|
end
|
128
142
|
|
129
143
|
def update_through_counter?(method)
|
@@ -137,13 +151,13 @@ module ActiveRecord
|
|
137
151
|
end
|
138
152
|
end
|
139
153
|
|
154
|
+
def delete_or_nullify_all_records(method)
|
155
|
+
delete_records(load_target, method)
|
156
|
+
end
|
157
|
+
|
140
158
|
def delete_records(records, method)
|
141
159
|
ensure_not_nested
|
142
160
|
|
143
|
-
# This is unoptimised; it will load all the target records
|
144
|
-
# even when we just want to delete everything.
|
145
|
-
records = load_target if records == :all
|
146
|
-
|
147
161
|
scope = through_association.scope
|
148
162
|
scope.where! construct_join_attributes(*records)
|
149
163
|
|
@@ -152,8 +166,8 @@ module ActiveRecord
|
|
152
166
|
if scope.klass.primary_key
|
153
167
|
count = scope.destroy_all.length
|
154
168
|
else
|
155
|
-
scope.
|
156
|
-
record.
|
169
|
+
scope.each do |record|
|
170
|
+
record._run_destroy_callbacks
|
157
171
|
end
|
158
172
|
|
159
173
|
arel = scope.arel
|
@@ -177,11 +191,11 @@ module ActiveRecord
|
|
177
191
|
klass.decrement_counter counter, records.map(&:id)
|
178
192
|
end
|
179
193
|
|
180
|
-
if through_reflection.
|
194
|
+
if through_reflection.collection? && update_through_counter?(method)
|
181
195
|
update_counter(-count, through_reflection)
|
196
|
+
else
|
197
|
+
update_counter(-count)
|
182
198
|
end
|
183
|
-
|
184
|
-
update_counter(-count)
|
185
199
|
end
|
186
200
|
|
187
201
|
def through_records_for(record)
|
@@ -198,7 +212,7 @@ module ActiveRecord
|
|
198
212
|
records.each do |record|
|
199
213
|
through_records = through_records_for(record)
|
200
214
|
|
201
|
-
if through_reflection.
|
215
|
+
if through_reflection.collection?
|
202
216
|
through_records.each { |r| through_association.target.delete(r) }
|
203
217
|
else
|
204
218
|
if through_records.include?(through_association.target)
|
@@ -212,13 +226,22 @@ module ActiveRecord
|
|
212
226
|
|
213
227
|
def find_target
|
214
228
|
return [] unless target_reflection_has_associated_record?
|
215
|
-
|
229
|
+
get_records
|
216
230
|
end
|
217
231
|
|
218
232
|
# NOTE - not sure that we can actually cope with inverses here
|
219
233
|
def invertible_for?(record)
|
220
234
|
false
|
221
235
|
end
|
236
|
+
|
237
|
+
def has_cached_counter?(reflection = reflection())
|
238
|
+
owner.attribute_present?(cached_counter_attribute_name(reflection))
|
239
|
+
end
|
240
|
+
|
241
|
+
def through_reflection_updates_counter_cache?
|
242
|
+
counter_name = cached_counter_attribute_name
|
243
|
+
inverse_updates_counter_named?(counter_name, through_reflection)
|
244
|
+
end
|
222
245
|
end
|
223
246
|
end
|
224
247
|
end
|
@@ -21,8 +21,11 @@ module ActiveRecord
|
|
21
21
|
super && reflection == other.reflection
|
22
22
|
end
|
23
23
|
|
24
|
+
JoinInformation = Struct.new :joins, :binds
|
25
|
+
|
24
26
|
def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
|
25
27
|
joins = []
|
28
|
+
bind_values = []
|
26
29
|
tables = tables.reverse
|
27
30
|
|
28
31
|
scope_chain_index = 0
|
@@ -34,14 +37,9 @@ module ActiveRecord
|
|
34
37
|
table = tables.shift
|
35
38
|
klass = reflection.klass
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
foreign_key = reflection.foreign_key
|
41
|
-
else
|
42
|
-
key = reflection.foreign_key
|
43
|
-
foreign_key = reflection.active_record_primary_key
|
44
|
-
end
|
40
|
+
join_keys = reflection.join_keys(klass)
|
41
|
+
key = join_keys.key
|
42
|
+
foreign_key = join_keys.foreign_key
|
45
43
|
|
46
44
|
constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
|
47
45
|
|
@@ -54,27 +52,39 @@ module ActiveRecord
|
|
54
52
|
end
|
55
53
|
scope_chain_index += 1
|
56
54
|
|
57
|
-
|
55
|
+
klass_scope =
|
56
|
+
if klass.current_scope && klass.current_scope.values.blank?
|
57
|
+
klass.unscoped
|
58
|
+
else
|
59
|
+
klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))
|
60
|
+
end
|
61
|
+
scope_chain_items.concat [klass_scope].compact
|
58
62
|
|
59
63
|
rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
|
60
64
|
left.merge right
|
61
65
|
end
|
62
66
|
|
63
|
-
if reflection.type
|
64
|
-
constraint = constraint.and table[reflection.type].eq foreign_klass.base_class.name
|
65
|
-
end
|
66
|
-
|
67
67
|
if rel && !rel.arel.constraints.empty?
|
68
|
+
bind_values.concat rel.bind_values
|
68
69
|
constraint = constraint.and rel.arel.constraints
|
69
70
|
end
|
70
71
|
|
72
|
+
if reflection.type
|
73
|
+
value = foreign_klass.base_class.name
|
74
|
+
column = klass.columns_hash[reflection.type.to_s]
|
75
|
+
|
76
|
+
substitute = klass.connection.substitute_at(column)
|
77
|
+
bind_values.push [column, value]
|
78
|
+
constraint = constraint.and table[reflection.type].eq substitute
|
79
|
+
end
|
80
|
+
|
71
81
|
joins << table.create_join(table, table.create_on(constraint), join_type)
|
72
82
|
|
73
83
|
# The current table in this iteration becomes the foreign table in the next
|
74
84
|
foreign_table, foreign_klass = table, klass
|
75
85
|
end
|
76
86
|
|
77
|
-
joins
|
87
|
+
JoinInformation.new joins, bind_values
|
78
88
|
end
|
79
89
|
|
80
90
|
# Builds equality condition.
|
@@ -86,7 +96,7 @@ module ActiveRecord
|
|
86
96
|
# end
|
87
97
|
#
|
88
98
|
# If I execute `Physician.joins(:appointments).to_a` then
|
89
|
-
#
|
99
|
+
# klass # => Physician
|
90
100
|
# table # => #<Arel::Table @name="appointments" ...>
|
91
101
|
# key # => physician_id
|
92
102
|
# foreign_table # => #<Arel::Table @name="physicians" ...>
|
@@ -94,7 +94,7 @@ module ActiveRecord
|
|
94
94
|
#
|
95
95
|
def initialize(base, associations, joins)
|
96
96
|
@alias_tracker = AliasTracker.create(base.connection, joins)
|
97
|
-
@alias_tracker.
|
97
|
+
@alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
|
98
98
|
tree = self.class.make_tree associations
|
99
99
|
@join_root = JoinBase.new base, build(tree, base)
|
100
100
|
@join_root.children.each { |child| construct_tables! @join_root, child }
|
@@ -131,7 +131,6 @@ module ActiveRecord
|
|
131
131
|
|
132
132
|
def instantiate(result_set, aliases)
|
133
133
|
primary_key = aliases.column_alias(join_root, join_root.primary_key)
|
134
|
-
type_caster = result_set.column_type primary_key
|
135
134
|
|
136
135
|
seen = Hash.new { |h,parent_klass|
|
137
136
|
h[parent_klass] = Hash.new { |i,parent_id|
|
@@ -143,13 +142,21 @@ module ActiveRecord
|
|
143
142
|
parents = model_cache[join_root]
|
144
143
|
column_aliases = aliases.column_aliases join_root
|
145
144
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
145
|
+
message_bus = ActiveSupport::Notifications.instrumenter
|
146
|
+
|
147
|
+
payload = {
|
148
|
+
record_count: result_set.length,
|
149
|
+
class_name: join_root.base_klass.name
|
151
150
|
}
|
152
151
|
|
152
|
+
message_bus.instrument('instantiation.active_record', payload) do
|
153
|
+
result_set.each { |row_hash|
|
154
|
+
parent_key = primary_key ? row_hash[primary_key] : row_hash
|
155
|
+
parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases)
|
156
|
+
construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
153
160
|
parents.values
|
154
161
|
end
|
155
162
|
|
@@ -165,17 +172,17 @@ module ActiveRecord
|
|
165
172
|
def make_outer_joins(parent, child)
|
166
173
|
tables = table_aliases_for(parent, child)
|
167
174
|
join_type = Arel::Nodes::OuterJoin
|
168
|
-
|
175
|
+
info = make_constraints parent, child, tables, join_type
|
169
176
|
|
170
|
-
|
177
|
+
[info] + child.children.flat_map { |c| make_outer_joins(child, c) }
|
171
178
|
end
|
172
179
|
|
173
180
|
def make_inner_joins(parent, child)
|
174
181
|
tables = child.tables
|
175
182
|
join_type = Arel::Nodes::InnerJoin
|
176
|
-
|
183
|
+
info = make_constraints parent, child, tables, join_type
|
177
184
|
|
178
|
-
|
185
|
+
[info] + child.children.flat_map { |c| make_inner_joins(child, c) }
|
179
186
|
end
|
180
187
|
|
181
188
|
def table_aliases_for(parent, node)
|
@@ -216,8 +223,9 @@ module ActiveRecord
|
|
216
223
|
associations.map do |name, right|
|
217
224
|
reflection = find_reflection base_klass, name
|
218
225
|
reflection.check_validity!
|
226
|
+
reflection.check_eager_loadable!
|
219
227
|
|
220
|
-
if reflection.
|
228
|
+
if reflection.polymorphic?
|
221
229
|
raise EagerLoadPolymorphicError.new(reflection)
|
222
230
|
end
|
223
231
|
|
@@ -226,6 +234,7 @@ module ActiveRecord
|
|
226
234
|
end
|
227
235
|
|
228
236
|
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
|
237
|
+
return if ar_parent.nil?
|
229
238
|
primary_id = ar_parent.id
|
230
239
|
|
231
240
|
parent.children.each do |node|
|
@@ -242,7 +251,11 @@ module ActiveRecord
|
|
242
251
|
|
243
252
|
key = aliases.column_alias(node, node.primary_key)
|
244
253
|
id = row[key]
|
245
|
-
|
254
|
+
if id.nil?
|
255
|
+
nil_association = ar_parent.association(node.reflection.name)
|
256
|
+
nil_association.loaded!
|
257
|
+
next
|
258
|
+
end
|
246
259
|
|
247
260
|
model = seen[parent.base_klass][primary_id][node.base_klass][id]
|
248
261
|
|
@@ -104,13 +104,11 @@ module ActiveRecord
|
|
104
104
|
end
|
105
105
|
|
106
106
|
def association_key_type
|
107
|
-
|
108
|
-
column && column.type
|
107
|
+
@klass.type_for_attribute(association_key_name.to_s).type
|
109
108
|
end
|
110
109
|
|
111
110
|
def owner_key_type
|
112
|
-
|
113
|
-
column && column.type
|
111
|
+
@model.type_for_attribute(owner_key_name.to_s).type
|
114
112
|
end
|
115
113
|
|
116
114
|
def load_slices(slices)
|
@@ -134,27 +132,32 @@ module ActiveRecord
|
|
134
132
|
scope = klass.unscoped
|
135
133
|
|
136
134
|
values = reflection_scope.values
|
135
|
+
reflection_binds = reflection_scope.bind_values
|
137
136
|
preload_values = preload_scope.values
|
137
|
+
preload_binds = preload_scope.bind_values
|
138
138
|
|
139
139
|
scope.where_values = Array(values[:where]) + Array(preload_values[:where])
|
140
140
|
scope.references_values = Array(values[:references]) + Array(preload_values[:references])
|
141
|
+
scope.bind_values = (reflection_binds + preload_binds)
|
141
142
|
|
142
143
|
scope._select! preload_values[:select] || values[:select] || table[Arel.star]
|
143
144
|
scope.includes! preload_values[:includes] || values[:includes]
|
145
|
+
scope.joins! preload_values[:joins] || values[:joins]
|
146
|
+
scope.order! preload_values[:order] || values[:order]
|
144
147
|
|
145
|
-
if preload_values
|
146
|
-
scope.
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
148
|
+
if preload_values[:reordering] || values[:reordering]
|
149
|
+
scope.reordering_value = true
|
150
|
+
end
|
151
|
+
|
152
|
+
if preload_values[:readonly] || values[:readonly]
|
153
|
+
scope.readonly!
|
151
154
|
end
|
152
155
|
|
153
156
|
if options[:as]
|
154
157
|
scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
|
155
158
|
end
|
156
159
|
|
157
|
-
scope.unscope_values = Array(values[:unscope])
|
160
|
+
scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope])
|
158
161
|
klass.default_scoped.merge(scope)
|
159
162
|
end
|
160
163
|
end
|
@@ -23,7 +23,7 @@ module ActiveRecord
|
|
23
23
|
|
24
24
|
reset_association owners, through_reflection.name
|
25
25
|
|
26
|
-
middle_records = through_records.
|
26
|
+
middle_records = through_records.flat_map { |(_,rec)| rec }
|
27
27
|
|
28
28
|
preloaders = preloader.preload(middle_records,
|
29
29
|
source_reflection.name,
|
@@ -63,7 +63,7 @@ module ActiveRecord
|
|
63
63
|
should_reset = (through_scope != through_reflection.klass.unscoped) ||
|
64
64
|
(reflection.options[:source_type] && through_reflection.collection?)
|
65
65
|
|
66
|
-
#
|
66
|
+
# Don't cache the association - we would only be caching a subset
|
67
67
|
if should_reset
|
68
68
|
owners.each { |owner|
|
69
69
|
owner.association(association_name).reset
|
@@ -81,10 +81,11 @@ module ActiveRecord
|
|
81
81
|
unless reflection_scope.where_values.empty?
|
82
82
|
scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
|
83
83
|
scope.where_values = reflection_scope.values[:where]
|
84
|
+
scope.bind_values = reflection_scope.bind_values
|
84
85
|
end
|
85
86
|
|
86
87
|
scope.references! reflection_scope.values[:references]
|
87
|
-
scope.order
|
88
|
+
scope = scope.order reflection_scope.values[:order] if scope.eager_loading?
|
88
89
|
end
|
89
90
|
|
90
91
|
scope
|
@@ -2,33 +2,42 @@ module ActiveRecord
|
|
2
2
|
module Associations
|
3
3
|
# Implements the details of eager loading of Active Record associations.
|
4
4
|
#
|
5
|
-
#
|
6
|
-
# However, there are two different eager loading strategies.
|
5
|
+
# Suppose that you have the following two Active Record models:
|
7
6
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# and all of its books via a single query:
|
7
|
+
# class Author < ActiveRecord::Base
|
8
|
+
# # columns: name, age
|
9
|
+
# has_many :books
|
10
|
+
# end
|
13
11
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
12
|
+
# class Book < ActiveRecord::Base
|
13
|
+
# # columns: title, sales, author_id
|
14
|
+
# end
|
17
15
|
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# '
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
16
|
+
# When you load an author with all associated books Active Record will make
|
17
|
+
# multiple queries like this:
|
18
|
+
#
|
19
|
+
# Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a
|
20
|
+
#
|
21
|
+
# => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
|
22
|
+
# => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
|
23
|
+
#
|
24
|
+
# Active Record saves the ids of the records from the first query to use in
|
25
|
+
# the second. Depending on the number of associations involved there can be
|
26
|
+
# arbitrarily many SQL queries made.
|
27
|
+
#
|
28
|
+
# However, if there is a WHERE clause that spans across tables Active
|
29
|
+
# Record will fall back to a slightly more resource-intensive single query:
|
30
|
+
#
|
31
|
+
# Author.includes(:books).where(books: {title: 'Illiad'}).to_a
|
32
|
+
# => SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
|
33
|
+
# `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
|
34
|
+
# FROM `authors`
|
35
|
+
# LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id`
|
36
|
+
# WHERE `books`.`title` = 'Illiad'
|
37
|
+
#
|
38
|
+
# This could result in many rows that contain redundant data and it performs poorly at scale
|
39
|
+
# and is therefore only used when necessary.
|
26
40
|
#
|
27
|
-
# The second strategy is to use multiple database queries, one for each
|
28
|
-
# level of association. Since Rails 2.1, this is the default strategy. In
|
29
|
-
# situations where a table join is necessary (e.g. when the +:conditions+
|
30
|
-
# option references an association's column), it will fallback to the table
|
31
|
-
# join strategy.
|
32
41
|
class Preloader #:nodoc:
|
33
42
|
extend ActiveSupport::Autoload
|
34
43
|
|
@@ -80,7 +89,7 @@ module ActiveRecord
|
|
80
89
|
# { author: :avatar }
|
81
90
|
# [ :books, { author: :avatar } ]
|
82
91
|
|
83
|
-
NULL_RELATION = Struct.new(:values).new({})
|
92
|
+
NULL_RELATION = Struct.new(:values, :bind_values).new({}, [])
|
84
93
|
|
85
94
|
def preload(records, associations, preload_scope = nil)
|
86
95
|
records = Array.wrap(records).compact.uniq
|
@@ -151,7 +160,7 @@ module ActiveRecord
|
|
151
160
|
h
|
152
161
|
end
|
153
162
|
|
154
|
-
class AlreadyLoaded
|
163
|
+
class AlreadyLoaded # :nodoc:
|
155
164
|
attr_reader :owners, :reflection
|
156
165
|
|
157
166
|
def initialize(klass, owners, reflection, preload_scope)
|
@@ -166,7 +175,7 @@ module ActiveRecord
|
|
166
175
|
end
|
167
176
|
end
|
168
177
|
|
169
|
-
class NullPreloader
|
178
|
+
class NullPreloader # :nodoc:
|
170
179
|
def self.new(klass, owners, reflection, preload_scope); self; end
|
171
180
|
def self.run(preloader); end
|
172
181
|
def self.preloaded_records; []; end
|
@@ -178,6 +187,7 @@ module ActiveRecord
|
|
178
187
|
if owners.first.association(reflection.name).loaded?
|
179
188
|
return AlreadyLoaded
|
180
189
|
end
|
190
|
+
reflection.check_preloadable!
|
181
191
|
|
182
192
|
case reflection.macro
|
183
193
|
when :has_many
|
@@ -3,7 +3,7 @@ module ActiveRecord
|
|
3
3
|
class SingularAssociation < Association #:nodoc:
|
4
4
|
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
|
5
5
|
def reader(force_reload = false)
|
6
|
-
if force_reload
|
6
|
+
if force_reload && klass
|
7
7
|
klass.uncached { reload }
|
8
8
|
elsif !loaded? || stale_target?
|
9
9
|
reload
|
@@ -38,8 +38,23 @@ module ActiveRecord
|
|
38
38
|
scope.scope_for_create.stringify_keys.except(klass.primary_key)
|
39
39
|
end
|
40
40
|
|
41
|
+
def get_records
|
42
|
+
return scope.limit(1).to_a if skip_statement_cache?
|
43
|
+
|
44
|
+
conn = klass.connection
|
45
|
+
sc = reflection.association_scope_cache(conn, owner) do
|
46
|
+
StatementCache.create(conn) { |params|
|
47
|
+
as = AssociationScope.create { params.bind }
|
48
|
+
target_scope.merge(as.scope(self, conn)).limit(1)
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
53
|
+
sc.execute binds, klass, klass.connection
|
54
|
+
end
|
55
|
+
|
41
56
|
def find_target
|
42
|
-
if record =
|
57
|
+
if record = get_records.first
|
43
58
|
set_inverse_instance record
|
44
59
|
end
|
45
60
|
end
|
@@ -3,7 +3,7 @@ module ActiveRecord
|
|
3
3
|
module Associations
|
4
4
|
module ThroughAssociation #:nodoc:
|
5
5
|
|
6
|
-
delegate :source_reflection, :through_reflection, :
|
6
|
+
delegate :source_reflection, :through_reflection, :to => :reflection
|
7
7
|
|
8
8
|
protected
|
9
9
|
|
@@ -13,14 +13,8 @@ module ActiveRecord
|
|
13
13
|
# 2. To get the type conditions for any STI models in the chain
|
14
14
|
def target_scope
|
15
15
|
scope = super
|
16
|
-
chain.drop(1).each do |reflection|
|
16
|
+
reflection.chain.drop(1).each do |reflection|
|
17
17
|
relation = reflection.klass.all
|
18
|
-
|
19
|
-
reflection_scope = reflection.scope
|
20
|
-
if reflection_scope && reflection_scope.arity.zero?
|
21
|
-
relation.merge!(reflection_scope)
|
22
|
-
end
|
23
|
-
|
24
18
|
scope.merge!(
|
25
19
|
relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
|
26
20
|
)
|
@@ -71,18 +65,17 @@ module ActiveRecord
|
|
71
65
|
# Note: this does not capture all cases, for example it would be crazy to try to
|
72
66
|
# properly support stale-checking for nested associations.
|
73
67
|
def stale_state
|
74
|
-
if through_reflection.
|
68
|
+
if through_reflection.belongs_to?
|
75
69
|
owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
|
76
70
|
end
|
77
71
|
end
|
78
72
|
|
79
73
|
def foreign_key_present?
|
80
|
-
through_reflection.
|
81
|
-
!owner[through_reflection.foreign_key].nil?
|
74
|
+
through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil?
|
82
75
|
end
|
83
76
|
|
84
77
|
def ensure_mutable
|
85
|
-
|
78
|
+
unless source_reflection.belongs_to?
|
86
79
|
raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
|
87
80
|
end
|
88
81
|
end
|