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.

Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,142 +1,101 @@
1
+
1
2
  module ActiveRecord
2
3
  # = Active Record Belongs To Has One Association
3
4
  module Associations
4
- class HasOneAssociation < AssociationProxy #:nodoc:
5
- def initialize(owner, reflection)
6
- super
7
- construct_sql
8
- end
5
+ class HasOneAssociation < SingularAssociation #:nodoc:
6
+
7
+ def handle_dependency
8
+ case options[:dependent]
9
+ when :restrict, :restrict_with_exception
10
+ raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target
11
+
12
+ when :restrict_with_error
13
+ if load_target
14
+ record = klass.human_attribute_name(reflection.name).downcase
15
+ owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record)
16
+ false
17
+ end
9
18
 
10
- def create(attrs = {}, replace_existing = true)
11
- new_record(replace_existing) do |reflection|
12
- attrs = merge_with_conditions(attrs)
13
- reflection.create_association(attrs)
19
+ else
20
+ delete
14
21
  end
15
22
  end
16
23
 
17
- def create!(attrs = {}, replace_existing = true)
18
- new_record(replace_existing) do |reflection|
19
- attrs = merge_with_conditions(attrs)
20
- reflection.create_association!(attrs)
21
- end
22
- end
24
+ def replace(record, save = true)
25
+ raise_on_type_mismatch!(record) if record
26
+ load_target
23
27
 
24
- def build(attrs = {}, replace_existing = true)
25
- new_record(replace_existing) do |reflection|
26
- attrs = merge_with_conditions(attrs)
27
- reflection.build_association(attrs)
28
- end
29
- end
28
+ return self.target if !(target || record)
29
+ if (target != record) || record.changed?
30
+ transaction_if(save) do
31
+ remove_target!(options[:dependent]) if target && !target.destroyed?
30
32
 
31
- def replace(obj, dont_save = false)
32
- load_target
33
+ if record
34
+ set_owner_attributes(record)
35
+ set_inverse_instance(record)
33
36
 
34
- unless @target.nil? || @target == obj
35
- if dependent? && !dont_save
36
- case @reflection.options[:dependent]
37
- when :delete
38
- @target.delete unless @target.new_record?
39
- @owner.clear_association_cache
40
- when :destroy
41
- @target.destroy unless @target.new_record?
42
- @owner.clear_association_cache
43
- when :nullify
44
- @target[@reflection.primary_key_name] = nil
45
- @target.save unless @owner.new_record? || @target.new_record?
37
+ if owner.persisted? && save && !record.save
38
+ nullify_owner_attributes(record)
39
+ set_owner_attributes(target) if target
40
+ raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
41
+ end
46
42
  end
47
- else
48
- @target[@reflection.primary_key_name] = nil
49
- @target.save unless @owner.new_record? || @target.new_record?
50
43
  end
51
44
  end
52
45
 
53
- if obj.nil?
54
- @target = nil
55
- else
56
- raise_on_type_mismatch(obj)
57
- set_belongs_to_association_for(obj)
58
- @target = (AssociationProxy === obj ? obj.target : obj)
59
- end
60
-
61
- set_inverse_instance(obj, @owner)
62
- @loaded = true
63
-
64
- unless @owner.new_record? or obj.nil? or dont_save
65
- return (obj.save ? self : false)
66
- else
67
- return (obj.nil? ? nil : self)
68
- end
46
+ self.target = record
69
47
  end
70
48
 
71
- protected
72
- def owner_quoted_id
73
- if @reflection.options[:primary_key]
74
- @owner.class.quote_value(@owner.send(@reflection.options[:primary_key]))
75
- else
76
- @owner.quoted_id
49
+ def delete(method = options[:dependent])
50
+ if load_target
51
+ case method
52
+ when :delete
53
+ target.delete
54
+ when :destroy
55
+ target.destroy
56
+ when :nullify
57
+ target.update_columns(reflection.foreign_key => nil)
77
58
  end
78
59
  end
60
+ end
79
61
 
80
62
  private
81
- def find_target
82
- options = @reflection.options.dup
83
- (options.keys - [:select, :order, :include, :readonly]).each do |key|
84
- options.delete key
85
- end
86
- options[:conditions] = @finder_sql
87
63
 
88
- the_target = @reflection.klass.find(:first, options)
89
- set_inverse_instance(the_target, @owner)
90
- the_target
64
+ # The reason that the save param for replace is false, if for create (not just build),
65
+ # is because the setting of the foreign keys is actually handled by the scoping when
66
+ # the record is instantiated, and so they are set straight away and do not need to be
67
+ # updated within replace.
68
+ def set_new_record(record)
69
+ replace(record, false)
91
70
  end
92
71
 
93
- def construct_sql
94
- case
95
- when @reflection.options[:as]
96
- @finder_sql =
97
- "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
98
- "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
72
+ def remove_target!(method)
73
+ case method
74
+ when :delete
75
+ target.delete
76
+ when :destroy
77
+ target.destroy
99
78
  else
100
- @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
79
+ nullify_owner_attributes(target)
80
+
81
+ if target.persisted? && owner.persisted? && !target.save
82
+ set_owner_attributes(target)
83
+ raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
84
+ "The record failed to save after its foreign key was set to nil."
85
+ end
101
86
  end
102
- @finder_sql << " AND (#{conditions})" if conditions
103
87
  end
104
88
 
105
- def construct_scope
106
- create_scoping = {}
107
- set_belongs_to_association_for(create_scoping)
108
- { :create => create_scoping }
89
+ def nullify_owner_attributes(record)
90
+ record[reflection.foreign_key] = nil
109
91
  end
110
92
 
111
- def new_record(replace_existing)
112
- # Make sure we load the target first, if we plan on replacing the existing
113
- # instance. Otherwise, if the target has not previously been loaded
114
- # elsewhere, the instance we create will get orphaned.
115
- load_target if replace_existing
116
- record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) do
117
- yield @reflection
118
- end
119
-
120
- if replace_existing
121
- replace(record, true)
93
+ def transaction_if(value)
94
+ if value
95
+ reflection.klass.transaction { yield }
122
96
  else
123
- record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
124
- self.target = record
125
- set_inverse_instance(record, @owner)
97
+ yield
126
98
  end
127
-
128
- record
129
- end
130
-
131
- def we_can_set_the_inverse_on_this?(record)
132
- inverse = @reflection.inverse_of
133
- return !inverse.nil?
134
- end
135
-
136
- def merge_with_conditions(attrs={})
137
- attrs ||= {}
138
- attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
139
- attrs
140
99
  end
141
100
  end
142
101
  end
@@ -1,40 +1,36 @@
1
- require "active_record/associations/through_association_scope"
2
-
3
1
  module ActiveRecord
4
2
  # = Active Record Has One Through Association
5
3
  module Associations
6
- class HasOneThroughAssociation < HasOneAssociation
7
- include ThroughAssociationScope
4
+ class HasOneThroughAssociation < HasOneAssociation #:nodoc:
5
+ include ThroughAssociation
8
6
 
9
- def replace(new_value)
10
- create_through_record(new_value)
11
- @target = new_value
7
+ def replace(record)
8
+ create_through_record(record)
9
+ self.target = record
12
10
  end
13
11
 
14
12
  private
15
13
 
16
- def create_through_record(new_value) #nodoc:
17
- klass = @reflection.through_reflection.klass
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)
18
19
 
19
- current_object = @owner.send(@reflection.through_reflection.name)
20
+ if through_record && !record
21
+ through_record.destroy
22
+ elsif record
23
+ attributes = construct_join_attributes(record)
20
24
 
21
- if current_object
22
- new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
23
- elsif new_value
24
- if @owner.new_record?
25
- self.target = new_value
26
- through_association = @owner.send(:association_instance_get, @reflection.through_reflection.name)
27
- through_association.build(construct_join_attributes(new_value))
28
- else
29
- @owner.send(@reflection.through_reflection.name, klass.create(construct_join_attributes(new_value)))
25
+ if through_record
26
+ through_record.update(attributes)
27
+ elsif owner.new_record?
28
+ through_proxy.build(attributes)
29
+ else
30
+ through_proxy.create(attributes)
31
+ end
30
32
  end
31
33
  end
32
- end
33
-
34
- private
35
- def find_target
36
- with_scope(construct_scope) { @reflection.klass.find(:first) }
37
- end
38
34
  end
39
35
  end
40
36
  end
@@ -0,0 +1,174 @@
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
+ case parent
59
+ when JoinBase
60
+ parent.base_klass == join_part.base_klass
61
+ else
62
+ parent == join_part
63
+ end
64
+ end
65
+ end
66
+
67
+ def join_to(manager)
68
+ tables = @tables.dup
69
+ foreign_table = parent_table
70
+ foreign_klass = parent.base_klass
71
+
72
+ # The chain starts with the target table, but we want to end with it here (makes
73
+ # more sense in this context), so we reverse
74
+ chain.reverse.each_with_index do |reflection, i|
75
+ table = tables.shift
76
+
77
+ case reflection.source_macro
78
+ when :belongs_to
79
+ key = reflection.association_primary_key
80
+ foreign_key = reflection.foreign_key
81
+ when :has_and_belongs_to_many
82
+ # Join the join table first...
83
+ manager.from(join(
84
+ table,
85
+ table[reflection.foreign_key].
86
+ eq(foreign_table[reflection.active_record_primary_key])
87
+ ))
88
+
89
+ foreign_table, table = table, tables.shift
90
+
91
+ key = reflection.association_primary_key
92
+ foreign_key = reflection.association_foreign_key
93
+ else
94
+ key = reflection.foreign_key
95
+ foreign_key = reflection.active_record_primary_key
96
+ end
97
+
98
+ constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
99
+
100
+ scope_chain_items = scope_chain[i]
101
+
102
+ if reflection.type
103
+ scope_chain_items += [
104
+ ActiveRecord::Relation.new(reflection.klass, table)
105
+ .where(reflection.type => foreign_klass.base_class.name)
106
+ ]
107
+ end
108
+
109
+ scope_chain_items.each do |item|
110
+ unless item.is_a?(Relation)
111
+ item = ActiveRecord::Relation.new(reflection.klass, table).instance_exec(self, &item)
112
+ end
113
+
114
+ constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty?
115
+ end
116
+
117
+ manager.from(join(table, constraint))
118
+
119
+ # The current table in this iteration becomes the foreign table in the next
120
+ foreign_table, foreign_klass = table, reflection.klass
121
+ end
122
+
123
+ manager
124
+ end
125
+
126
+ # Builds equality condition.
127
+ #
128
+ # Example:
129
+ #
130
+ # class Physician < ActiveRecord::Base
131
+ # has_many :appointments
132
+ # end
133
+ #
134
+ # If I execute `Physician.joins(:appointments).to_a` then
135
+ # reflection #=> #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
136
+ # table #=> #<Arel::Table @name="appointments" ...>
137
+ # key #=> physician_id
138
+ # foreign_table #=> #<Arel::Table @name="physicians" ...>
139
+ # foreign_key #=> id
140
+ #
141
+ def build_constraint(reflection, table, key, foreign_table, foreign_key)
142
+ constraint = table[key].eq(foreign_table[foreign_key])
143
+
144
+ if reflection.klass.finder_needs_type_condition?
145
+ constraint = table.create_and([
146
+ constraint,
147
+ reflection.klass.send(:type_condition, table)
148
+ ])
149
+ end
150
+
151
+ constraint
152
+ end
153
+
154
+ def join_relation(joining_relation)
155
+ self.join_type = Arel::OuterJoin
156
+ joining_relation.joins(self)
157
+ end
158
+
159
+ def table
160
+ tables.last
161
+ end
162
+
163
+ def aliased_table_name
164
+ table.table_alias || table.name
165
+ end
166
+
167
+ def scope_chain
168
+ @scope_chain ||= reflection.scope_chain.reverse
169
+ end
170
+
171
+ end
172
+ end
173
+ end
174
+ 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.base_klass == base_klass
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
+ base_klass.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 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 :base_klass
15
+
16
+ delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :base_klass
17
+
18
+ def initialize(base_klass)
19
+ @base_klass = base_klass
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])).compact.each_with_index do |column_name, i|
58
+ @column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
59
+ end
60
+ end
61
+ @column_names_with_alias
62
+ end
63
+
64
+ def extract_record(row)
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)] ||= base_klass.instantiate(extract_record(row))
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end