activerecord 3.0.0 → 4.0.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 +7 -0
- data/CHANGELOG.md +2102 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +35 -44
- data/examples/performance.rb +110 -100
- data/lib/active_record/aggregations.rb +59 -75
- data/lib/active_record/associations/alias_tracker.rb +76 -0
- data/lib/active_record/associations/association.rb +248 -0
- data/lib/active_record/associations/association_scope.rb +135 -0
- data/lib/active_record/associations/belongs_to_association.rb +60 -59
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
- data/lib/active_record/associations/builder/association.rb +108 -0
- data/lib/active_record/associations/builder/belongs_to.rb +98 -0
- data/lib/active_record/associations/builder/collection_association.rb +89 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +25 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +608 -0
- data/lib/active_record/associations/collection_proxy.rb +986 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
- data/lib/active_record/associations/has_many_association.rb +83 -76
- data/lib/active_record/associations/has_many_through_association.rb +147 -66
- data/lib/active_record/associations/has_one_association.rb +67 -108
- data/lib/active_record/associations/has_one_through_association.rb +21 -25
- data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_dependency.rb +235 -0
- data/lib/active_record/associations/join_helper.rb +45 -0
- data/lib/active_record/associations/preloader/association.rb +121 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +63 -0
- data/lib/active_record/associations/preloader.rb +178 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +87 -0
- data/lib/active_record/associations.rb +512 -1224
- data/lib/active_record/attribute_assignment.rb +201 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
- data/lib/active_record/attribute_methods/dirty.rb +51 -28
- data/lib/active_record/attribute_methods/primary_key.rb +94 -22
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +63 -72
- data/lib/active_record/attribute_methods/serialization.rb +162 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
- data/lib/active_record/attribute_methods/write.rb +39 -13
- data/lib/active_record/attribute_methods.rb +362 -29
- data/lib/active_record/autosave_association.rb +132 -75
- data/lib/active_record/base.rb +83 -1627
- data/lib/active_record/callbacks.rb +69 -47
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
- data/lib/active_record/connection_adapters/column.rb +318 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
- data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +463 -0
- data/lib/active_record/counter_cache.rb +108 -101
- data/lib/active_record/dynamic_matchers.rb +131 -0
- data/lib/active_record/errors.rb +54 -13
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +55 -0
- data/lib/active_record/fixtures.rb +703 -785
- data/lib/active_record/inheritance.rb +200 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +69 -60
- data/lib/active_record/locking/pessimistic.rb +34 -12
- data/lib/active_record/log_subscriber.rb +40 -6
- data/lib/active_record/migration/command_recorder.rb +164 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +614 -216
- data/lib/active_record/model_schema.rb +345 -0
- data/lib/active_record/nested_attributes.rb +248 -119
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +275 -57
- data/lib/active_record/query_cache.rb +29 -9
- data/lib/active_record/querying.rb +62 -0
- data/lib/active_record/railtie.rb +135 -21
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +17 -5
- data/lib/active_record/railties/databases.rake +249 -359
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +30 -0
- data/lib/active_record/reflection.rb +283 -103
- data/lib/active_record/relation/batches.rb +38 -34
- data/lib/active_record/relation/calculations.rb +252 -139
- data/lib/active_record/relation/delegation.rb +125 -0
- data/lib/active_record/relation/finder_methods.rb +182 -188
- data/lib/active_record/relation/merger.rb +161 -0
- data/lib/active_record/relation/predicate_builder.rb +86 -21
- data/lib/active_record/relation/query_methods.rb +917 -134
- data/lib/active_record/relation/spawn_methods.rb +53 -92
- data/lib/active_record/relation.rb +405 -143
- data/lib/active_record/result.rb +67 -0
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +168 -0
- data/lib/active_record/schema.rb +20 -14
- data/lib/active_record/schema_dumper.rb +55 -46
- data/lib/active_record/schema_migration.rb +39 -0
- data/lib/active_record/scoping/default.rb +146 -0
- data/lib/active_record/scoping/named.rb +175 -0
- data/lib/active_record/scoping.rb +82 -0
- data/lib/active_record/serialization.rb +8 -46
- data/lib/active_record/serializers/xml_serializer.rb +21 -68
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +156 -0
- data/lib/active_record/tasks/database_tasks.rb +203 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
- data/lib/active_record/test_case.rb +57 -28
- data/lib/active_record/timestamp.rb +49 -18
- data/lib/active_record/transactions.rb +106 -63
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +25 -24
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +123 -83
- data/lib/active_record/validations.rb +29 -29
- data/lib/active_record/version.rb +7 -5
- data/lib/active_record.rb +83 -34
- data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
- data/lib/rails/generators/active_record.rb +4 -8
- metadata +163 -121
- data/CHANGELOG +0 -6023
- data/examples/associations.png +0 -0
- data/lib/active_record/association_preload.rb +0 -403
- data/lib/active_record/associations/association_collection.rb +0 -562
- data/lib/active_record/associations/association_proxy.rb +0 -295
- data/lib/active_record/associations/through_association_scope.rb +0 -154
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
- data/lib/active_record/dynamic_finder_match.rb +0 -53
- data/lib/active_record/dynamic_scope_match.rb +0 -32
- data/lib/active_record/named_scope.rb +0 -138
- data/lib/active_record/observer.rb +0 -140
- data/lib/active_record/session_store.rb +0 -340
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -0,0 +1,235 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class JoinDependency # :nodoc:
|
4
|
+
autoload :JoinPart, 'active_record/associations/join_dependency/join_part'
|
5
|
+
autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
|
6
|
+
autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
|
7
|
+
|
8
|
+
attr_reader :join_parts, :reflections, :alias_tracker, :base_klass
|
9
|
+
|
10
|
+
# base is the base class on which operation is taking place.
|
11
|
+
# associations is the list of associations which are joined using hash, symbol or array.
|
12
|
+
# joins is the list of all string join commnads and arel nodes.
|
13
|
+
#
|
14
|
+
# Example :
|
15
|
+
#
|
16
|
+
# class Physician < ActiveRecord::Base
|
17
|
+
# has_many :appointments
|
18
|
+
# has_many :patients, through: :appointments
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# If I execute `@physician.patients.to_a` then
|
22
|
+
# base #=> Physician
|
23
|
+
# associations #=> []
|
24
|
+
# joins #=> [#<Arel::Nodes::InnerJoin: ...]
|
25
|
+
#
|
26
|
+
# However if I execute `Physician.joins(:appointments).to_a` then
|
27
|
+
# base #=> Physician
|
28
|
+
# associations #=> [:appointments]
|
29
|
+
# joins #=> []
|
30
|
+
#
|
31
|
+
def initialize(base, associations, joins)
|
32
|
+
@base_klass = base
|
33
|
+
@table_joins = joins
|
34
|
+
@join_parts = [JoinBase.new(base)]
|
35
|
+
@associations = {}
|
36
|
+
@reflections = []
|
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
|
48
|
+
end
|
49
|
+
|
50
|
+
def join_associations
|
51
|
+
join_parts.last(join_parts.length - 1)
|
52
|
+
end
|
53
|
+
|
54
|
+
def join_base
|
55
|
+
join_parts.first
|
56
|
+
end
|
57
|
+
|
58
|
+
def columns
|
59
|
+
join_parts.collect { |join_part|
|
60
|
+
table = join_part.aliased_table
|
61
|
+
join_part.column_names_with_alias.collect{ |column_name, aliased_name|
|
62
|
+
table[column_name].as Arel.sql(aliased_name)
|
63
|
+
}
|
64
|
+
}.flatten
|
65
|
+
end
|
66
|
+
|
67
|
+
def instantiate(rows)
|
68
|
+
primary_key = join_base.aliased_primary_key
|
69
|
+
parents = {}
|
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
|
77
|
+
|
78
|
+
remove_duplicate_results!(base_klass, records, @associations)
|
79
|
+
records
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
|
84
|
+
def remove_duplicate_results!(base, records, associations)
|
85
|
+
case associations
|
86
|
+
when Symbol, String
|
87
|
+
reflection = base.reflections[associations]
|
88
|
+
remove_uniq_by_reflection(reflection, records)
|
89
|
+
when Array
|
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
|
108
|
+
|
109
|
+
remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def cache_joined_association(association)
|
115
|
+
associations = []
|
116
|
+
parent = association.parent
|
117
|
+
while parent != join_base
|
118
|
+
associations.unshift(parent.reflection.name)
|
119
|
+
parent = parent.parent
|
120
|
+
end
|
121
|
+
ref = @associations
|
122
|
+
associations.each do |key|
|
123
|
+
ref = ref[key]
|
124
|
+
end
|
125
|
+
ref[association.reflection.name] ||= {}
|
126
|
+
end
|
127
|
+
|
128
|
+
def build(associations, parent = nil, join_type = Arel::InnerJoin)
|
129
|
+
parent ||= join_parts.last
|
130
|
+
case associations
|
131
|
+
when Symbol, String
|
132
|
+
reflection = parent.reflections[associations.intern] or
|
133
|
+
raise ConfigurationError, "Association named '#{ associations }' was not found on #{ parent.base_klass.name }; perhaps you misspelled it?"
|
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
|
154
|
+
end
|
155
|
+
|
156
|
+
def find_join_association(name_or_reflection, parent)
|
157
|
+
if String === name_or_reflection
|
158
|
+
name_or_reflection = name_or_reflection.to_sym
|
159
|
+
end
|
160
|
+
|
161
|
+
join_associations.detect { |j|
|
162
|
+
j.reflection == name_or_reflection && j.parent == parent
|
163
|
+
}
|
164
|
+
end
|
165
|
+
|
166
|
+
def remove_uniq_by_reflection(reflection, records)
|
167
|
+
if reflection && reflection.collection?
|
168
|
+
records.each { |record| record.send(reflection.name).target.uniq! }
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def build_join_association(reflection, parent)
|
173
|
+
JoinAssociation.new(reflection, self, parent)
|
174
|
+
end
|
175
|
+
|
176
|
+
def construct(parent, associations, join_parts, row)
|
177
|
+
case associations
|
178
|
+
when Symbol, String
|
179
|
+
name = associations.to_s
|
180
|
+
|
181
|
+
join_part = join_parts.detect { |j|
|
182
|
+
j.reflection.name.to_s == name &&
|
183
|
+
j.parent_table_name == parent.class.table_name }
|
184
|
+
|
185
|
+
raise(ConfigurationError, "No such association") unless join_part
|
186
|
+
|
187
|
+
join_parts.delete(join_part)
|
188
|
+
construct_association(parent, join_part, row)
|
189
|
+
when Array
|
190
|
+
associations.each do |association|
|
191
|
+
construct(parent, association, join_parts, row)
|
192
|
+
end
|
193
|
+
when Hash
|
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
|
197
|
+
end
|
198
|
+
else
|
199
|
+
raise ConfigurationError, associations.inspect
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def construct_association(record, join_part, row)
|
204
|
+
return if record.id.to_s != join_part.parent.record_id(row).to_s
|
205
|
+
|
206
|
+
macro = join_part.reflection.macro
|
207
|
+
if macro == :has_one
|
208
|
+
return record.association(join_part.reflection.name).target if record.association_cache.key?(join_part.reflection.name)
|
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)
|
216
|
+
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
|
+
else
|
222
|
+
raise ConfigurationError, "unknown macro: #{join_part.reflection.macro}"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
association
|
226
|
+
end
|
227
|
+
|
228
|
+
def set_target_and_inverse(join_part, association, record)
|
229
|
+
other = record.association(join_part.reflection.name)
|
230
|
+
other.target = association
|
231
|
+
other.set_inverse_instance(association)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
# Helper class module which gets mixed into JoinDependency::JoinAssociation and AssociationScope
|
4
|
+
module JoinHelper #:nodoc:
|
5
|
+
|
6
|
+
def join_type
|
7
|
+
Arel::InnerJoin
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def construct_tables
|
13
|
+
tables = []
|
14
|
+
chain.each do |reflection|
|
15
|
+
tables << alias_tracker.aliased_table_for(
|
16
|
+
table_name_for(reflection),
|
17
|
+
table_alias_for(reflection, reflection != self.reflection)
|
18
|
+
)
|
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
|
+
end
|
27
|
+
tables
|
28
|
+
end
|
29
|
+
|
30
|
+
def table_name_for(reflection)
|
31
|
+
reflection.table_name
|
32
|
+
end
|
33
|
+
|
34
|
+
def table_alias_for(reflection, join = false)
|
35
|
+
name = "#{reflection.plural_name}_#{alias_suffix}"
|
36
|
+
name << "_join" if join
|
37
|
+
name
|
38
|
+
end
|
39
|
+
|
40
|
+
def join(table, constraint)
|
41
|
+
table.create_join(table, table.create_on(constraint), join_type)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class Preloader
|
4
|
+
class Association #:nodoc:
|
5
|
+
attr_reader :owners, :reflection, :preload_scope, :model, :klass
|
6
|
+
|
7
|
+
def initialize(klass, owners, reflection, preload_scope)
|
8
|
+
@klass = klass
|
9
|
+
@owners = owners
|
10
|
+
@reflection = reflection
|
11
|
+
@preload_scope = preload_scope
|
12
|
+
@model = owners.first && owners.first.class
|
13
|
+
@scope = nil
|
14
|
+
@owners_by_key = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
unless owners.first.association(reflection.name).loaded?
|
19
|
+
preload
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def preload
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def scope
|
28
|
+
@scope ||= build_scope
|
29
|
+
end
|
30
|
+
|
31
|
+
def records_for(ids)
|
32
|
+
scope.where(association_key.in(ids))
|
33
|
+
end
|
34
|
+
|
35
|
+
def table
|
36
|
+
klass.arel_table
|
37
|
+
end
|
38
|
+
|
39
|
+
# The name of the key on the associated records
|
40
|
+
def association_key_name
|
41
|
+
raise NotImplementedError
|
42
|
+
end
|
43
|
+
|
44
|
+
# This is overridden by HABTM as the condition should be on the foreign_key column in
|
45
|
+
# the join table
|
46
|
+
def association_key
|
47
|
+
table[association_key_name]
|
48
|
+
end
|
49
|
+
|
50
|
+
# The name of the key on the model which declares the association
|
51
|
+
def owner_key_name
|
52
|
+
raise NotImplementedError
|
53
|
+
end
|
54
|
+
|
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
|
+
def owners_by_key
|
58
|
+
@owners_by_key ||= owners.group_by do |owner|
|
59
|
+
key = owner[owner_key_name]
|
60
|
+
key && key.to_s
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def options
|
65
|
+
reflection.options
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def associated_records_by_owner
|
71
|
+
owners_map = owners_by_key
|
72
|
+
owner_keys = owners_map.keys.compact
|
73
|
+
|
74
|
+
if klass.nil? || owner_keys.empty?
|
75
|
+
records = []
|
76
|
+
else
|
77
|
+
# Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
|
78
|
+
# Make several smaller queries if necessary or make one query if the adapter supports it
|
79
|
+
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
|
+
|
88
|
+
owners_map[owner_key].each do |owner|
|
89
|
+
records_by_owner[owner] << record
|
90
|
+
end
|
91
|
+
end
|
92
|
+
records_by_owner
|
93
|
+
end
|
94
|
+
|
95
|
+
def reflection_scope
|
96
|
+
@reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped
|
97
|
+
end
|
98
|
+
|
99
|
+
def build_scope
|
100
|
+
scope = klass.unscoped
|
101
|
+
scope.default_scoped = true
|
102
|
+
|
103
|
+
values = reflection_scope.values
|
104
|
+
preload_values = preload_scope.values
|
105
|
+
|
106
|
+
scope.where_values = Array(values[:where]) + Array(preload_values[:where])
|
107
|
+
scope.references_values = Array(values[:references]) + Array(preload_values[:references])
|
108
|
+
|
109
|
+
scope.select! preload_values[:select] || values[:select] || table[Arel.star]
|
110
|
+
scope.includes! preload_values[:includes] || values[:includes]
|
111
|
+
|
112
|
+
if options[:as]
|
113
|
+
scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name })
|
114
|
+
end
|
115
|
+
|
116
|
+
scope
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class Preloader
|
4
|
+
class BelongsTo < SingularAssociation #:nodoc:
|
5
|
+
|
6
|
+
def association_key_name
|
7
|
+
reflection.options[:primary_key] || klass && klass.primary_key
|
8
|
+
end
|
9
|
+
|
10
|
+
def owner_key_name
|
11
|
+
reflection.foreign_key
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class Preloader
|
4
|
+
class CollectionAssociation < Association #:nodoc:
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def build_scope
|
9
|
+
super.order(preload_scope.values[:order] || reflection_scope.values[:order])
|
10
|
+
end
|
11
|
+
|
12
|
+
def preload
|
13
|
+
associated_records_by_owner.each do |owner, records|
|
14
|
+
association = owner.association(reflection.name)
|
15
|
+
association.loaded!
|
16
|
+
association.target.concat(records)
|
17
|
+
records.each { |record| association.set_inverse_instance(record) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class Preloader
|
4
|
+
class HasAndBelongsToMany < CollectionAssociation #:nodoc:
|
5
|
+
attr_reader :join_table
|
6
|
+
|
7
|
+
def initialize(klass, records, reflection, preload_options)
|
8
|
+
super
|
9
|
+
@join_table = Arel::Table.new(reflection.join_table).alias('t0')
|
10
|
+
end
|
11
|
+
|
12
|
+
# Unlike the other associations, we want to get a raw array of rows so that we can
|
13
|
+
# access the aliased column on the join table
|
14
|
+
def records_for(ids)
|
15
|
+
scope = super
|
16
|
+
klass.connection.select_all(scope.arel, 'SQL', scope.bind_values)
|
17
|
+
end
|
18
|
+
|
19
|
+
def owner_key_name
|
20
|
+
reflection.active_record_primary_key
|
21
|
+
end
|
22
|
+
|
23
|
+
def association_key_name
|
24
|
+
'ar_association_key_name'
|
25
|
+
end
|
26
|
+
|
27
|
+
def association_key
|
28
|
+
join_table[reflection.foreign_key]
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Once we have used the join table column (in super), we manually instantiate the
|
34
|
+
# actual records, ensuring that we don't create more than one instances of the same
|
35
|
+
# record
|
36
|
+
def associated_records_by_owner
|
37
|
+
records = {}
|
38
|
+
super.each_value do |rows|
|
39
|
+
rows.map! { |row| records[row[klass.primary_key]] ||= klass.instantiate(row) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_scope
|
44
|
+
super.joins(join).select(join_select)
|
45
|
+
end
|
46
|
+
|
47
|
+
def join_select
|
48
|
+
association_key.as(Arel.sql(association_key_name))
|
49
|
+
end
|
50
|
+
|
51
|
+
def join
|
52
|
+
condition = table[reflection.association_primary_key].eq(
|
53
|
+
join_table[reflection.association_foreign_key])
|
54
|
+
|
55
|
+
table.create_join(join_table, table.create_on(condition))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class Preloader
|
4
|
+
class HasMany < CollectionAssociation #:nodoc:
|
5
|
+
|
6
|
+
def association_key_name
|
7
|
+
reflection.foreign_key
|
8
|
+
end
|
9
|
+
|
10
|
+
def owner_key_name
|
11
|
+
reflection.active_record_primary_key
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class Preloader
|
4
|
+
class HasManyThrough < CollectionAssociation #:nodoc:
|
5
|
+
include ThroughAssociation
|
6
|
+
|
7
|
+
def associated_records_by_owner
|
8
|
+
records_by_owner = super
|
9
|
+
|
10
|
+
if reflection_scope.distinct_value
|
11
|
+
records_by_owner.each_value { |records| records.uniq! }
|
12
|
+
end
|
13
|
+
|
14
|
+
records_by_owner
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class Preloader
|
4
|
+
class HasOne < SingularAssociation #:nodoc:
|
5
|
+
|
6
|
+
def association_key_name
|
7
|
+
reflection.foreign_key
|
8
|
+
end
|
9
|
+
|
10
|
+
def owner_key_name
|
11
|
+
reflection.active_record_primary_key
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def build_scope
|
17
|
+
super.order(preload_scope.values[:order] || reflection_scope.values[:order])
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class Preloader
|
4
|
+
class SingularAssociation < Association #:nodoc:
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def preload
|
9
|
+
associated_records_by_owner.each do |owner, associated_records|
|
10
|
+
record = associated_records.first
|
11
|
+
|
12
|
+
association = owner.association(reflection.name)
|
13
|
+
association.target = record
|
14
|
+
association.set_inverse_instance(record)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class Preloader
|
4
|
+
module ThroughAssociation #:nodoc:
|
5
|
+
|
6
|
+
def through_reflection
|
7
|
+
reflection.through_reflection
|
8
|
+
end
|
9
|
+
|
10
|
+
def source_reflection
|
11
|
+
reflection.source_reflection
|
12
|
+
end
|
13
|
+
|
14
|
+
def associated_records_by_owner
|
15
|
+
through_records = through_records_by_owner
|
16
|
+
|
17
|
+
Preloader.new(through_records.values.flatten, source_reflection.name, reflection_scope).run
|
18
|
+
|
19
|
+
through_records.each do |owner, records|
|
20
|
+
records.map! { |r| r.send(source_reflection.name) }.flatten!
|
21
|
+
records.compact!
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def through_records_by_owner
|
28
|
+
Preloader.new(owners, through_reflection.name, through_scope).run
|
29
|
+
|
30
|
+
Hash[owners.map do |owner|
|
31
|
+
through_records = Array.wrap(owner.send(through_reflection.name))
|
32
|
+
|
33
|
+
# Dont cache the association - we would only be caching a subset
|
34
|
+
if (through_scope != through_reflection.klass.unscoped) ||
|
35
|
+
(reflection.options[:source_type] && through_reflection.collection?)
|
36
|
+
owner.association(through_reflection.name).reset
|
37
|
+
end
|
38
|
+
|
39
|
+
[owner, through_records]
|
40
|
+
end]
|
41
|
+
end
|
42
|
+
|
43
|
+
def through_scope
|
44
|
+
through_scope = through_reflection.klass.unscoped
|
45
|
+
|
46
|
+
if options[:source_type]
|
47
|
+
through_scope.where! reflection.foreign_type => options[:source_type]
|
48
|
+
else
|
49
|
+
unless reflection_scope.where_values.empty?
|
50
|
+
through_scope.includes_values = Array(reflection_scope.values[:includes] || options[:source])
|
51
|
+
through_scope.where_values = reflection_scope.values[:where]
|
52
|
+
end
|
53
|
+
|
54
|
+
through_scope.references! reflection_scope.values[:references]
|
55
|
+
through_scope.order! reflection_scope.values[:order] if through_scope.eager_loading?
|
56
|
+
end
|
57
|
+
|
58
|
+
through_scope
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|