activerecord 4.0.4 → 4.1.16
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 +1632 -1797
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/examples/performance.rb +30 -18
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +2 -1
- data/lib/active_record/association_relation.rb +4 -0
- data/lib/active_record/associations/alias_tracker.rb +49 -29
- data/lib/active_record/associations/association.rb +9 -17
- data/lib/active_record/associations/association_scope.rb +59 -49
- data/lib/active_record/associations/belongs_to_association.rb +34 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +6 -1
- data/lib/active_record/associations/builder/association.rb +84 -54
- data/lib/active_record/associations/builder/belongs_to.rb +90 -58
- data/lib/active_record/associations/builder/collection_association.rb +47 -45
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +119 -25
- data/lib/active_record/associations/builder/has_many.rb +3 -3
- data/lib/active_record/associations/builder/has_one.rb +5 -7
- data/lib/active_record/associations/builder/singular_association.rb +6 -7
- data/lib/active_record/associations/collection_association.rb +121 -111
- data/lib/active_record/associations/collection_proxy.rb +73 -18
- data/lib/active_record/associations/has_many_association.rb +14 -11
- data/lib/active_record/associations/has_many_through_association.rb +33 -6
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +46 -104
- data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
- data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
- data/lib/active_record/associations/join_dependency.rb +208 -168
- data/lib/active_record/associations/preloader/association.rb +69 -27
- data/lib/active_record/associations/preloader/collection_association.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +58 -26
- data/lib/active_record/associations/preloader.rb +63 -49
- data/lib/active_record/associations/singular_association.rb +6 -5
- data/lib/active_record/associations/through_association.rb +30 -9
- data/lib/active_record/associations.rb +116 -42
- data/lib/active_record/attribute_assignment.rb +6 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
- data/lib/active_record/attribute_methods/dirty.rb +35 -26
- data/lib/active_record/attribute_methods/primary_key.rb +8 -1
- data/lib/active_record/attribute_methods/read.rb +56 -29
- data/lib/active_record/attribute_methods/serialization.rb +44 -12
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +13 -1
- data/lib/active_record/attribute_methods/write.rb +59 -26
- data/lib/active_record/attribute_methods.rb +82 -43
- data/lib/active_record/autosave_association.rb +209 -194
- data/lib/active_record/base.rb +6 -2
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +5 -10
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -24
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -13
- data/lib/active_record/connection_adapters/abstract/quoting.rb +6 -3
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +90 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +45 -70
- data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -96
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +74 -66
- data/lib/active_record/connection_adapters/column.rb +1 -35
- data/lib/active_record/connection_adapters/connection_specification.rb +231 -43
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -5
- data/lib/active_record/connection_adapters/mysql_adapter.rb +24 -17
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +22 -15
- data/lib/active_record/connection_adapters/postgresql/cast.rb +12 -4
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -44
- data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -14
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +37 -12
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +20 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +98 -52
- data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -60
- data/lib/active_record/connection_handling.rb +39 -5
- data/lib/active_record/core.rb +38 -54
- data/lib/active_record/counter_cache.rb +9 -10
- data/lib/active_record/dynamic_matchers.rb +6 -2
- data/lib/active_record/enum.rb +199 -0
- data/lib/active_record/errors.rb +22 -5
- data/lib/active_record/fixture_set/file.rb +2 -1
- data/lib/active_record/fixtures.rb +173 -76
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +23 -9
- data/lib/active_record/integration.rb +54 -1
- data/lib/active_record/locking/optimistic.rb +7 -2
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +6 -13
- data/lib/active_record/migration/command_recorder.rb +8 -2
- data/lib/active_record/migration.rb +91 -56
- data/lib/active_record/model_schema.rb +7 -14
- data/lib/active_record/nested_attributes.rb +25 -13
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +26 -6
- data/lib/active_record/persistence.rb +23 -29
- data/lib/active_record/querying.rb +15 -12
- data/lib/active_record/railtie.rb +12 -61
- data/lib/active_record/railties/databases.rake +37 -56
- data/lib/active_record/readonly_attributes.rb +0 -6
- data/lib/active_record/reflection.rb +230 -79
- data/lib/active_record/relation/batches.rb +74 -24
- data/lib/active_record/relation/calculations.rb +52 -48
- data/lib/active_record/relation/delegation.rb +54 -39
- data/lib/active_record/relation/finder_methods.rb +210 -67
- data/lib/active_record/relation/merger.rb +15 -12
- data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder.rb +81 -40
- data/lib/active_record/relation/query_methods.rb +185 -108
- data/lib/active_record/relation/spawn_methods.rb +8 -5
- data/lib/active_record/relation.rb +79 -84
- data/lib/active_record/result.rb +45 -6
- data/lib/active_record/runtime_registry.rb +5 -0
- data/lib/active_record/sanitization.rb +4 -4
- data/lib/active_record/schema_dumper.rb +18 -6
- data/lib/active_record/schema_migration.rb +31 -18
- data/lib/active_record/scoping/default.rb +5 -18
- data/lib/active_record/scoping/named.rb +14 -29
- data/lib/active_record/scoping.rb +5 -0
- data/lib/active_record/store.rb +67 -18
- data/lib/active_record/tasks/database_tasks.rb +66 -26
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -10
- 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 +6 -6
- data/lib/active_record/transactions.rb +10 -12
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +19 -9
- data/lib/active_record/version.rb +4 -7
- data/lib/active_record.rb +5 -7
- data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
- data/lib/rails/generators/active_record.rb +2 -8
- metadata +18 -30
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
- data/lib/active_record/associations/join_helper.rb +0 -45
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
- data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
- data/lib/active_record/test_case.rb +0 -96
@@ -1,15 +1,79 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Associations
|
3
3
|
class JoinDependency # :nodoc:
|
4
|
-
autoload :JoinPart, 'active_record/associations/join_dependency/join_part'
|
5
4
|
autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
|
6
5
|
autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
|
7
6
|
|
8
|
-
|
7
|
+
class Aliases # :nodoc:
|
8
|
+
def initialize(tables)
|
9
|
+
@tables = tables
|
10
|
+
@alias_cache = tables.each_with_object({}) { |table,h|
|
11
|
+
h[table.node] = table.columns.each_with_object({}) { |column,i|
|
12
|
+
i[column.name] = column.alias
|
13
|
+
}
|
14
|
+
}
|
15
|
+
@name_and_alias_cache = tables.each_with_object({}) { |table,h|
|
16
|
+
h[table.node] = table.columns.map { |column|
|
17
|
+
[column.name, column.alias]
|
18
|
+
}
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def columns
|
23
|
+
@tables.flat_map { |t| t.column_aliases }
|
24
|
+
end
|
25
|
+
|
26
|
+
# An array of [column_name, alias] pairs for the table
|
27
|
+
def column_aliases(node)
|
28
|
+
@name_and_alias_cache[node]
|
29
|
+
end
|
30
|
+
|
31
|
+
def column_alias(node, column)
|
32
|
+
@alias_cache[node][column]
|
33
|
+
end
|
34
|
+
|
35
|
+
class Table < Struct.new(:node, :columns)
|
36
|
+
def table
|
37
|
+
Arel::Nodes::TableAlias.new node.table, node.aliased_table_name
|
38
|
+
end
|
39
|
+
|
40
|
+
def column_aliases
|
41
|
+
t = table
|
42
|
+
columns.map { |column| t[column.name].as Arel.sql column.alias }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
Column = Struct.new(:name, :alias)
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :alias_tracker, :base_klass, :join_root
|
49
|
+
|
50
|
+
def self.make_tree(associations)
|
51
|
+
hash = {}
|
52
|
+
walk_tree associations, hash
|
53
|
+
hash
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.walk_tree(associations, hash)
|
57
|
+
case associations
|
58
|
+
when Symbol, String
|
59
|
+
hash[associations.to_sym] ||= {}
|
60
|
+
when Array
|
61
|
+
associations.each do |assoc|
|
62
|
+
walk_tree assoc, hash
|
63
|
+
end
|
64
|
+
when Hash
|
65
|
+
associations.each do |k,v|
|
66
|
+
cache = hash[k] ||= {}
|
67
|
+
walk_tree v, cache
|
68
|
+
end
|
69
|
+
else
|
70
|
+
raise ConfigurationError, associations.inspect
|
71
|
+
end
|
72
|
+
end
|
9
73
|
|
10
74
|
# base is the base class on which operation is taking place.
|
11
75
|
# associations is the list of associations which are joined using hash, symbol or array.
|
12
|
-
# joins is the list of all string join
|
76
|
+
# joins is the list of all string join commands and arel nodes.
|
13
77
|
#
|
14
78
|
# Example :
|
15
79
|
#
|
@@ -19,216 +83,192 @@ module ActiveRecord
|
|
19
83
|
# end
|
20
84
|
#
|
21
85
|
# If I execute `@physician.patients.to_a` then
|
22
|
-
# base
|
23
|
-
# associations
|
24
|
-
# joins
|
86
|
+
# base # => Physician
|
87
|
+
# associations # => []
|
88
|
+
# joins # => [#<Arel::Nodes::InnerJoin: ...]
|
25
89
|
#
|
26
90
|
# However if I execute `Physician.joins(:appointments).to_a` then
|
27
|
-
# base
|
28
|
-
# associations
|
29
|
-
# joins
|
91
|
+
# base # => Physician
|
92
|
+
# associations # => [:appointments]
|
93
|
+
# joins # => []
|
30
94
|
#
|
31
95
|
def initialize(base, associations, joins)
|
32
|
-
@
|
33
|
-
@
|
34
|
-
|
35
|
-
@
|
36
|
-
@
|
37
|
-
@alias_tracker = AliasTracker.new(base.connection, joins)
|
38
|
-
@alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
|
39
|
-
build(associations)
|
40
|
-
end
|
41
|
-
|
42
|
-
def graft(*associations)
|
43
|
-
associations.each do |association|
|
44
|
-
join_associations.detect {|a| association == a} ||
|
45
|
-
build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
|
46
|
-
end
|
47
|
-
self
|
96
|
+
@alias_tracker = AliasTracker.create(base.connection, joins)
|
97
|
+
@alias_tracker.aliased_name_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
|
98
|
+
tree = self.class.make_tree associations
|
99
|
+
@join_root = JoinBase.new base, build(tree, base)
|
100
|
+
@join_root.children.each { |child| construct_tables! @join_root, child }
|
48
101
|
end
|
49
102
|
|
50
|
-
def
|
51
|
-
|
103
|
+
def reflections
|
104
|
+
join_root.drop(1).map!(&:reflection)
|
52
105
|
end
|
53
106
|
|
54
|
-
def
|
55
|
-
|
107
|
+
def join_constraints(outer_joins)
|
108
|
+
joins = join_root.children.flat_map { |child|
|
109
|
+
make_inner_joins join_root, child
|
110
|
+
}
|
111
|
+
|
112
|
+
joins.concat outer_joins.flat_map { |oj|
|
113
|
+
if join_root.match? oj.join_root
|
114
|
+
walk join_root, oj.join_root
|
115
|
+
else
|
116
|
+
oj.join_root.children.flat_map { |child|
|
117
|
+
make_outer_joins oj.join_root, child
|
118
|
+
}
|
119
|
+
end
|
120
|
+
}
|
56
121
|
end
|
57
122
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
table[column_name].as Arel.sql(aliased_name)
|
123
|
+
def aliases
|
124
|
+
Aliases.new join_root.each_with_index.map { |join_part,i|
|
125
|
+
columns = join_part.column_names.each_with_index.map { |column_name,j|
|
126
|
+
Aliases::Column.new column_name, "t#{i}_r#{j}"
|
63
127
|
}
|
64
|
-
|
128
|
+
Aliases::Table.new(join_part, columns)
|
129
|
+
}
|
65
130
|
end
|
66
131
|
|
67
|
-
def instantiate(
|
68
|
-
primary_key =
|
69
|
-
|
70
|
-
|
71
|
-
records = rows.map { |model|
|
72
|
-
primary_id = model[primary_key]
|
73
|
-
parent = parents[primary_id] ||= join_base.instantiate(model)
|
74
|
-
construct(parent, @associations, join_associations, model)
|
75
|
-
parent
|
76
|
-
}.uniq
|
132
|
+
def instantiate(result_set, aliases)
|
133
|
+
primary_key = aliases.column_alias(join_root, join_root.primary_key)
|
134
|
+
type_caster = result_set.column_type primary_key
|
77
135
|
|
78
|
-
|
79
|
-
|
80
|
-
|
136
|
+
seen = Hash.new { |h,parent_klass|
|
137
|
+
h[parent_klass] = Hash.new { |i,parent_id|
|
138
|
+
i[parent_id] = Hash.new { |j,child_klass| j[child_klass] = {} }
|
139
|
+
}
|
140
|
+
}
|
81
141
|
|
82
|
-
|
142
|
+
model_cache = Hash.new { |h,klass| h[klass] = {} }
|
143
|
+
parents = model_cache[join_root]
|
144
|
+
column_aliases = aliases.column_aliases join_root
|
83
145
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
associations.each do |association|
|
91
|
-
remove_duplicate_results!(base, records, association)
|
92
|
-
end
|
93
|
-
when Hash
|
94
|
-
associations.each_key do |name|
|
95
|
-
reflection = base.reflections[name]
|
96
|
-
remove_uniq_by_reflection(reflection, records)
|
97
|
-
|
98
|
-
parent_records = []
|
99
|
-
records.each do |record|
|
100
|
-
if descendant = record.send(reflection.name)
|
101
|
-
if reflection.collection?
|
102
|
-
parent_records.concat descendant.target.uniq
|
103
|
-
else
|
104
|
-
parent_records << descendant
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
146
|
+
result_set.each { |row_hash|
|
147
|
+
parent_key = primary_key ? row_hash[primary_key] : row_hash
|
148
|
+
primary_id = type_caster.type_cast parent_key
|
149
|
+
parent = parents[primary_id] ||= join_root.instantiate(row_hash, column_aliases)
|
150
|
+
construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
|
151
|
+
}
|
108
152
|
|
109
|
-
|
110
|
-
end
|
111
|
-
end
|
153
|
+
parents.values
|
112
154
|
end
|
113
155
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
ref = @associations
|
122
|
-
associations.each do |key|
|
123
|
-
ref = ref[key]
|
124
|
-
end
|
125
|
-
ref[association.reflection.name] ||= {}
|
156
|
+
private
|
157
|
+
|
158
|
+
def make_constraints(parent, child, tables, join_type)
|
159
|
+
chain = child.reflection.chain
|
160
|
+
foreign_table = parent.table
|
161
|
+
foreign_klass = parent.base_klass
|
162
|
+
child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain)
|
126
163
|
end
|
127
164
|
|
128
|
-
def
|
129
|
-
parent
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
unless join_association = find_join_association(reflection, parent)
|
135
|
-
@reflections << reflection
|
136
|
-
join_association = build_join_association(reflection, parent)
|
137
|
-
join_association.join_type = join_type
|
138
|
-
@join_parts << join_association
|
139
|
-
cache_joined_association(join_association)
|
140
|
-
end
|
141
|
-
join_association
|
142
|
-
when Array
|
143
|
-
associations.each do |association|
|
144
|
-
build(association, parent, join_type)
|
145
|
-
end
|
146
|
-
when Hash
|
147
|
-
associations.keys.sort_by { |a| a.to_s }.each do |name|
|
148
|
-
join_association = build(name, parent, join_type)
|
149
|
-
build(associations[name], join_association, join_type)
|
150
|
-
end
|
151
|
-
else
|
152
|
-
raise ConfigurationError, associations.inspect
|
153
|
-
end
|
165
|
+
def make_outer_joins(parent, child)
|
166
|
+
tables = table_aliases_for(parent, child)
|
167
|
+
join_type = Arel::Nodes::OuterJoin
|
168
|
+
joins = make_constraints parent, child, tables, join_type
|
169
|
+
|
170
|
+
joins.concat child.children.flat_map { |c| make_outer_joins(child, c) }
|
154
171
|
end
|
155
172
|
|
156
|
-
def
|
157
|
-
|
158
|
-
|
159
|
-
|
173
|
+
def make_inner_joins(parent, child)
|
174
|
+
tables = child.tables
|
175
|
+
join_type = Arel::Nodes::InnerJoin
|
176
|
+
joins = make_constraints parent, child, tables, join_type
|
160
177
|
|
161
|
-
|
162
|
-
|
178
|
+
joins.concat child.children.flat_map { |c| make_inner_joins(child, c) }
|
179
|
+
end
|
180
|
+
|
181
|
+
def table_aliases_for(parent, node)
|
182
|
+
node.reflection.chain.map { |reflection|
|
183
|
+
alias_tracker.aliased_table_for(
|
184
|
+
reflection.table_name,
|
185
|
+
table_alias_for(reflection, parent, reflection != node.reflection)
|
186
|
+
)
|
163
187
|
}
|
164
188
|
end
|
165
189
|
|
166
|
-
def
|
167
|
-
|
168
|
-
|
169
|
-
end
|
190
|
+
def construct_tables!(parent, node)
|
191
|
+
node.tables = table_aliases_for(parent, node)
|
192
|
+
node.children.each { |child| construct_tables! node, child }
|
170
193
|
end
|
171
194
|
|
172
|
-
def
|
173
|
-
|
195
|
+
def table_alias_for(reflection, parent, join)
|
196
|
+
name = "#{reflection.plural_name}_#{parent.table_name}"
|
197
|
+
name << "_join" if join
|
198
|
+
name
|
174
199
|
end
|
175
200
|
|
176
|
-
def
|
177
|
-
|
178
|
-
|
179
|
-
|
201
|
+
def walk(left, right)
|
202
|
+
intersection, missing = right.children.map { |node1|
|
203
|
+
[left.children.find { |node2| node1.match? node2 }, node1]
|
204
|
+
}.partition(&:first)
|
180
205
|
|
181
|
-
|
182
|
-
|
183
|
-
|
206
|
+
ojs = missing.flat_map { |_,n| make_outer_joins left, n }
|
207
|
+
intersection.flat_map { |l,r| walk l, r }.concat ojs
|
208
|
+
end
|
184
209
|
|
185
|
-
|
210
|
+
def find_reflection(klass, name)
|
211
|
+
klass._reflect_on_association(name) or
|
212
|
+
raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
|
213
|
+
end
|
186
214
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
associations.sort_by { |k,_| k.to_s }.each do |association_name, assoc|
|
195
|
-
association = construct(parent, association_name, join_parts, row)
|
196
|
-
construct(association, assoc, join_parts, row) if association
|
215
|
+
def build(associations, base_klass)
|
216
|
+
associations.map do |name, right|
|
217
|
+
reflection = find_reflection base_klass, name
|
218
|
+
reflection.check_validity!
|
219
|
+
|
220
|
+
if reflection.options[:polymorphic]
|
221
|
+
raise EagerLoadPolymorphicError.new(reflection)
|
197
222
|
end
|
198
|
-
|
199
|
-
|
223
|
+
|
224
|
+
JoinAssociation.new reflection, build(right, reflection.klass)
|
200
225
|
end
|
201
226
|
end
|
202
227
|
|
203
|
-
def
|
204
|
-
|
228
|
+
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
|
229
|
+
primary_id = ar_parent.id
|
205
230
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
|
210
|
-
set_target_and_inverse(join_part, association, record)
|
211
|
-
else
|
212
|
-
association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
|
213
|
-
case macro
|
214
|
-
when :has_many, :has_and_belongs_to_many
|
215
|
-
other = record.association(join_part.reflection.name)
|
231
|
+
parent.children.each do |node|
|
232
|
+
if node.reflection.collection?
|
233
|
+
other = ar_parent.association(node.reflection.name)
|
216
234
|
other.loaded!
|
217
|
-
other.target.push(association) if association
|
218
|
-
other.set_inverse_instance(association)
|
219
|
-
when :belongs_to
|
220
|
-
set_target_and_inverse(join_part, association, record)
|
221
235
|
else
|
222
|
-
|
236
|
+
if ar_parent.association_cache.key?(node.reflection.name)
|
237
|
+
model = ar_parent.association(node.reflection.name).target
|
238
|
+
construct(model, node, row, rs, seen, model_cache, aliases)
|
239
|
+
next
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
key = aliases.column_alias(node, node.primary_key)
|
244
|
+
id = row[key]
|
245
|
+
next if id.nil?
|
246
|
+
|
247
|
+
model = seen[parent.base_klass][primary_id][node.base_klass][id]
|
248
|
+
|
249
|
+
if model
|
250
|
+
construct(model, node, row, rs, seen, model_cache, aliases)
|
251
|
+
else
|
252
|
+
model = construct_model(ar_parent, node, row, model_cache, id, aliases)
|
253
|
+
seen[parent.base_klass][primary_id][node.base_klass][id] = model
|
254
|
+
construct(model, node, row, rs, seen, model_cache, aliases)
|
223
255
|
end
|
224
256
|
end
|
225
|
-
association
|
226
257
|
end
|
227
258
|
|
228
|
-
def
|
229
|
-
|
230
|
-
|
231
|
-
other.
|
259
|
+
def construct_model(record, node, row, model_cache, id, aliases)
|
260
|
+
model = model_cache[node][id] ||= node.instantiate(row,
|
261
|
+
aliases.column_aliases(node))
|
262
|
+
other = record.association(node.reflection.name)
|
263
|
+
|
264
|
+
if node.reflection.collection?
|
265
|
+
other.target.push(model)
|
266
|
+
else
|
267
|
+
other.target = model
|
268
|
+
end
|
269
|
+
|
270
|
+
other.set_inverse_instance(model)
|
271
|
+
model
|
232
272
|
end
|
233
273
|
end
|
234
274
|
end
|
@@ -3,6 +3,7 @@ module ActiveRecord
|
|
3
3
|
class Preloader
|
4
4
|
class Association #:nodoc:
|
5
5
|
attr_reader :owners, :reflection, :preload_scope, :model, :klass
|
6
|
+
attr_reader :preloaded_records
|
6
7
|
|
7
8
|
def initialize(klass, owners, reflection, preload_scope)
|
8
9
|
@klass = klass
|
@@ -12,15 +13,14 @@ module ActiveRecord
|
|
12
13
|
@model = owners.first && owners.first.class
|
13
14
|
@scope = nil
|
14
15
|
@owners_by_key = nil
|
16
|
+
@preloaded_records = []
|
15
17
|
end
|
16
18
|
|
17
|
-
def run
|
18
|
-
|
19
|
-
preload
|
20
|
-
end
|
19
|
+
def run(preloader)
|
20
|
+
preload(preloader)
|
21
21
|
end
|
22
22
|
|
23
|
-
def preload
|
23
|
+
def preload(preloader)
|
24
24
|
raise NotImplementedError
|
25
25
|
end
|
26
26
|
|
@@ -29,6 +29,10 @@ module ActiveRecord
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def records_for(ids)
|
32
|
+
query_scope(ids)
|
33
|
+
end
|
34
|
+
|
35
|
+
def query_scope(ids)
|
32
36
|
scope.where(association_key.in(ids))
|
33
37
|
end
|
34
38
|
|
@@ -52,13 +56,16 @@ module ActiveRecord
|
|
52
56
|
raise NotImplementedError
|
53
57
|
end
|
54
58
|
|
55
|
-
# We're converting to a string here because postgres will return the aliased association
|
56
|
-
# key in a habtm as a string (for whatever reason)
|
57
59
|
def owners_by_key
|
58
|
-
@owners_by_key ||=
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
62
69
|
end
|
63
70
|
|
64
71
|
def options
|
@@ -67,38 +74,64 @@ module ActiveRecord
|
|
67
74
|
|
68
75
|
private
|
69
76
|
|
70
|
-
def associated_records_by_owner
|
77
|
+
def associated_records_by_owner(preloader)
|
71
78
|
owners_map = owners_by_key
|
72
79
|
owner_keys = owners_map.keys.compact
|
73
80
|
|
74
|
-
|
75
|
-
|
76
|
-
|
81
|
+
# Each record may have multiple owners, and vice-versa
|
82
|
+
records_by_owner = owners.each_with_object({}) do |owner,h|
|
83
|
+
h[owner] = []
|
84
|
+
end
|
85
|
+
|
86
|
+
if owner_keys.any?
|
77
87
|
# Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
|
78
88
|
# Make several smaller queries if necessary or make one query if the adapter supports it
|
79
89
|
sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
|
80
|
-
records = sliced.map { |slice| records_for(slice).to_a }.flatten
|
81
|
-
end
|
82
|
-
|
83
|
-
# Each record may have multiple owners, and vice-versa
|
84
|
-
records_by_owner = Hash[owners.map { |owner| [owner, []] }]
|
85
|
-
records.each do |record|
|
86
|
-
owner_key = record[association_key_name].to_s
|
87
90
|
|
88
|
-
|
89
|
-
|
91
|
+
records = load_slices sliced
|
92
|
+
records.each do |record, owner_key|
|
93
|
+
owners_map[owner_key].each do |owner|
|
94
|
+
records_by_owner[owner] << record
|
95
|
+
end
|
90
96
|
end
|
91
97
|
end
|
98
|
+
|
92
99
|
records_by_owner
|
93
100
|
end
|
94
101
|
|
102
|
+
def key_conversion_required?
|
103
|
+
association_key_type != owner_key_type
|
104
|
+
end
|
105
|
+
|
106
|
+
def association_key_type
|
107
|
+
column = @klass.column_types[association_key_name.to_s]
|
108
|
+
column && column.type
|
109
|
+
end
|
110
|
+
|
111
|
+
def owner_key_type
|
112
|
+
column = @model.column_types[owner_key_name.to_s]
|
113
|
+
column && column.type
|
114
|
+
end
|
115
|
+
|
116
|
+
def load_slices(slices)
|
117
|
+
@preloaded_records = slices.flat_map { |slice|
|
118
|
+
records_for(slice)
|
119
|
+
}
|
120
|
+
|
121
|
+
@preloaded_records.map { |record|
|
122
|
+
key = record[association_key_name]
|
123
|
+
key = key.to_s if key_conversion_required?
|
124
|
+
|
125
|
+
[record, key]
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
95
129
|
def reflection_scope
|
96
130
|
@reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped
|
97
131
|
end
|
98
132
|
|
99
133
|
def build_scope
|
100
134
|
scope = klass.unscoped
|
101
|
-
scope.default_scoped = true
|
102
135
|
|
103
136
|
values = reflection_scope.values
|
104
137
|
preload_values = preload_scope.values
|
@@ -106,14 +139,23 @@ module ActiveRecord
|
|
106
139
|
scope.where_values = Array(values[:where]) + Array(preload_values[:where])
|
107
140
|
scope.references_values = Array(values[:references]) + Array(preload_values[:references])
|
108
141
|
|
109
|
-
scope.
|
142
|
+
scope._select! preload_values[:select] || values[:select] || table[Arel.star]
|
110
143
|
scope.includes! preload_values[:includes] || values[:includes]
|
111
144
|
|
145
|
+
if preload_values.key? :order
|
146
|
+
scope.order! preload_values[:order]
|
147
|
+
else
|
148
|
+
if values.key? :order
|
149
|
+
scope.order! values[:order]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
112
153
|
if options[:as]
|
113
154
|
scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
|
114
155
|
end
|
115
156
|
|
116
|
-
scope
|
157
|
+
scope.unscope_values = Array(values[:unscope])
|
158
|
+
klass.default_scoped.merge(scope)
|
117
159
|
end
|
118
160
|
end
|
119
161
|
end
|
@@ -9,8 +9,8 @@ module ActiveRecord
|
|
9
9
|
super.order(preload_scope.values[:order] || reflection_scope.values[:order])
|
10
10
|
end
|
11
11
|
|
12
|
-
def preload
|
13
|
-
associated_records_by_owner.each do |owner, records|
|
12
|
+
def preload(preloader)
|
13
|
+
associated_records_by_owner(preloader).each do |owner, records|
|
14
14
|
association = owner.association(reflection.name)
|
15
15
|
association.loaded!
|
16
16
|
association.target.concat(records)
|
@@ -5,13 +5,13 @@ module ActiveRecord
|
|
5
5
|
|
6
6
|
private
|
7
7
|
|
8
|
-
def preload
|
9
|
-
associated_records_by_owner.each do |owner, associated_records|
|
8
|
+
def preload(preloader)
|
9
|
+
associated_records_by_owner(preloader).each do |owner, associated_records|
|
10
10
|
record = associated_records.first
|
11
11
|
|
12
12
|
association = owner.association(reflection.name)
|
13
13
|
association.target = record
|
14
|
-
association.set_inverse_instance(record)
|
14
|
+
association.set_inverse_instance(record) if record
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|