activerecord 3.0.20 → 3.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.
- data/CHANGELOG +220 -91
- data/README.rdoc +3 -3
- data/examples/performance.rb +88 -109
- data/lib/active_record.rb +6 -2
- data/lib/active_record/aggregations.rb +22 -45
- data/lib/active_record/associations.rb +264 -991
- data/lib/active_record/associations/alias_tracker.rb +85 -0
- data/lib/active_record/associations/association.rb +231 -0
- data/lib/active_record/associations/association_scope.rb +120 -0
- data/lib/active_record/associations/belongs_to_association.rb +40 -60
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +15 -63
- data/lib/active_record/associations/builder/association.rb +53 -0
- data/lib/active_record/associations/builder/belongs_to.rb +85 -0
- data/lib/active_record/associations/builder/collection_association.rb +75 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +63 -0
- data/lib/active_record/associations/builder/has_many.rb +65 -0
- data/lib/active_record/associations/builder/has_one.rb +63 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +524 -0
- data/lib/active_record/associations/collection_proxy.rb +125 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +27 -118
- data/lib/active_record/associations/has_many_association.rb +50 -79
- data/lib/active_record/associations/has_many_through_association.rb +98 -67
- data/lib/active_record/associations/has_one_association.rb +45 -115
- data/lib/active_record/associations/has_one_through_association.rb +21 -25
- data/lib/active_record/associations/join_dependency.rb +215 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +150 -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_helper.rb +56 -0
- data/lib/active_record/associations/preloader.rb +177 -0
- data/lib/active_record/associations/preloader/association.rb +126 -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 +15 -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 +67 -0
- data/lib/active_record/associations/singular_association.rb +55 -0
- data/lib/active_record/associations/through_association.rb +80 -0
- data/lib/active_record/attribute_methods.rb +19 -5
- data/lib/active_record/attribute_methods/before_type_cast.rb +9 -8
- data/lib/active_record/attribute_methods/dirty.rb +8 -2
- data/lib/active_record/attribute_methods/primary_key.rb +33 -13
- data/lib/active_record/attribute_methods/read.rb +17 -17
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -4
- data/lib/active_record/attribute_methods/write.rb +2 -1
- data/lib/active_record/autosave_association.rb +66 -45
- data/lib/active_record/base.rb +445 -273
- data/lib/active_record/callbacks.rb +24 -33
- data/lib/active_record/coders/yaml_column.rb +41 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +106 -13
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +16 -2
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -12
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +16 -16
- data/lib/active_record/connection_adapters/abstract/quoting.rb +61 -22
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -273
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -42
- data/lib/active_record/connection_adapters/abstract_adapter.rb +44 -25
- data/lib/active_record/connection_adapters/column.rb +268 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +686 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +331 -88
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +295 -267
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -7
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +108 -26
- data/lib/active_record/counter_cache.rb +7 -4
- data/lib/active_record/fixtures.rb +174 -192
- data/lib/active_record/identity_map.rb +131 -0
- data/lib/active_record/locking/optimistic.rb +20 -14
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +24 -4
- data/lib/active_record/migration.rb +265 -144
- data/lib/active_record/migration/command_recorder.rb +103 -0
- data/lib/active_record/named_scope.rb +68 -25
- data/lib/active_record/nested_attributes.rb +58 -15
- data/lib/active_record/observer.rb +3 -7
- data/lib/active_record/persistence.rb +58 -38
- data/lib/active_record/query_cache.rb +25 -3
- data/lib/active_record/railtie.rb +21 -12
- data/lib/active_record/railties/console_sandbox.rb +6 -0
- data/lib/active_record/railties/databases.rake +147 -116
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/reflection.rb +176 -44
- data/lib/active_record/relation.rb +125 -49
- data/lib/active_record/relation/batches.rb +7 -5
- data/lib/active_record/relation/calculations.rb +50 -18
- data/lib/active_record/relation/finder_methods.rb +47 -26
- data/lib/active_record/relation/predicate_builder.rb +24 -21
- data/lib/active_record/relation/query_methods.rb +117 -101
- data/lib/active_record/relation/spawn_methods.rb +27 -20
- data/lib/active_record/result.rb +34 -0
- data/lib/active_record/schema.rb +5 -6
- data/lib/active_record/schema_dumper.rb +11 -13
- data/lib/active_record/serialization.rb +2 -2
- data/lib/active_record/serializers/xml_serializer.rb +10 -10
- data/lib/active_record/session_store.rb +8 -2
- data/lib/active_record/test_case.rb +9 -20
- data/lib/active_record/timestamp.rb +21 -9
- data/lib/active_record/transactions.rb +16 -15
- data/lib/active_record/validations.rb +21 -22
- data/lib/active_record/validations/associated.rb +3 -1
- data/lib/active_record/validations/uniqueness.rb +48 -58
- data/lib/active_record/version.rb +3 -3
- data/lib/rails/generators/active_record.rb +6 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +10 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +2 -1
- data/lib/rails/generators/active_record/model/templates/migration.rb +6 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +2 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +2 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +2 -1
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +2 -2
- metadata +106 -77
- checksums.yaml +0 -7
- data/lib/active_record/association_preload.rb +0 -431
- data/lib/active_record/associations/association_collection.rb +0 -572
- data/lib/active_record/associations/association_proxy.rb +0 -304
- data/lib/active_record/associations/through_association_scope.rb +0 -160
@@ -0,0 +1,215 @@
|
|
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, :active_record
|
9
|
+
|
10
|
+
def initialize(base, associations, joins)
|
11
|
+
@active_record = base
|
12
|
+
@table_joins = joins
|
13
|
+
@join_parts = [JoinBase.new(base)]
|
14
|
+
@associations = {}
|
15
|
+
@reflections = []
|
16
|
+
@alias_tracker = AliasTracker.new(joins)
|
17
|
+
@alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
|
18
|
+
build(associations)
|
19
|
+
end
|
20
|
+
|
21
|
+
def graft(*associations)
|
22
|
+
associations.each do |association|
|
23
|
+
join_associations.detect {|a| association == a} ||
|
24
|
+
build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
|
25
|
+
end
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def join_associations
|
30
|
+
join_parts.last(join_parts.length - 1)
|
31
|
+
end
|
32
|
+
|
33
|
+
def join_base
|
34
|
+
join_parts.first
|
35
|
+
end
|
36
|
+
|
37
|
+
def columns
|
38
|
+
join_parts.collect { |join_part|
|
39
|
+
table = join_part.aliased_table
|
40
|
+
join_part.column_names_with_alias.collect{ |column_name, aliased_name|
|
41
|
+
table[column_name].as Arel.sql(aliased_name)
|
42
|
+
}
|
43
|
+
}.flatten
|
44
|
+
end
|
45
|
+
|
46
|
+
def instantiate(rows)
|
47
|
+
primary_key = join_base.aliased_primary_key
|
48
|
+
parents = {}
|
49
|
+
|
50
|
+
records = rows.map { |model|
|
51
|
+
primary_id = model[primary_key]
|
52
|
+
parent = parents[primary_id] ||= join_base.instantiate(model)
|
53
|
+
construct(parent, @associations, join_associations, model)
|
54
|
+
parent
|
55
|
+
}.uniq
|
56
|
+
|
57
|
+
remove_duplicate_results!(active_record, records, @associations)
|
58
|
+
records
|
59
|
+
end
|
60
|
+
|
61
|
+
def remove_duplicate_results!(base, records, associations)
|
62
|
+
case associations
|
63
|
+
when Symbol, String
|
64
|
+
reflection = base.reflections[associations]
|
65
|
+
remove_uniq_by_reflection(reflection, records)
|
66
|
+
when Array
|
67
|
+
associations.each do |association|
|
68
|
+
remove_duplicate_results!(base, records, association)
|
69
|
+
end
|
70
|
+
when Hash
|
71
|
+
associations.keys.each do |name|
|
72
|
+
reflection = base.reflections[name]
|
73
|
+
remove_uniq_by_reflection(reflection, records)
|
74
|
+
|
75
|
+
parent_records = []
|
76
|
+
records.each do |record|
|
77
|
+
if descendant = record.send(reflection.name)
|
78
|
+
if reflection.collection?
|
79
|
+
parent_records.concat descendant.target.uniq
|
80
|
+
else
|
81
|
+
parent_records << descendant
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
93
|
+
def cache_joined_association(association)
|
94
|
+
associations = []
|
95
|
+
parent = association.parent
|
96
|
+
while parent != join_base
|
97
|
+
associations.unshift(parent.reflection.name)
|
98
|
+
parent = parent.parent
|
99
|
+
end
|
100
|
+
ref = @associations
|
101
|
+
associations.each do |key|
|
102
|
+
ref = ref[key]
|
103
|
+
end
|
104
|
+
ref[association.reflection.name] ||= {}
|
105
|
+
end
|
106
|
+
|
107
|
+
def build(associations, parent = nil, join_type = Arel::InnerJoin)
|
108
|
+
parent ||= join_parts.last
|
109
|
+
case associations
|
110
|
+
when Symbol, String
|
111
|
+
reflection = parent.reflections[associations.to_s.intern] or
|
112
|
+
raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
|
113
|
+
unless join_association = find_join_association(reflection, parent)
|
114
|
+
@reflections << reflection
|
115
|
+
join_association = build_join_association(reflection, parent)
|
116
|
+
join_association.join_type = join_type
|
117
|
+
@join_parts << join_association
|
118
|
+
cache_joined_association(join_association)
|
119
|
+
end
|
120
|
+
join_association
|
121
|
+
when Array
|
122
|
+
associations.each do |association|
|
123
|
+
build(association, parent, join_type)
|
124
|
+
end
|
125
|
+
when Hash
|
126
|
+
associations.keys.sort_by { |a| a.to_s }.each do |name|
|
127
|
+
join_association = build(name, parent, join_type)
|
128
|
+
build(associations[name], join_association, join_type)
|
129
|
+
end
|
130
|
+
else
|
131
|
+
raise ConfigurationError, associations.inspect
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def find_join_association(name_or_reflection, parent)
|
136
|
+
if String === name_or_reflection
|
137
|
+
name_or_reflection = name_or_reflection.to_sym
|
138
|
+
end
|
139
|
+
|
140
|
+
join_associations.detect { |j|
|
141
|
+
j.reflection == name_or_reflection && j.parent == parent
|
142
|
+
}
|
143
|
+
end
|
144
|
+
|
145
|
+
def remove_uniq_by_reflection(reflection, records)
|
146
|
+
if reflection && reflection.collection?
|
147
|
+
records.each { |record| record.send(reflection.name).target.uniq! }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def build_join_association(reflection, parent)
|
152
|
+
JoinAssociation.new(reflection, self, parent)
|
153
|
+
end
|
154
|
+
|
155
|
+
def construct(parent, associations, join_parts, row)
|
156
|
+
case associations
|
157
|
+
when Symbol, String
|
158
|
+
name = associations.to_s
|
159
|
+
|
160
|
+
join_part = join_parts.detect { |j|
|
161
|
+
j.reflection.name.to_s == name &&
|
162
|
+
j.parent_table_name == parent.class.table_name }
|
163
|
+
|
164
|
+
raise(ConfigurationError, "No such association") unless join_part
|
165
|
+
|
166
|
+
join_parts.delete(join_part)
|
167
|
+
construct_association(parent, join_part, row)
|
168
|
+
when Array
|
169
|
+
associations.each do |association|
|
170
|
+
construct(parent, association, join_parts, row)
|
171
|
+
end
|
172
|
+
when Hash
|
173
|
+
associations.sort_by { |k,_| k.to_s }.each do |association_name, assoc|
|
174
|
+
association = construct(parent, association_name, join_parts, row)
|
175
|
+
construct(association, assoc, join_parts, row) if association
|
176
|
+
end
|
177
|
+
else
|
178
|
+
raise ConfigurationError, associations.inspect
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def construct_association(record, join_part, row)
|
183
|
+
return if record.id.to_s != join_part.parent.record_id(row).to_s
|
184
|
+
|
185
|
+
macro = join_part.reflection.macro
|
186
|
+
if macro == :has_one
|
187
|
+
return if record.association_cache.key?(join_part.reflection.name)
|
188
|
+
association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
|
189
|
+
set_target_and_inverse(join_part, association, record)
|
190
|
+
else
|
191
|
+
return if row[join_part.aliased_primary_key].nil?
|
192
|
+
association = join_part.instantiate(row)
|
193
|
+
case macro
|
194
|
+
when :has_many, :has_and_belongs_to_many
|
195
|
+
other = record.association(join_part.reflection.name)
|
196
|
+
other.loaded!
|
197
|
+
other.target.push(association)
|
198
|
+
other.set_inverse_instance(association)
|
199
|
+
when :belongs_to
|
200
|
+
set_target_and_inverse(join_part, association, record)
|
201
|
+
else
|
202
|
+
raise ConfigurationError, "unknown macro: #{join_part.reflection.macro}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
association
|
206
|
+
end
|
207
|
+
|
208
|
+
def set_target_and_inverse(join_part, association, record)
|
209
|
+
other = record.association(join_part.reflection.name)
|
210
|
+
other.target = association
|
211
|
+
other.set_inverse_instance(association)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class JoinDependency # :nodoc:
|
4
|
+
class JoinAssociation < JoinPart # :nodoc:
|
5
|
+
include JoinHelper
|
6
|
+
|
7
|
+
# The reflection of the association represented
|
8
|
+
attr_reader :reflection
|
9
|
+
|
10
|
+
# The JoinDependency object which this JoinAssociation exists within. This is mainly
|
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
|
30
|
+
|
31
|
+
alias :alias_suffix :parent_table_name
|
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)
|
41
|
+
|
42
|
+
@reflection = reflection
|
43
|
+
@join_dependency = join_dependency
|
44
|
+
@parent = parent
|
45
|
+
@join_type = Arel::InnerJoin
|
46
|
+
@aliased_prefix = "t#{ join_dependency.join_parts.size }"
|
47
|
+
@tables = construct_tables.reverse
|
48
|
+
end
|
49
|
+
|
50
|
+
def ==(other)
|
51
|
+
other.class == self.class &&
|
52
|
+
other.reflection == reflection &&
|
53
|
+
other.parent == parent
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_parent_in(other_join_dependency)
|
57
|
+
other_join_dependency.join_parts.detect do |join_part|
|
58
|
+
parent == join_part
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def join_to(relation)
|
63
|
+
tables = @tables.dup
|
64
|
+
foreign_table = parent_table
|
65
|
+
|
66
|
+
# The chain starts with the target table, but we want to end with it here (makes
|
67
|
+
# more sense in this context), so we reverse
|
68
|
+
chain.reverse.each_with_index do |reflection, i|
|
69
|
+
table = tables.shift
|
70
|
+
|
71
|
+
case reflection.source_macro
|
72
|
+
when :belongs_to
|
73
|
+
key = reflection.association_primary_key
|
74
|
+
foreign_key = reflection.foreign_key
|
75
|
+
when :has_and_belongs_to_many
|
76
|
+
# Join the join table first...
|
77
|
+
relation.from(join(
|
78
|
+
table,
|
79
|
+
table[reflection.foreign_key].
|
80
|
+
eq(foreign_table[reflection.active_record_primary_key])
|
81
|
+
))
|
82
|
+
|
83
|
+
foreign_table, table = table, tables.shift
|
84
|
+
|
85
|
+
key = reflection.association_primary_key
|
86
|
+
foreign_key = reflection.association_foreign_key
|
87
|
+
else
|
88
|
+
key = reflection.foreign_key
|
89
|
+
foreign_key = reflection.active_record_primary_key
|
90
|
+
end
|
91
|
+
|
92
|
+
constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
|
93
|
+
|
94
|
+
relation.from(join(table, constraint))
|
95
|
+
|
96
|
+
unless conditions[i].empty?
|
97
|
+
relation.where(sanitize(conditions[i], table))
|
98
|
+
end
|
99
|
+
|
100
|
+
# The current table in this iteration becomes the foreign table in the next
|
101
|
+
foreign_table = table
|
102
|
+
end
|
103
|
+
|
104
|
+
relation
|
105
|
+
end
|
106
|
+
|
107
|
+
def build_constraint(reflection, table, key, foreign_table, foreign_key)
|
108
|
+
constraint = table[key].eq(foreign_table[foreign_key])
|
109
|
+
|
110
|
+
if reflection.klass.finder_needs_type_condition?
|
111
|
+
constraint = table.create_and([
|
112
|
+
constraint,
|
113
|
+
reflection.klass.send(:type_condition, table)
|
114
|
+
])
|
115
|
+
end
|
116
|
+
|
117
|
+
constraint
|
118
|
+
end
|
119
|
+
|
120
|
+
def join_relation(joining_relation)
|
121
|
+
self.join_type = Arel::OuterJoin
|
122
|
+
joining_relation.joins(self)
|
123
|
+
end
|
124
|
+
|
125
|
+
def table
|
126
|
+
tables.last
|
127
|
+
end
|
128
|
+
|
129
|
+
def aliased_table_name
|
130
|
+
table.table_alias || table.name
|
131
|
+
end
|
132
|
+
|
133
|
+
def conditions
|
134
|
+
@conditions ||= reflection.conditions.reverse
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def interpolate(conditions)
|
140
|
+
if conditions.respond_to?(:to_proc)
|
141
|
+
instance_eval(&conditions)
|
142
|
+
else
|
143
|
+
conditions
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class JoinDependency # :nodoc:
|
4
|
+
class JoinBase < JoinPart # :nodoc:
|
5
|
+
def ==(other)
|
6
|
+
other.class == self.class &&
|
7
|
+
other.active_record == active_record
|
8
|
+
end
|
9
|
+
|
10
|
+
def aliased_prefix
|
11
|
+
"t0"
|
12
|
+
end
|
13
|
+
|
14
|
+
def table
|
15
|
+
Arel::Table.new(table_name, arel_engine)
|
16
|
+
end
|
17
|
+
|
18
|
+
def aliased_table_name
|
19
|
+
active_record.table_name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class JoinDependency # :nodoc:
|
4
|
+
# A JoinPart represents a part of a JoinDependency. It is an abstract class, inherited
|
5
|
+
# by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
|
6
|
+
# everything else is being joined onto. A JoinAssociation represents an association which
|
7
|
+
# is joining to the base. A JoinAssociation may result in more than one actual join
|
8
|
+
# operations (for example a has_and_belongs_to_many JoinAssociation would result in
|
9
|
+
# two; one for the join table and one for the target table).
|
10
|
+
class JoinPart # :nodoc:
|
11
|
+
# The Active Record class which this join part is associated 'about'; for a JoinBase
|
12
|
+
# this is the actual base model, for a JoinAssociation this is the target model of the
|
13
|
+
# association.
|
14
|
+
attr_reader :active_record
|
15
|
+
|
16
|
+
delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :active_record
|
17
|
+
|
18
|
+
def initialize(active_record)
|
19
|
+
@active_record = active_record
|
20
|
+
@cached_record = {}
|
21
|
+
@column_names_with_alias = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def aliased_table
|
25
|
+
Arel::Nodes::TableAlias.new table, aliased_table_name
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(other)
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
# An Arel::Table for the active_record
|
33
|
+
def table
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
|
37
|
+
# The prefix to be used when aliasing columns in the active_record's table
|
38
|
+
def aliased_prefix
|
39
|
+
raise NotImplementedError
|
40
|
+
end
|
41
|
+
|
42
|
+
# The alias for the active_record's table
|
43
|
+
def aliased_table_name
|
44
|
+
raise NotImplementedError
|
45
|
+
end
|
46
|
+
|
47
|
+
# The alias for the primary key of the active_record's table
|
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])).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)
|
65
|
+
Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}]
|
66
|
+
end
|
67
|
+
|
68
|
+
def record_id(row)
|
69
|
+
row[aliased_primary_key]
|
70
|
+
end
|
71
|
+
|
72
|
+
def instantiate(row)
|
73
|
+
@cached_record[record_id(row)] ||= active_record.send(:instantiate, extract_record(row))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|