activerecord 4.1.0 → 4.2.0
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 +776 -1330
- data/README.rdoc +15 -10
- data/lib/active_record/aggregations.rb +12 -8
- data/lib/active_record/association_relation.rb +4 -0
- data/lib/active_record/associations/alias_tracker.rb +14 -13
- data/lib/active_record/associations/association.rb +2 -2
- data/lib/active_record/associations/association_scope.rb +83 -43
- data/lib/active_record/associations/belongs_to_association.rb +15 -5
- 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/has_and_belongs_to_many.rb +9 -6
- 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 +66 -29
- data/lib/active_record/associations/collection_proxy.rb +22 -26
- data/lib/active_record/associations/has_many_association.rb +65 -18
- data/lib/active_record/associations/has_many_through_association.rb +55 -27
- data/lib/active_record/associations/has_one_association.rb +0 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +19 -15
- data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
- data/lib/active_record/associations/join_dependency.rb +20 -12
- data/lib/active_record/associations/preloader/association.rb +34 -11
- data/lib/active_record/associations/preloader/through_association.rb +4 -3
- data/lib/active_record/associations/preloader.rb +49 -59
- data/lib/active_record/associations/singular_association.rb +25 -4
- data/lib/active_record/associations/through_association.rb +23 -14
- data/lib/active_record/associations.rb +171 -42
- data/lib/active_record/attribute.rb +149 -0
- data/lib/active_record/attribute_assignment.rb +18 -10
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
- data/lib/active_record/attribute_methods/dirty.rb +98 -44
- data/lib/active_record/attribute_methods/primary_key.rb +14 -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 +37 -147
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +34 -28
- data/lib/active_record/attribute_methods/write.rb +14 -21
- data/lib/active_record/attribute_methods.rb +67 -94
- data/lib/active_record/attribute_set/builder.rb +86 -0
- data/lib/active_record/attribute_set.rb +77 -0
- data/lib/active_record/attributes.rb +139 -0
- data/lib/active_record/autosave_association.rb +45 -38
- data/lib/active_record/base.rb +10 -20
- data/lib/active_record/callbacks.rb +7 -7
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +78 -52
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +38 -59
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -55
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +126 -54
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +198 -64
- data/lib/active_record/connection_adapters/abstract/transaction.rb +126 -114
- data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -55
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +240 -135
- data/lib/active_record/connection_adapters/column.rb +28 -239
- data/lib/active_record/connection_adapters/connection_specification.rb +16 -25
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -22
- data/lib/active_record/connection_adapters/mysql_adapter.rb +65 -149
- 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 +39 -27
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -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 +14 -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 +27 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -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 +15 -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 +97 -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 -374
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +55 -135
- 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 +127 -38
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +220 -466
- data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -61
- data/lib/active_record/connection_handling.rb +3 -3
- data/lib/active_record/core.rb +143 -32
- data/lib/active_record/counter_cache.rb +60 -7
- data/lib/active_record/enum.rb +10 -11
- data/lib/active_record/errors.rb +49 -27
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/fixtures.rb +56 -70
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +35 -10
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/locking/optimistic.rb +35 -17
- data/lib/active_record/log_subscriber.rb +1 -1
- 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 +52 -49
- data/lib/active_record/model_schema.rb +49 -57
- data/lib/active_record/nested_attributes.rb +7 -7
- data/lib/active_record/null_relation.rb +19 -5
- data/lib/active_record/persistence.rb +50 -31
- data/lib/active_record/query_cache.rb +3 -3
- data/lib/active_record/querying.rb +10 -7
- data/lib/active_record/railtie.rb +14 -11
- data/lib/active_record/railties/databases.rake +56 -54
- data/lib/active_record/readonly_attributes.rb +0 -1
- data/lib/active_record/reflection.rb +286 -102
- data/lib/active_record/relation/batches.rb +0 -1
- data/lib/active_record/relation/calculations.rb +39 -31
- data/lib/active_record/relation/delegation.rb +2 -2
- data/lib/active_record/relation/finder_methods.rb +80 -36
- data/lib/active_record/relation/merger.rb +25 -30
- data/lib/active_record/relation/predicate_builder/array_handler.rb +31 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +11 -10
- data/lib/active_record/relation/query_methods.rb +141 -55
- data/lib/active_record/relation/spawn_methods.rb +3 -0
- data/lib/active_record/relation.rb +69 -30
- 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 +58 -26
- data/lib/active_record/schema_migration.rb +11 -0
- data/lib/active_record/scoping/default.rb +8 -7
- 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 +19 -10
- data/lib/active_record/tasks/database_tasks.rb +73 -7
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +11 -9
- data/lib/active_record/transactions.rb +37 -21
- 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 +30 -0
- data/lib/active_record/type/date.rb +46 -0
- data/lib/active_record/type/date_time.rb +43 -0
- data/lib/active_record/type/decimal.rb +40 -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 +17 -0
- data/lib/active_record/type/integer.rb +55 -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 +56 -0
- data/lib/active_record/type/string.rb +36 -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 +101 -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 +6 -4
- data/lib/active_record/validations/uniqueness.rb +11 -17
- data/lib/active_record/validations.rb +25 -19
- data/lib/active_record.rb +3 -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 +4 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +6 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
- metadata +65 -10
- data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -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,33 @@ module ActiveRecord
|
|
54
52
|
end
|
55
53
|
scope_chain_index += 1
|
56
54
|
|
57
|
-
scope_chain_items.concat [klass.send(:build_default_scope)].compact
|
55
|
+
scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact
|
58
56
|
|
59
57
|
rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
|
60
58
|
left.merge right
|
61
59
|
end
|
62
60
|
|
63
|
-
if reflection.type
|
64
|
-
constraint = constraint.and table[reflection.type].eq foreign_klass.base_class.name
|
65
|
-
end
|
66
|
-
|
67
61
|
if rel && !rel.arel.constraints.empty?
|
62
|
+
bind_values.concat rel.bind_values
|
68
63
|
constraint = constraint.and rel.arel.constraints
|
69
64
|
end
|
70
65
|
|
66
|
+
if reflection.type
|
67
|
+
value = foreign_klass.base_class.name
|
68
|
+
column = klass.columns_hash[reflection.type.to_s]
|
69
|
+
|
70
|
+
substitute = klass.connection.substitute_at(column)
|
71
|
+
bind_values.push [column, value]
|
72
|
+
constraint = constraint.and table[reflection.type].eq substitute
|
73
|
+
end
|
74
|
+
|
71
75
|
joins << table.create_join(table, table.create_on(constraint), join_type)
|
72
76
|
|
73
77
|
# The current table in this iteration becomes the foreign table in the next
|
74
78
|
foreign_table, foreign_klass = table, klass
|
75
79
|
end
|
76
80
|
|
77
|
-
joins
|
81
|
+
JoinInformation.new joins, bind_values
|
78
82
|
end
|
79
83
|
|
80
84
|
# Builds equality condition.
|
@@ -86,7 +90,7 @@ module ActiveRecord
|
|
86
90
|
# end
|
87
91
|
#
|
88
92
|
# If I execute `Physician.joins(:appointments).to_a` then
|
89
|
-
#
|
93
|
+
# klass # => Physician
|
90
94
|
# table # => #<Arel::Table @name="appointments" ...>
|
91
95
|
# key # => physician_id
|
92
96
|
# 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,12 +142,20 @@ module ActiveRecord
|
|
143
142
|
parents = model_cache[join_root]
|
144
143
|
column_aliases = aliases.column_aliases join_root
|
145
144
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
145
|
+
message_bus = ActiveSupport::Notifications.instrumenter
|
146
|
+
|
147
|
+
payload = {
|
148
|
+
record_count: result_set.length,
|
149
|
+
class_name: join_root.base_klass.name
|
150
150
|
}
|
151
151
|
|
152
|
+
message_bus.instrument('instantiation.active_record', payload) do
|
153
|
+
result_set.each { |row_hash|
|
154
|
+
parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases)
|
155
|
+
construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
152
159
|
parents.values
|
153
160
|
end
|
154
161
|
|
@@ -164,17 +171,17 @@ module ActiveRecord
|
|
164
171
|
def make_outer_joins(parent, child)
|
165
172
|
tables = table_aliases_for(parent, child)
|
166
173
|
join_type = Arel::Nodes::OuterJoin
|
167
|
-
|
174
|
+
info = make_constraints parent, child, tables, join_type
|
168
175
|
|
169
|
-
|
176
|
+
[info] + child.children.flat_map { |c| make_outer_joins(child, c) }
|
170
177
|
end
|
171
178
|
|
172
179
|
def make_inner_joins(parent, child)
|
173
180
|
tables = child.tables
|
174
181
|
join_type = Arel::Nodes::InnerJoin
|
175
|
-
|
182
|
+
info = make_constraints parent, child, tables, join_type
|
176
183
|
|
177
|
-
|
184
|
+
[info] + child.children.flat_map { |c| make_inner_joins(child, c) }
|
178
185
|
end
|
179
186
|
|
180
187
|
def table_aliases_for(parent, node)
|
@@ -207,7 +214,7 @@ module ActiveRecord
|
|
207
214
|
end
|
208
215
|
|
209
216
|
def find_reflection(klass, name)
|
210
|
-
klass.
|
217
|
+
klass._reflect_on_association(name) or
|
211
218
|
raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
|
212
219
|
end
|
213
220
|
|
@@ -215,8 +222,9 @@ module ActiveRecord
|
|
215
222
|
associations.map do |name, right|
|
216
223
|
reflection = find_reflection base_klass, name
|
217
224
|
reflection.check_validity!
|
225
|
+
reflection.check_eager_loadable!
|
218
226
|
|
219
|
-
if reflection.
|
227
|
+
if reflection.polymorphic?
|
220
228
|
raise EagerLoadPolymorphicError.new(reflection)
|
221
229
|
end
|
222
230
|
|
@@ -57,9 +57,15 @@ module ActiveRecord
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def owners_by_key
|
60
|
-
@owners_by_key ||=
|
61
|
-
|
62
|
-
|
60
|
+
@owners_by_key ||= if key_conversion_required?
|
61
|
+
owners.group_by do |owner|
|
62
|
+
owner[owner_key_name].to_s
|
63
|
+
end
|
64
|
+
else
|
65
|
+
owners.group_by do |owner|
|
66
|
+
owner[owner_key_name]
|
67
|
+
end
|
68
|
+
end
|
63
69
|
end
|
64
70
|
|
65
71
|
def options
|
@@ -93,13 +99,28 @@ module ActiveRecord
|
|
93
99
|
records_by_owner
|
94
100
|
end
|
95
101
|
|
102
|
+
def key_conversion_required?
|
103
|
+
association_key_type != owner_key_type
|
104
|
+
end
|
105
|
+
|
106
|
+
def association_key_type
|
107
|
+
@klass.type_for_attribute(association_key_name.to_s).type
|
108
|
+
end
|
109
|
+
|
110
|
+
def owner_key_type
|
111
|
+
@model.type_for_attribute(owner_key_name.to_s).type
|
112
|
+
end
|
113
|
+
|
96
114
|
def load_slices(slices)
|
97
115
|
@preloaded_records = slices.flat_map { |slice|
|
98
116
|
records_for(slice)
|
99
117
|
}
|
100
118
|
|
101
119
|
@preloaded_records.map { |record|
|
102
|
-
|
120
|
+
key = record[association_key_name]
|
121
|
+
key = key.to_s if key_conversion_required?
|
122
|
+
|
123
|
+
[record, key]
|
103
124
|
}
|
104
125
|
end
|
105
126
|
|
@@ -111,26 +132,28 @@ module ActiveRecord
|
|
111
132
|
scope = klass.unscoped
|
112
133
|
|
113
134
|
values = reflection_scope.values
|
135
|
+
reflection_binds = reflection_scope.bind_values
|
114
136
|
preload_values = preload_scope.values
|
137
|
+
preload_binds = preload_scope.bind_values
|
115
138
|
|
116
139
|
scope.where_values = Array(values[:where]) + Array(preload_values[:where])
|
117
140
|
scope.references_values = Array(values[:references]) + Array(preload_values[:references])
|
141
|
+
scope.bind_values = (reflection_binds + preload_binds)
|
118
142
|
|
119
|
-
scope.
|
143
|
+
scope._select! preload_values[:select] || values[:select] || table[Arel.star]
|
120
144
|
scope.includes! preload_values[:includes] || values[:includes]
|
145
|
+
scope.joins! preload_values[:joins] || values[:joins]
|
146
|
+
scope.order! preload_values[:order] || values[:order]
|
121
147
|
|
122
|
-
if preload_values
|
123
|
-
scope.
|
124
|
-
else
|
125
|
-
if values.key? :order
|
126
|
-
scope.order! values[:order]
|
127
|
-
end
|
148
|
+
if preload_values[:readonly] || values[:readonly]
|
149
|
+
scope.readonly!
|
128
150
|
end
|
129
151
|
|
130
152
|
if options[:as]
|
131
153
|
scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
|
132
154
|
end
|
133
155
|
|
156
|
+
scope.unscope_values = Array(values[:unscope])
|
134
157
|
klass.default_scoped.merge(scope)
|
135
158
|
end
|
136
159
|
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
|
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
|
@@ -112,13 +121,14 @@ module ActiveRecord
|
|
112
121
|
end
|
113
122
|
|
114
123
|
def preloaders_for_hash(association, records, scope)
|
115
|
-
|
116
|
-
|
117
|
-
loaders = preloaders_for_one parent, records, scope
|
124
|
+
association.flat_map { |parent, child|
|
125
|
+
loaders = preloaders_for_one parent, records, scope
|
118
126
|
|
119
|
-
|
120
|
-
|
121
|
-
|
127
|
+
recs = loaders.flat_map(&:preloaded_records).uniq
|
128
|
+
loaders.concat Array.wrap(child).flat_map { |assoc|
|
129
|
+
preloaders_on assoc, recs, scope
|
130
|
+
}
|
131
|
+
loaders
|
122
132
|
}
|
123
133
|
end
|
124
134
|
|
@@ -140,36 +150,14 @@ module ActiveRecord
|
|
140
150
|
end
|
141
151
|
|
142
152
|
def grouped_records(association, records)
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def records_by_reflection(association, records)
|
153
|
-
records.group_by do |record|
|
154
|
-
reflection = record.class.reflect_on_association(association)
|
155
|
-
|
156
|
-
reflection || raise_config_error(record, association)
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
def raise_config_error(record, association)
|
161
|
-
raise ActiveRecord::ConfigurationError,
|
162
|
-
"Association named '#{association}' was not found on #{record.class.name}; " \
|
163
|
-
"perhaps you misspelled it?"
|
164
|
-
end
|
165
|
-
|
166
|
-
def association_klass(reflection, record)
|
167
|
-
if reflection.macro == :belongs_to && reflection.options[:polymorphic]
|
168
|
-
klass = record.read_attribute(reflection.foreign_type.to_s)
|
169
|
-
klass && klass.constantize
|
170
|
-
else
|
171
|
-
reflection.klass
|
153
|
+
h = {}
|
154
|
+
records.each do |record|
|
155
|
+
next unless record
|
156
|
+
assoc = record.association(association)
|
157
|
+
klasses = h[assoc.reflection] ||= {}
|
158
|
+
(klasses[assoc.klass] ||= []) << record
|
172
159
|
end
|
160
|
+
h
|
173
161
|
end
|
174
162
|
|
175
163
|
class AlreadyLoaded
|
@@ -190,6 +178,7 @@ module ActiveRecord
|
|
190
178
|
class NullPreloader
|
191
179
|
def self.new(klass, owners, reflection, preload_scope); self; end
|
192
180
|
def self.run(preloader); end
|
181
|
+
def self.preloaded_records; []; end
|
193
182
|
end
|
194
183
|
|
195
184
|
def preloader_for(reflection, owners, rhs_klass)
|
@@ -198,6 +187,7 @@ module ActiveRecord
|
|
198
187
|
if owners.first.association(reflection.name).loaded?
|
199
188
|
return AlreadyLoaded
|
200
189
|
end
|
190
|
+
reflection.check_preloadable!
|
201
191
|
|
202
192
|
case reflection.macro
|
203
193
|
when :has_many
|
@@ -18,11 +18,11 @@ module ActiveRecord
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def create(attributes = {}, &block)
|
21
|
-
|
21
|
+
_create_record(attributes, &block)
|
22
22
|
end
|
23
23
|
|
24
24
|
def create!(attributes = {}, &block)
|
25
|
-
|
25
|
+
_create_record(attributes, true, &block)
|
26
26
|
end
|
27
27
|
|
28
28
|
def build(attributes = {})
|
@@ -38,8 +38,29 @@ module ActiveRecord
|
|
38
38
|
scope.scope_for_create.stringify_keys.except(klass.primary_key)
|
39
39
|
end
|
40
40
|
|
41
|
+
def get_records
|
42
|
+
if reflection.scope_chain.any?(&:any?) ||
|
43
|
+
scope.eager_loading? ||
|
44
|
+
klass.current_scope ||
|
45
|
+
klass.default_scopes.any?
|
46
|
+
|
47
|
+
return scope.limit(1).to_a
|
48
|
+
end
|
49
|
+
|
50
|
+
conn = klass.connection
|
51
|
+
sc = reflection.association_scope_cache(conn, owner) do
|
52
|
+
StatementCache.create(conn) { |params|
|
53
|
+
as = AssociationScope.create { params.bind }
|
54
|
+
target_scope.merge(as.scope(self, conn)).limit(1)
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
59
|
+
sc.execute binds, klass, klass.connection
|
60
|
+
end
|
61
|
+
|
41
62
|
def find_target
|
42
|
-
if record =
|
63
|
+
if record = get_records.first
|
43
64
|
set_inverse_instance record
|
44
65
|
end
|
45
66
|
end
|
@@ -52,7 +73,7 @@ module ActiveRecord
|
|
52
73
|
replace(record)
|
53
74
|
end
|
54
75
|
|
55
|
-
def
|
76
|
+
def _create_record(attributes, raise_error = false)
|
56
77
|
record = build_record(attributes)
|
57
78
|
yield(record) if block_given?
|
58
79
|
saved = record.save
|
@@ -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,10 +13,16 @@ 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
|
+
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
|
+
|
17
24
|
scope.merge!(
|
18
|
-
|
19
|
-
except(:select, :create_with, :includes, :preload, :joins, :eager_load)
|
25
|
+
relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
|
20
26
|
)
|
21
27
|
end
|
22
28
|
scope
|
@@ -39,12 +45,16 @@ module ActiveRecord
|
|
39
45
|
def construct_join_attributes(*records)
|
40
46
|
ensure_mutable
|
41
47
|
|
42
|
-
|
43
|
-
source_reflection.
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
+
if source_reflection.association_primary_key(reflection.klass) == reflection.klass.primary_key
|
49
|
+
join_attributes = { source_reflection.name => records }
|
50
|
+
else
|
51
|
+
join_attributes = {
|
52
|
+
source_reflection.foreign_key =>
|
53
|
+
records.map { |record|
|
54
|
+
record.send(source_reflection.association_primary_key(reflection.klass))
|
55
|
+
}
|
56
|
+
}
|
57
|
+
end
|
48
58
|
|
49
59
|
if options[:source_type]
|
50
60
|
join_attributes[source_reflection.foreign_type] =
|
@@ -61,18 +71,17 @@ module ActiveRecord
|
|
61
71
|
# Note: this does not capture all cases, for example it would be crazy to try to
|
62
72
|
# properly support stale-checking for nested associations.
|
63
73
|
def stale_state
|
64
|
-
if through_reflection.
|
74
|
+
if through_reflection.belongs_to?
|
65
75
|
owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
|
66
76
|
end
|
67
77
|
end
|
68
78
|
|
69
79
|
def foreign_key_present?
|
70
|
-
through_reflection.
|
71
|
-
!owner[through_reflection.foreign_key].nil?
|
80
|
+
through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil?
|
72
81
|
end
|
73
82
|
|
74
83
|
def ensure_mutable
|
75
|
-
|
84
|
+
unless source_reflection.belongs_to?
|
76
85
|
raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
|
77
86
|
end
|
78
87
|
end
|