activerecord 4.0.13 → 4.1.0.beta1
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 +745 -2700
- data/README.rdoc +2 -2
- data/examples/performance.rb +30 -18
- data/examples/simple.rb +4 -4
- data/lib/active_record.rb +2 -6
- data/lib/active_record/aggregations.rb +2 -1
- data/lib/active_record/association_relation.rb +0 -4
- data/lib/active_record/associations.rb +87 -43
- data/lib/active_record/associations/alias_tracker.rb +1 -3
- data/lib/active_record/associations/association.rb +8 -16
- data/lib/active_record/associations/association_scope.rb +5 -16
- data/lib/active_record/associations/belongs_to_association.rb +34 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +78 -54
- data/lib/active_record/associations/builder/belongs_to.rb +91 -58
- data/lib/active_record/associations/builder/collection_association.rb +47 -45
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -25
- data/lib/active_record/associations/builder/has_many.rb +2 -2
- 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 +68 -105
- data/lib/active_record/associations/collection_proxy.rb +12 -15
- data/lib/active_record/associations/has_many_association.rb +11 -9
- data/lib/active_record/associations/has_many_through_association.rb +16 -12
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +204 -165
- data/lib/active_record/associations/join_dependency/join_association.rb +43 -101
- 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_helper.rb +2 -11
- data/lib/active_record/associations/preloader.rb +89 -34
- data/lib/active_record/associations/preloader/association.rb +43 -25
- 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/singular_association.rb +6 -5
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +5 -2
- data/lib/active_record/attribute_methods.rb +45 -40
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
- data/lib/active_record/attribute_methods/dirty.rb +8 -22
- data/lib/active_record/attribute_methods/primary_key.rb +1 -7
- data/lib/active_record/attribute_methods/read.rb +55 -28
- data/lib/active_record/attribute_methods/serialization.rb +12 -33
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -13
- data/lib/active_record/attribute_methods/write.rb +37 -12
- data/lib/active_record/autosave_association.rb +207 -207
- data/lib/active_record/base.rb +5 -1
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -7
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +11 -22
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -14
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -5
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +84 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +52 -83
- data/lib/active_record/connection_adapters/abstract/transaction.rb +0 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +14 -97
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +58 -60
- data/lib/active_record/connection_adapters/column.rb +1 -35
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +3 -4
- data/lib/active_record/connection_adapters/mysql_adapter.rb +16 -15
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +24 -18
- data/lib/active_record/connection_adapters/postgresql/cast.rb +20 -16
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +23 -43
- data/lib/active_record/connection_adapters/postgresql/oid.rb +19 -12
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +28 -23
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +8 -30
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +92 -75
- data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +31 -64
- data/lib/active_record/connection_handling.rb +2 -2
- data/lib/active_record/core.rb +22 -43
- data/lib/active_record/counter_cache.rb +7 -7
- data/lib/active_record/enum.rb +100 -0
- data/lib/active_record/errors.rb +10 -5
- data/lib/active_record/fixture_set/file.rb +2 -1
- data/lib/active_record/fixtures.rb +171 -74
- data/lib/active_record/inheritance.rb +16 -22
- data/lib/active_record/integration.rb +52 -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 +5 -12
- data/lib/active_record/migration.rb +62 -46
- data/lib/active_record/migration/command_recorder.rb +7 -13
- data/lib/active_record/model_schema.rb +7 -14
- data/lib/active_record/nested_attributes.rb +10 -8
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +3 -3
- data/lib/active_record/persistence.rb +16 -34
- data/lib/active_record/querying.rb +14 -12
- data/lib/active_record/railtie.rb +0 -50
- data/lib/active_record/railties/databases.rake +12 -15
- data/lib/active_record/readonly_attributes.rb +0 -6
- data/lib/active_record/reflection.rb +189 -75
- data/lib/active_record/relation.rb +69 -94
- data/lib/active_record/relation/batches.rb +57 -23
- data/lib/active_record/relation/calculations.rb +36 -43
- data/lib/active_record/relation/delegation.rb +54 -39
- data/lib/active_record/relation/finder_methods.rb +107 -62
- data/lib/active_record/relation/merger.rb +7 -20
- data/lib/active_record/relation/predicate_builder.rb +57 -38
- data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +110 -98
- data/lib/active_record/relation/spawn_methods.rb +1 -2
- data/lib/active_record/result.rb +45 -6
- data/lib/active_record/runtime_registry.rb +5 -0
- data/lib/active_record/sanitization.rb +6 -8
- data/lib/active_record/schema_dumper.rb +16 -5
- data/lib/active_record/schema_migration.rb +24 -25
- data/lib/active_record/scoping/default.rb +5 -18
- data/lib/active_record/scoping/named.rb +8 -29
- data/lib/active_record/store.rb +56 -28
- data/lib/active_record/tasks/database_tasks.rb +8 -4
- data/lib/active_record/timestamp.rb +4 -4
- data/lib/active_record/transactions.rb +8 -10
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +1 -6
- data/lib/active_record/version.rb +1 -1
- data/lib/rails/generators/active_record.rb +2 -8
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
- metadata +32 -45
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
- 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 -102
@@ -1,128 +1,80 @@
|
|
1
|
+
require 'active_record/associations/join_dependency/join_part'
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module Associations
|
3
5
|
class JoinDependency # :nodoc:
|
4
6
|
class JoinAssociation < JoinPart # :nodoc:
|
5
|
-
include JoinHelper
|
6
|
-
|
7
7
|
# The reflection of the association represented
|
8
8
|
attr_reader :reflection
|
9
9
|
|
10
|
-
|
11
|
-
# relevant for generating aliases which do not conflict with other joins which are
|
12
|
-
# part of the query.
|
13
|
-
attr_reader :join_dependency
|
14
|
-
|
15
|
-
# A JoinBase instance representing the active record we are joining onto.
|
16
|
-
# (So in Author.has_many :posts, the Author would be that base record.)
|
17
|
-
attr_reader :parent
|
18
|
-
|
19
|
-
# What type of join will be generated, either Arel::InnerJoin (default) or Arel::OuterJoin
|
20
|
-
attr_accessor :join_type
|
21
|
-
|
22
|
-
# These implement abstract methods from the superclass
|
23
|
-
attr_reader :aliased_prefix
|
24
|
-
|
25
|
-
attr_reader :tables
|
26
|
-
|
27
|
-
delegate :options, :through_reflection, :source_reflection, :chain, :to => :reflection
|
28
|
-
delegate :table, :table_name, :to => :parent, :prefix => :parent
|
29
|
-
delegate :alias_tracker, :to => :join_dependency
|
10
|
+
attr_accessor :tables
|
30
11
|
|
31
|
-
|
32
|
-
|
33
|
-
def initialize(reflection, join_dependency, parent = nil)
|
34
|
-
reflection.check_validity!
|
35
|
-
|
36
|
-
if reflection.options[:polymorphic]
|
37
|
-
raise EagerLoadPolymorphicError.new(reflection)
|
38
|
-
end
|
39
|
-
|
40
|
-
super(reflection.klass)
|
12
|
+
def initialize(reflection, children)
|
13
|
+
super(reflection.klass, children)
|
41
14
|
|
42
15
|
@reflection = reflection
|
43
|
-
@
|
44
|
-
@parent = parent
|
45
|
-
@join_type = Arel::InnerJoin
|
46
|
-
@aliased_prefix = "t#{ join_dependency.join_parts.size }"
|
47
|
-
@tables = construct_tables.reverse
|
16
|
+
@tables = nil
|
48
17
|
end
|
49
18
|
|
50
|
-
def
|
51
|
-
|
52
|
-
|
53
|
-
other.parent == parent
|
19
|
+
def match?(other)
|
20
|
+
return true if self == other
|
21
|
+
super && reflection == other.reflection
|
54
22
|
end
|
55
23
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
when JoinBase
|
60
|
-
parent.base_klass == join_part.base_klass
|
61
|
-
else
|
62
|
-
parent == join_part
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
24
|
+
def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
|
25
|
+
joins = []
|
26
|
+
tables = tables.reverse
|
66
27
|
|
67
|
-
|
68
|
-
tables = @tables.dup
|
69
|
-
foreign_table = parent_table
|
70
|
-
foreign_klass = parent.base_klass
|
28
|
+
scope_chain_iter = scope_chain.reverse_each
|
71
29
|
|
72
30
|
# The chain starts with the target table, but we want to end with it here (makes
|
73
31
|
# more sense in this context), so we reverse
|
74
|
-
chain.
|
32
|
+
chain.reverse_each do |reflection|
|
75
33
|
table = tables.shift
|
34
|
+
klass = reflection.klass
|
76
35
|
|
77
36
|
case reflection.source_macro
|
78
37
|
when :belongs_to
|
79
38
|
key = reflection.association_primary_key
|
80
39
|
foreign_key = reflection.foreign_key
|
81
|
-
when :has_and_belongs_to_many
|
82
|
-
# Join the join table first...
|
83
|
-
manager.from(join(
|
84
|
-
table,
|
85
|
-
table[reflection.foreign_key].
|
86
|
-
eq(foreign_table[reflection.active_record_primary_key])
|
87
|
-
))
|
88
|
-
|
89
|
-
foreign_table, table = table, tables.shift
|
90
|
-
|
91
|
-
key = reflection.association_primary_key
|
92
|
-
foreign_key = reflection.association_foreign_key
|
93
40
|
else
|
94
41
|
key = reflection.foreign_key
|
95
42
|
foreign_key = reflection.active_record_primary_key
|
96
43
|
end
|
97
44
|
|
98
|
-
constraint = build_constraint(
|
45
|
+
constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
|
99
46
|
|
100
|
-
scope_chain_items =
|
47
|
+
scope_chain_items = scope_chain_iter.next.map do |item|
|
48
|
+
if item.is_a?(Relation)
|
49
|
+
item
|
50
|
+
else
|
51
|
+
ActiveRecord::Relation.create(klass, table).instance_exec(node, &item)
|
52
|
+
end
|
53
|
+
end
|
101
54
|
|
102
55
|
if reflection.type
|
103
|
-
scope_chain_items
|
104
|
-
ActiveRecord::Relation.
|
56
|
+
scope_chain_items <<
|
57
|
+
ActiveRecord::Relation.create(klass, table)
|
105
58
|
.where(reflection.type => foreign_klass.base_class.name)
|
106
|
-
]
|
107
59
|
end
|
108
60
|
|
109
|
-
scope_chain_items
|
61
|
+
scope_chain_items.concat [klass.send(:build_default_scope)].compact
|
110
62
|
|
111
|
-
scope_chain_items.
|
112
|
-
|
113
|
-
|
114
|
-
end
|
63
|
+
rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
|
64
|
+
left.merge right
|
65
|
+
end
|
115
66
|
|
116
|
-
|
67
|
+
if rel && !rel.arel.constraints.empty?
|
68
|
+
constraint = constraint.and rel.arel.constraints
|
117
69
|
end
|
118
70
|
|
119
|
-
|
71
|
+
joins << table.create_join(table, table.create_on(constraint), join_type)
|
120
72
|
|
121
73
|
# The current table in this iteration becomes the foreign table in the next
|
122
|
-
foreign_table, foreign_klass = table,
|
74
|
+
foreign_table, foreign_klass = table, klass
|
123
75
|
end
|
124
76
|
|
125
|
-
|
77
|
+
joins
|
126
78
|
end
|
127
79
|
|
128
80
|
# Builds equality condition.
|
@@ -134,42 +86,32 @@ module ActiveRecord
|
|
134
86
|
# end
|
135
87
|
#
|
136
88
|
# If I execute `Physician.joins(:appointments).to_a` then
|
137
|
-
# reflection
|
138
|
-
# table
|
139
|
-
# key
|
140
|
-
# foreign_table
|
141
|
-
# foreign_key
|
89
|
+
# reflection # => #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
|
90
|
+
# table # => #<Arel::Table @name="appointments" ...>
|
91
|
+
# key # => physician_id
|
92
|
+
# foreign_table # => #<Arel::Table @name="physicians" ...>
|
93
|
+
# foreign_key # => id
|
142
94
|
#
|
143
|
-
def build_constraint(
|
95
|
+
def build_constraint(klass, table, key, foreign_table, foreign_key)
|
144
96
|
constraint = table[key].eq(foreign_table[foreign_key])
|
145
97
|
|
146
|
-
if
|
98
|
+
if klass.finder_needs_type_condition?
|
147
99
|
constraint = table.create_and([
|
148
100
|
constraint,
|
149
|
-
|
101
|
+
klass.send(:type_condition, table)
|
150
102
|
])
|
151
103
|
end
|
152
104
|
|
153
105
|
constraint
|
154
106
|
end
|
155
107
|
|
156
|
-
def join_relation(joining_relation)
|
157
|
-
self.join_type = Arel::OuterJoin
|
158
|
-
joining_relation.joins(self)
|
159
|
-
end
|
160
|
-
|
161
108
|
def table
|
162
|
-
tables.
|
109
|
+
tables.first
|
163
110
|
end
|
164
111
|
|
165
112
|
def aliased_table_name
|
166
113
|
table.table_alias || table.name
|
167
114
|
end
|
168
|
-
|
169
|
-
def scope_chain
|
170
|
-
@scope_chain ||= reflection.scope_chain.reverse
|
171
|
-
end
|
172
|
-
|
173
115
|
end
|
174
116
|
end
|
175
117
|
end
|
@@ -1,18 +1,16 @@
|
|
1
|
+
require 'active_record/associations/join_dependency/join_part'
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module Associations
|
3
5
|
class JoinDependency # :nodoc:
|
4
6
|
class JoinBase < JoinPart # :nodoc:
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
end
|
9
|
-
|
10
|
-
def aliased_prefix
|
11
|
-
"t0"
|
7
|
+
def match?(other)
|
8
|
+
return true if self == other
|
9
|
+
super && base_klass == other.base_klass
|
12
10
|
end
|
13
11
|
|
14
12
|
def table
|
15
|
-
|
13
|
+
base_klass.arel_table
|
16
14
|
end
|
17
15
|
|
18
16
|
def aliased_table_name
|
@@ -8,34 +8,36 @@ module ActiveRecord
|
|
8
8
|
# operations (for example a has_and_belongs_to_many JoinAssociation would result in
|
9
9
|
# two; one for the join table and one for the target table).
|
10
10
|
class JoinPart # :nodoc:
|
11
|
+
include Enumerable
|
12
|
+
|
11
13
|
# The Active Record class which this join part is associated 'about'; for a JoinBase
|
12
14
|
# this is the actual base model, for a JoinAssociation this is the target model of the
|
13
15
|
# association.
|
14
|
-
attr_reader :base_klass
|
16
|
+
attr_reader :base_klass, :children
|
15
17
|
|
16
|
-
delegate :table_name, :column_names, :primary_key, :
|
18
|
+
delegate :table_name, :column_names, :primary_key, :to => :base_klass
|
17
19
|
|
18
|
-
def initialize(base_klass)
|
20
|
+
def initialize(base_klass, children)
|
19
21
|
@base_klass = base_klass
|
20
|
-
@cached_record = {}
|
21
22
|
@column_names_with_alias = nil
|
23
|
+
@children = children
|
22
24
|
end
|
23
25
|
|
24
|
-
def
|
25
|
-
|
26
|
+
def name
|
27
|
+
reflection.name
|
26
28
|
end
|
27
29
|
|
28
|
-
def
|
29
|
-
|
30
|
+
def match?(other)
|
31
|
+
self.class == other.class
|
30
32
|
end
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
def each(&block)
|
35
|
+
yield self
|
36
|
+
children.each { |child| child.each(&block) }
|
35
37
|
end
|
36
38
|
|
37
|
-
#
|
38
|
-
def
|
39
|
+
# An Arel::Table for the active_record
|
40
|
+
def table
|
39
41
|
raise NotImplementedError
|
40
42
|
end
|
41
43
|
|
@@ -44,24 +46,7 @@ module ActiveRecord
|
|
44
46
|
raise NotImplementedError
|
45
47
|
end
|
46
48
|
|
47
|
-
|
48
|
-
def aliased_primary_key
|
49
|
-
"#{aliased_prefix}_r0"
|
50
|
-
end
|
51
|
-
|
52
|
-
# An array of [column_name, alias] pairs for the table
|
53
|
-
def column_names_with_alias
|
54
|
-
unless @column_names_with_alias
|
55
|
-
@column_names_with_alias = []
|
56
|
-
|
57
|
-
([primary_key] + (column_names - [primary_key])).compact.each_with_index do |column_name, i|
|
58
|
-
@column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
|
59
|
-
end
|
60
|
-
end
|
61
|
-
@column_names_with_alias
|
62
|
-
end
|
63
|
-
|
64
|
-
def extract_record(row)
|
49
|
+
def extract_record(row, column_names_with_alias)
|
65
50
|
# This code is performance critical as it is called per row.
|
66
51
|
# see: https://github.com/rails/rails/pull/12185
|
67
52
|
hash = {}
|
@@ -78,12 +63,8 @@ module ActiveRecord
|
|
78
63
|
hash
|
79
64
|
end
|
80
65
|
|
81
|
-
def
|
82
|
-
row
|
83
|
-
end
|
84
|
-
|
85
|
-
def instantiate(row)
|
86
|
-
@cached_record[record_id(row)] ||= base_klass.instantiate(extract_record(row))
|
66
|
+
def instantiate(row, aliases)
|
67
|
+
base_klass.instantiate(extract_record(row, aliases))
|
87
68
|
end
|
88
69
|
end
|
89
70
|
end
|
@@ -10,21 +10,12 @@ module ActiveRecord
|
|
10
10
|
private
|
11
11
|
|
12
12
|
def construct_tables
|
13
|
-
|
14
|
-
|
15
|
-
tables << alias_tracker.aliased_table_for(
|
13
|
+
chain.map do |reflection|
|
14
|
+
alias_tracker.aliased_table_for(
|
16
15
|
table_name_for(reflection),
|
17
16
|
table_alias_for(reflection, reflection != self.reflection)
|
18
17
|
)
|
19
|
-
|
20
|
-
if reflection.source_macro == :has_and_belongs_to_many
|
21
|
-
tables << alias_tracker.aliased_table_for(
|
22
|
-
(reflection.source_reflection || reflection).join_table,
|
23
|
-
table_alias_for(reflection, true)
|
24
|
-
)
|
25
|
-
end
|
26
18
|
end
|
27
|
-
tables
|
28
19
|
end
|
29
20
|
|
30
21
|
def table_name_for(reflection)
|
@@ -42,12 +42,9 @@ module ActiveRecord
|
|
42
42
|
autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
|
43
43
|
autoload :HasOne, 'active_record/associations/preloader/has_one'
|
44
44
|
autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
|
45
|
-
autoload :HasAndBelongsToMany, 'active_record/associations/preloader/has_and_belongs_to_many'
|
46
45
|
autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
|
47
46
|
end
|
48
47
|
|
49
|
-
attr_reader :records, :associations, :preload_scope, :model
|
50
|
-
|
51
48
|
# Eager loads the named associations for the given Active Record record(s).
|
52
49
|
#
|
53
50
|
# In this description, 'association name' shall refer to the name passed
|
@@ -82,38 +79,47 @@ module ActiveRecord
|
|
82
79
|
# [ :books, :author ]
|
83
80
|
# { author: :avatar }
|
84
81
|
# [ :books, { author: :avatar } ]
|
85
|
-
def initialize(records, associations, preload_scope = nil)
|
86
|
-
@records = Array.wrap(records).compact.uniq
|
87
|
-
@associations = Array.wrap(associations)
|
88
|
-
@preload_scope = preload_scope || Relation.new(nil, nil)
|
89
|
-
end
|
90
82
|
|
91
|
-
|
92
|
-
|
93
|
-
|
83
|
+
NULL_RELATION = Struct.new(:values).new({})
|
84
|
+
|
85
|
+
def preload(records, associations, preload_scope = nil)
|
86
|
+
records = Array.wrap(records).compact.uniq
|
87
|
+
associations = Array.wrap(associations)
|
88
|
+
preload_scope = preload_scope || NULL_RELATION
|
89
|
+
|
90
|
+
if records.empty?
|
91
|
+
[]
|
92
|
+
else
|
93
|
+
associations.flat_map { |association|
|
94
|
+
preloaders_on association, records, preload_scope
|
95
|
+
}
|
94
96
|
end
|
95
97
|
end
|
96
98
|
|
97
99
|
private
|
98
100
|
|
99
|
-
def
|
101
|
+
def preloaders_on(association, records, scope)
|
100
102
|
case association
|
101
103
|
when Hash
|
102
|
-
|
104
|
+
preloaders_for_hash(association, records, scope)
|
103
105
|
when Symbol
|
104
|
-
|
106
|
+
preloaders_for_one(association, records, scope)
|
105
107
|
when String
|
106
|
-
|
108
|
+
preloaders_for_one(association.to_sym, records, scope)
|
107
109
|
else
|
108
110
|
raise ArgumentError, "#{association.inspect} was not recognised for preload"
|
109
111
|
end
|
110
112
|
end
|
111
113
|
|
112
|
-
def
|
113
|
-
association.
|
114
|
-
|
115
|
-
|
116
|
-
|
114
|
+
def preloaders_for_hash(association, records, scope)
|
115
|
+
parent, child = association.to_a.first # hash should only be of length 1
|
116
|
+
|
117
|
+
loaders = preloaders_for_one parent, records, scope
|
118
|
+
|
119
|
+
recs = loaders.flat_map(&:preloaded_records).uniq
|
120
|
+
loaders.concat Array.wrap(child).flat_map { |assoc|
|
121
|
+
preloaders_on assoc, recs, scope
|
122
|
+
}
|
117
123
|
end
|
118
124
|
|
119
125
|
# Not all records have the same class, so group then preload group on the reflection
|
@@ -123,32 +129,81 @@ module ActiveRecord
|
|
123
129
|
# Additionally, polymorphic belongs_to associations can have multiple associated
|
124
130
|
# classes, depending on the polymorphic_type field. So we group by the classes as
|
125
131
|
# well.
|
126
|
-
def
|
127
|
-
grouped_records(association).
|
128
|
-
klasses.
|
129
|
-
preloader_for(reflection).new(
|
132
|
+
def preloaders_for_one(association, records, scope)
|
133
|
+
grouped_records(association, records).flat_map do |reflection, klasses|
|
134
|
+
klasses.map do |rhs_klass, rs|
|
135
|
+
loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope)
|
136
|
+
loader.run self
|
137
|
+
loader
|
130
138
|
end
|
131
139
|
end
|
132
140
|
end
|
133
141
|
|
134
|
-
def grouped_records(association)
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
142
|
+
def grouped_records(association, records)
|
143
|
+
reflection_records = records_by_reflection(association, records)
|
144
|
+
|
145
|
+
reflection_records.each_with_object({}) do |(reflection, r_records),h|
|
146
|
+
h[reflection] = r_records.group_by { |record|
|
147
|
+
association_klass(reflection, record)
|
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)
|
140
157
|
end
|
141
|
-
h
|
142
158
|
end
|
143
159
|
|
144
|
-
def
|
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
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class AlreadyLoaded
|
176
|
+
attr_reader :owners, :reflection
|
177
|
+
|
178
|
+
def initialize(klass, owners, reflection, preload_scope)
|
179
|
+
@owners = owners
|
180
|
+
@reflection = reflection
|
181
|
+
end
|
182
|
+
|
183
|
+
def run(preloader); end
|
184
|
+
|
185
|
+
def preloaded_records
|
186
|
+
owners.flat_map { |owner| owner.read_attribute reflection.name }
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class NullPreloader
|
191
|
+
def self.new(klass, owners, reflection, preload_scope); self; end
|
192
|
+
def self.run(preloader); end
|
193
|
+
end
|
194
|
+
|
195
|
+
def preloader_for(reflection, owners, rhs_klass)
|
196
|
+
return NullPreloader unless rhs_klass
|
197
|
+
|
198
|
+
if owners.first.association(reflection.name).loaded?
|
199
|
+
return AlreadyLoaded
|
200
|
+
end
|
201
|
+
|
145
202
|
case reflection.macro
|
146
203
|
when :has_many
|
147
204
|
reflection.options[:through] ? HasManyThrough : HasMany
|
148
205
|
when :has_one
|
149
206
|
reflection.options[:through] ? HasOneThrough : HasOne
|
150
|
-
when :has_and_belongs_to_many
|
151
|
-
HasAndBelongsToMany
|
152
207
|
when :belongs_to
|
153
208
|
BelongsTo
|
154
209
|
end
|