sskirby-activerecord 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +6749 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +222 -0
- data/examples/associations.png +0 -0
- data/examples/performance.rb +177 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +147 -0
- data/lib/active_record/aggregations.rb +255 -0
- data/lib/active_record/associations.rb +1604 -0
- data/lib/active_record/associations/alias_tracker.rb +79 -0
- data/lib/active_record/associations/association.rb +239 -0
- data/lib/active_record/associations/association_scope.rb +119 -0
- data/lib/active_record/associations/belongs_to_association.rb +79 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
- data/lib/active_record/associations/builder/association.rb +55 -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 +57 -0
- data/lib/active_record/associations/builder/has_many.rb +71 -0
- data/lib/active_record/associations/builder/has_one.rb +62 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +574 -0
- data/lib/active_record/associations/collection_proxy.rb +132 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
- data/lib/active_record/associations/has_many_association.rb +108 -0
- data/lib/active_record/associations/has_many_through_association.rb +180 -0
- data/lib/active_record/associations/has_one_association.rb +73 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency.rb +214 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +154 -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 +55 -0
- data/lib/active_record/associations/preloader.rb +177 -0
- data/lib/active_record/associations/preloader/association.rb +127 -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 +64 -0
- data/lib/active_record/associations/through_association.rb +83 -0
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods.rb +272 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +101 -0
- data/lib/active_record/attribute_methods/primary_key.rb +114 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +135 -0
- data/lib/active_record/attribute_methods/serialization.rb +93 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
- data/lib/active_record/attribute_methods/write.rb +69 -0
- data/lib/active_record/autosave_association.rb +422 -0
- data/lib/active_record/base.rb +716 -0
- data/lib/active_record/callbacks.rb +275 -0
- data/lib/active_record/coders/yaml_column.rb +41 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
- data/lib/active_record/connection_adapters/column.rb +270 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/counter_cache.rb +119 -0
- data/lib/active_record/dynamic_finder_match.rb +56 -0
- data/lib/active_record/dynamic_matchers.rb +79 -0
- data/lib/active_record/dynamic_scope_match.rb +23 -0
- data/lib/active_record/errors.rb +195 -0
- data/lib/active_record/explain.rb +85 -0
- data/lib/active_record/explain_subscriber.rb +21 -0
- data/lib/active_record/fixtures.rb +906 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/identity_map.rb +156 -0
- data/lib/active_record/inheritance.rb +167 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locale/en.yml +40 -0
- data/lib/active_record/locking/optimistic.rb +183 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +68 -0
- data/lib/active_record/migration.rb +765 -0
- data/lib/active_record/migration/command_recorder.rb +105 -0
- data/lib/active_record/model_schema.rb +366 -0
- data/lib/active_record/nested_attributes.rb +469 -0
- data/lib/active_record/observer.rb +121 -0
- data/lib/active_record/persistence.rb +372 -0
- data/lib/active_record/query_cache.rb +74 -0
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +119 -0
- data/lib/active_record/railties/console_sandbox.rb +6 -0
- data/lib/active_record/railties/controller_runtime.rb +49 -0
- data/lib/active_record/railties/databases.rake +620 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +534 -0
- data/lib/active_record/relation.rb +534 -0
- data/lib/active_record/relation/batches.rb +90 -0
- data/lib/active_record/relation/calculations.rb +354 -0
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +398 -0
- data/lib/active_record/relation/predicate_builder.rb +58 -0
- data/lib/active_record/relation/query_methods.rb +417 -0
- data/lib/active_record/relation/spawn_methods.rb +148 -0
- data/lib/active_record/result.rb +34 -0
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema.rb +58 -0
- data/lib/active_record/schema_dumper.rb +204 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/serialization.rb +18 -0
- data/lib/active_record/serializers/xml_serializer.rb +202 -0
- data/lib/active_record/session_store.rb +358 -0
- data/lib/active_record/store.rb +50 -0
- data/lib/active_record/test_case.rb +73 -0
- data/lib/active_record/timestamp.rb +113 -0
- data/lib/active_record/transactions.rb +360 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations.rb +83 -0
- data/lib/active_record/validations/associated.rb +43 -0
- data/lib/active_record/validations/uniqueness.rb +180 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/rails/generators/active_record.rb +25 -0
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
- data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
- metadata +242 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require 'active_support/core_ext/object/inclusion'
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
# = Active Record Belongs To Has One Association
|
|
5
|
+
module Associations
|
|
6
|
+
class HasOneAssociation < SingularAssociation #:nodoc:
|
|
7
|
+
def replace(record, save = true)
|
|
8
|
+
raise_on_type_mismatch(record) if record
|
|
9
|
+
load_target
|
|
10
|
+
|
|
11
|
+
reflection.klass.transaction do
|
|
12
|
+
if target && target != record
|
|
13
|
+
remove_target!(options[:dependent]) unless target.destroyed?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if record
|
|
17
|
+
set_owner_attributes(record)
|
|
18
|
+
set_inverse_instance(record)
|
|
19
|
+
|
|
20
|
+
if owner.persisted? && save && !record.save
|
|
21
|
+
nullify_owner_attributes(record)
|
|
22
|
+
set_owner_attributes(target) if target
|
|
23
|
+
raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
self.target = record
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def delete(method = options[:dependent])
|
|
32
|
+
if load_target
|
|
33
|
+
case method
|
|
34
|
+
when :delete
|
|
35
|
+
target.delete
|
|
36
|
+
when :destroy
|
|
37
|
+
target.destroy
|
|
38
|
+
when :nullify
|
|
39
|
+
target.update_attribute(reflection.foreign_key, nil)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# The reason that the save param for replace is false, if for create (not just build),
|
|
47
|
+
# is because the setting of the foreign keys is actually handled by the scoping when
|
|
48
|
+
# the record is instantiated, and so they are set straight away and do not need to be
|
|
49
|
+
# updated within replace.
|
|
50
|
+
def set_new_record(record)
|
|
51
|
+
replace(record, false)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def remove_target!(method)
|
|
55
|
+
if method.in?([:delete, :destroy])
|
|
56
|
+
target.send(method)
|
|
57
|
+
else
|
|
58
|
+
nullify_owner_attributes(target)
|
|
59
|
+
|
|
60
|
+
if target.persisted? && owner.persisted? && !target.save
|
|
61
|
+
set_owner_attributes(target)
|
|
62
|
+
raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
|
|
63
|
+
"The record failed to save when after its foreign key was set to nil."
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def nullify_owner_attributes(record)
|
|
69
|
+
record[reflection.foreign_key] = nil
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
# = Active Record Has One Through Association
|
|
3
|
+
module Associations
|
|
4
|
+
class HasOneThroughAssociation < HasOneAssociation #:nodoc:
|
|
5
|
+
include ThroughAssociation
|
|
6
|
+
|
|
7
|
+
def replace(record)
|
|
8
|
+
create_through_record(record)
|
|
9
|
+
self.target = record
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def create_through_record(record)
|
|
15
|
+
ensure_not_nested
|
|
16
|
+
|
|
17
|
+
through_proxy = owner.association(through_reflection.name)
|
|
18
|
+
through_record = through_proxy.send(:load_target)
|
|
19
|
+
|
|
20
|
+
if through_record && !record
|
|
21
|
+
through_record.destroy
|
|
22
|
+
elsif record
|
|
23
|
+
attributes = construct_join_attributes(record)
|
|
24
|
+
|
|
25
|
+
if through_record
|
|
26
|
+
through_record.update_attributes(attributes)
|
|
27
|
+
elsif owner.new_record?
|
|
28
|
+
through_proxy.build(attributes)
|
|
29
|
+
else
|
|
30
|
+
through_proxy.create(attributes)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,214 @@
|
|
|
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 record.association(join_part.reflection.name).target 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
|
+
association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
|
|
192
|
+
case macro
|
|
193
|
+
when :has_many, :has_and_belongs_to_many
|
|
194
|
+
other = record.association(join_part.reflection.name)
|
|
195
|
+
other.loaded!
|
|
196
|
+
other.target.push(association) if association
|
|
197
|
+
other.set_inverse_instance(association)
|
|
198
|
+
when :belongs_to
|
|
199
|
+
set_target_and_inverse(join_part, association, record)
|
|
200
|
+
else
|
|
201
|
+
raise ConfigurationError, "unknown macro: #{join_part.reflection.macro}"
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
association
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def set_target_and_inverse(join_part, association, record)
|
|
208
|
+
other = record.association(join_part.reflection.name)
|
|
209
|
+
other.target = association
|
|
210
|
+
other.set_inverse_instance(association)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
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
|
+
foreign_klass = parent.active_record
|
|
66
|
+
|
|
67
|
+
# The chain starts with the target table, but we want to end with it here (makes
|
|
68
|
+
# more sense in this context), so we reverse
|
|
69
|
+
chain.reverse.each_with_index do |reflection, i|
|
|
70
|
+
table = tables.shift
|
|
71
|
+
|
|
72
|
+
case reflection.source_macro
|
|
73
|
+
when :belongs_to
|
|
74
|
+
key = reflection.association_primary_key
|
|
75
|
+
foreign_key = reflection.foreign_key
|
|
76
|
+
when :has_and_belongs_to_many
|
|
77
|
+
# Join the join table first...
|
|
78
|
+
relation.from(join(
|
|
79
|
+
table,
|
|
80
|
+
table[reflection.foreign_key].
|
|
81
|
+
eq(foreign_table[reflection.active_record_primary_key])
|
|
82
|
+
))
|
|
83
|
+
|
|
84
|
+
foreign_table, table = table, tables.shift
|
|
85
|
+
|
|
86
|
+
key = reflection.association_primary_key
|
|
87
|
+
foreign_key = reflection.association_foreign_key
|
|
88
|
+
else
|
|
89
|
+
key = reflection.foreign_key
|
|
90
|
+
foreign_key = reflection.active_record_primary_key
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
|
|
94
|
+
|
|
95
|
+
conditions = self.conditions[i].dup
|
|
96
|
+
conditions << { reflection.type => foreign_klass.base_class.name } if reflection.type
|
|
97
|
+
|
|
98
|
+
unless conditions.empty?
|
|
99
|
+
constraint = constraint.and(sanitize(conditions, table))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
relation.from(join(table, constraint))
|
|
103
|
+
|
|
104
|
+
# The current table in this iteration becomes the foreign table in the next
|
|
105
|
+
foreign_table, foreign_klass = table, reflection.klass
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
relation
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def build_constraint(reflection, table, key, foreign_table, foreign_key)
|
|
112
|
+
constraint = table[key].eq(foreign_table[foreign_key])
|
|
113
|
+
|
|
114
|
+
if reflection.klass.finder_needs_type_condition?
|
|
115
|
+
constraint = table.create_and([
|
|
116
|
+
constraint,
|
|
117
|
+
reflection.klass.send(:type_condition, table)
|
|
118
|
+
])
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
constraint
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def join_relation(joining_relation)
|
|
125
|
+
self.join_type = Arel::OuterJoin
|
|
126
|
+
joining_relation.joins(self)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def table
|
|
130
|
+
tables.last
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def aliased_table_name
|
|
134
|
+
table.table_alias || table.name
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def conditions
|
|
138
|
+
@conditions ||= reflection.conditions.reverse
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def interpolate(conditions)
|
|
144
|
+
if conditions.respond_to?(:to_proc)
|
|
145
|
+
instance_eval(&conditions)
|
|
146
|
+
else
|
|
147
|
+
conditions
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|