sskirby-activerecord 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. data/CHANGELOG.md +6749 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +222 -0
  4. data/examples/associations.png +0 -0
  5. data/examples/performance.rb +177 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +147 -0
  8. data/lib/active_record/aggregations.rb +255 -0
  9. data/lib/active_record/associations.rb +1604 -0
  10. data/lib/active_record/associations/alias_tracker.rb +79 -0
  11. data/lib/active_record/associations/association.rb +239 -0
  12. data/lib/active_record/associations/association_scope.rb +119 -0
  13. data/lib/active_record/associations/belongs_to_association.rb +79 -0
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
  15. data/lib/active_record/associations/builder/association.rb +55 -0
  16. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  17. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  19. data/lib/active_record/associations/builder/has_many.rb +71 -0
  20. data/lib/active_record/associations/builder/has_one.rb +62 -0
  21. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  22. data/lib/active_record/associations/collection_association.rb +574 -0
  23. data/lib/active_record/associations/collection_proxy.rb +132 -0
  24. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
  25. data/lib/active_record/associations/has_many_association.rb +108 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +180 -0
  27. data/lib/active_record/associations/has_one_association.rb +73 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +214 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  33. data/lib/active_record/associations/join_helper.rb +55 -0
  34. data/lib/active_record/associations/preloader.rb +177 -0
  35. data/lib/active_record/associations/preloader/association.rb +127 -0
  36. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  37. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  38. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  39. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  40. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  41. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  42. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  43. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  44. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  45. data/lib/active_record/associations/singular_association.rb +64 -0
  46. data/lib/active_record/associations/through_association.rb +83 -0
  47. data/lib/active_record/attribute_assignment.rb +221 -0
  48. data/lib/active_record/attribute_methods.rb +272 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  50. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  51. data/lib/active_record/attribute_methods/dirty.rb +101 -0
  52. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  53. data/lib/active_record/attribute_methods/query.rb +39 -0
  54. data/lib/active_record/attribute_methods/read.rb +135 -0
  55. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  56. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
  57. data/lib/active_record/attribute_methods/write.rb +69 -0
  58. data/lib/active_record/autosave_association.rb +422 -0
  59. data/lib/active_record/base.rb +716 -0
  60. data/lib/active_record/callbacks.rb +275 -0
  61. data/lib/active_record/coders/yaml_column.rb +41 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
  70. data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
  71. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  72. data/lib/active_record/connection_adapters/column.rb +270 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
  75. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
  76. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
  78. data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
  79. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  80. data/lib/active_record/counter_cache.rb +119 -0
  81. data/lib/active_record/dynamic_finder_match.rb +56 -0
  82. data/lib/active_record/dynamic_matchers.rb +79 -0
  83. data/lib/active_record/dynamic_scope_match.rb +23 -0
  84. data/lib/active_record/errors.rb +195 -0
  85. data/lib/active_record/explain.rb +85 -0
  86. data/lib/active_record/explain_subscriber.rb +21 -0
  87. data/lib/active_record/fixtures.rb +906 -0
  88. data/lib/active_record/fixtures/file.rb +65 -0
  89. data/lib/active_record/identity_map.rb +156 -0
  90. data/lib/active_record/inheritance.rb +167 -0
  91. data/lib/active_record/integration.rb +49 -0
  92. data/lib/active_record/locale/en.yml +40 -0
  93. data/lib/active_record/locking/optimistic.rb +183 -0
  94. data/lib/active_record/locking/pessimistic.rb +77 -0
  95. data/lib/active_record/log_subscriber.rb +68 -0
  96. data/lib/active_record/migration.rb +765 -0
  97. data/lib/active_record/migration/command_recorder.rb +105 -0
  98. data/lib/active_record/model_schema.rb +366 -0
  99. data/lib/active_record/nested_attributes.rb +469 -0
  100. data/lib/active_record/observer.rb +121 -0
  101. data/lib/active_record/persistence.rb +372 -0
  102. data/lib/active_record/query_cache.rb +74 -0
  103. data/lib/active_record/querying.rb +58 -0
  104. data/lib/active_record/railtie.rb +119 -0
  105. data/lib/active_record/railties/console_sandbox.rb +6 -0
  106. data/lib/active_record/railties/controller_runtime.rb +49 -0
  107. data/lib/active_record/railties/databases.rake +620 -0
  108. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  109. data/lib/active_record/readonly_attributes.rb +26 -0
  110. data/lib/active_record/reflection.rb +534 -0
  111. data/lib/active_record/relation.rb +534 -0
  112. data/lib/active_record/relation/batches.rb +90 -0
  113. data/lib/active_record/relation/calculations.rb +354 -0
  114. data/lib/active_record/relation/delegation.rb +49 -0
  115. data/lib/active_record/relation/finder_methods.rb +398 -0
  116. data/lib/active_record/relation/predicate_builder.rb +58 -0
  117. data/lib/active_record/relation/query_methods.rb +417 -0
  118. data/lib/active_record/relation/spawn_methods.rb +148 -0
  119. data/lib/active_record/result.rb +34 -0
  120. data/lib/active_record/sanitization.rb +194 -0
  121. data/lib/active_record/schema.rb +58 -0
  122. data/lib/active_record/schema_dumper.rb +204 -0
  123. data/lib/active_record/scoping.rb +152 -0
  124. data/lib/active_record/scoping/default.rb +142 -0
  125. data/lib/active_record/scoping/named.rb +202 -0
  126. data/lib/active_record/serialization.rb +18 -0
  127. data/lib/active_record/serializers/xml_serializer.rb +202 -0
  128. data/lib/active_record/session_store.rb +358 -0
  129. data/lib/active_record/store.rb +50 -0
  130. data/lib/active_record/test_case.rb +73 -0
  131. data/lib/active_record/timestamp.rb +113 -0
  132. data/lib/active_record/transactions.rb +360 -0
  133. data/lib/active_record/translation.rb +22 -0
  134. data/lib/active_record/validations.rb +83 -0
  135. data/lib/active_record/validations/associated.rb +43 -0
  136. data/lib/active_record/validations/uniqueness.rb +180 -0
  137. data/lib/active_record/version.rb +10 -0
  138. data/lib/rails/generators/active_record.rb +25 -0
  139. data/lib/rails/generators/active_record/migration.rb +15 -0
  140. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  141. data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
  142. data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
  143. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  144. data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
  145. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  146. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  147. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  148. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  149. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  150. 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