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.

Files changed (122) hide show
  1. data/CHANGELOG +220 -91
  2. data/README.rdoc +3 -3
  3. data/examples/performance.rb +88 -109
  4. data/lib/active_record.rb +6 -2
  5. data/lib/active_record/aggregations.rb +22 -45
  6. data/lib/active_record/associations.rb +264 -991
  7. data/lib/active_record/associations/alias_tracker.rb +85 -0
  8. data/lib/active_record/associations/association.rb +231 -0
  9. data/lib/active_record/associations/association_scope.rb +120 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +40 -60
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +15 -63
  12. data/lib/active_record/associations/builder/association.rb +53 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +63 -0
  16. data/lib/active_record/associations/builder/has_many.rb +65 -0
  17. data/lib/active_record/associations/builder/has_one.rb +63 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +524 -0
  20. data/lib/active_record/associations/collection_proxy.rb +125 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +27 -118
  22. data/lib/active_record/associations/has_many_association.rb +50 -79
  23. data/lib/active_record/associations/has_many_through_association.rb +98 -67
  24. data/lib/active_record/associations/has_one_association.rb +45 -115
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency.rb +215 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +150 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_helper.rb +56 -0
  31. data/lib/active_record/associations/preloader.rb +177 -0
  32. data/lib/active_record/associations/preloader/association.rb +126 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  42. data/lib/active_record/associations/singular_association.rb +55 -0
  43. data/lib/active_record/associations/through_association.rb +80 -0
  44. data/lib/active_record/attribute_methods.rb +19 -5
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +9 -8
  46. data/lib/active_record/attribute_methods/dirty.rb +8 -2
  47. data/lib/active_record/attribute_methods/primary_key.rb +33 -13
  48. data/lib/active_record/attribute_methods/read.rb +17 -17
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -4
  50. data/lib/active_record/attribute_methods/write.rb +2 -1
  51. data/lib/active_record/autosave_association.rb +66 -45
  52. data/lib/active_record/base.rb +445 -273
  53. data/lib/active_record/callbacks.rb +24 -33
  54. data/lib/active_record/coders/yaml_column.rb +41 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +106 -13
  56. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +16 -2
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -12
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +16 -16
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +61 -22
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -273
  62. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -42
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +44 -25
  64. data/lib/active_record/connection_adapters/column.rb +268 -0
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +686 -0
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +331 -88
  67. data/lib/active_record/connection_adapters/postgresql_adapter.rb +295 -267
  68. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -7
  69. data/lib/active_record/connection_adapters/sqlite_adapter.rb +108 -26
  70. data/lib/active_record/counter_cache.rb +7 -4
  71. data/lib/active_record/fixtures.rb +174 -192
  72. data/lib/active_record/identity_map.rb +131 -0
  73. data/lib/active_record/locking/optimistic.rb +20 -14
  74. data/lib/active_record/locking/pessimistic.rb +4 -4
  75. data/lib/active_record/log_subscriber.rb +24 -4
  76. data/lib/active_record/migration.rb +265 -144
  77. data/lib/active_record/migration/command_recorder.rb +103 -0
  78. data/lib/active_record/named_scope.rb +68 -25
  79. data/lib/active_record/nested_attributes.rb +58 -15
  80. data/lib/active_record/observer.rb +3 -7
  81. data/lib/active_record/persistence.rb +58 -38
  82. data/lib/active_record/query_cache.rb +25 -3
  83. data/lib/active_record/railtie.rb +21 -12
  84. data/lib/active_record/railties/console_sandbox.rb +6 -0
  85. data/lib/active_record/railties/databases.rake +147 -116
  86. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  87. data/lib/active_record/reflection.rb +176 -44
  88. data/lib/active_record/relation.rb +125 -49
  89. data/lib/active_record/relation/batches.rb +7 -5
  90. data/lib/active_record/relation/calculations.rb +50 -18
  91. data/lib/active_record/relation/finder_methods.rb +47 -26
  92. data/lib/active_record/relation/predicate_builder.rb +24 -21
  93. data/lib/active_record/relation/query_methods.rb +117 -101
  94. data/lib/active_record/relation/spawn_methods.rb +27 -20
  95. data/lib/active_record/result.rb +34 -0
  96. data/lib/active_record/schema.rb +5 -6
  97. data/lib/active_record/schema_dumper.rb +11 -13
  98. data/lib/active_record/serialization.rb +2 -2
  99. data/lib/active_record/serializers/xml_serializer.rb +10 -10
  100. data/lib/active_record/session_store.rb +8 -2
  101. data/lib/active_record/test_case.rb +9 -20
  102. data/lib/active_record/timestamp.rb +21 -9
  103. data/lib/active_record/transactions.rb +16 -15
  104. data/lib/active_record/validations.rb +21 -22
  105. data/lib/active_record/validations/associated.rb +3 -1
  106. data/lib/active_record/validations/uniqueness.rb +48 -58
  107. data/lib/active_record/version.rb +3 -3
  108. data/lib/rails/generators/active_record.rb +6 -0
  109. data/lib/rails/generators/active_record/migration/templates/migration.rb +10 -2
  110. data/lib/rails/generators/active_record/model/model_generator.rb +2 -1
  111. data/lib/rails/generators/active_record/model/templates/migration.rb +6 -5
  112. data/lib/rails/generators/active_record/model/templates/model.rb +2 -0
  113. data/lib/rails/generators/active_record/model/templates/module.rb +2 -0
  114. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  115. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +2 -1
  116. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +2 -2
  117. metadata +106 -77
  118. checksums.yaml +0 -7
  119. data/lib/active_record/association_preload.rb +0 -431
  120. data/lib/active_record/associations/association_collection.rb +0 -572
  121. data/lib/active_record/associations/association_proxy.rb +0 -304
  122. data/lib/active_record/associations/through_association_scope.rb +0 -160
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2004-2010 David Heinemeier Hansson
2
+ # Copyright (c) 2004-2011 David Heinemeier Hansson
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -43,7 +43,6 @@ module ActiveRecord
43
43
  autoload :ConnectionNotEstablished, 'active_record/errors'
44
44
 
45
45
  autoload :Aggregations
46
- autoload :AssociationPreload
47
46
  autoload :Associations
48
47
  autoload :AttributeMethods
49
48
  autoload :AutosaveAssociation
@@ -79,6 +78,11 @@ module ActiveRecord
79
78
  autoload :Timestamp
80
79
  autoload :Transactions
81
80
  autoload :Validations
81
+ autoload :IdentityMap
82
+ end
83
+
84
+ module Coders
85
+ autoload :YAMLColumn, 'active_record/coders/yaml_column'
82
86
  end
83
87
 
84
88
  module AttributeMethods
@@ -4,9 +4,7 @@ module ActiveRecord
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  def clear_aggregation_cache #:nodoc:
7
- self.class.reflect_on_all_aggregations.to_a.each do |assoc|
8
- instance_variable_set "@#{assoc.name}", nil
9
- end unless self.new_record?
7
+ @aggregation_cache.clear if persisted?
10
8
  end
11
9
 
12
10
  # Active Record implements aggregation through a macro-like class method called +composed_of+
@@ -48,7 +46,7 @@ module ActiveRecord
48
46
  #
49
47
  # def <=>(other_money)
50
48
  # if currency == other_money.currency
51
- # amount <=> other_money.amount
49
+ # amount <=> amount
52
50
  # else
53
51
  # amount <=> other_money.exchange_to(currency).amount
54
52
  # end
@@ -222,53 +220,32 @@ module ActiveRecord
222
220
 
223
221
  private
224
222
  def reader_method(name, class_name, mapping, allow_nil, constructor)
225
- module_eval do
226
- define_method(name) do |*args|
227
- force_reload = args.first || false
228
-
229
- unless instance_variable_defined?("@#{name}")
230
- instance_variable_set("@#{name}", nil)
231
- end
232
-
233
- if (instance_variable_get("@#{name}").nil? || force_reload) && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
234
- attrs = mapping.collect {|pair| read_attribute(pair.first)}
235
- object = case constructor
236
- when Symbol
237
- class_name.constantize.send(constructor, *attrs)
238
- when Proc, Method
239
- constructor.call(*attrs)
240
- else
241
- raise ArgumentError, 'Constructor must be a symbol denoting the constructor method to call or a Proc to be invoked.'
242
- end
243
- instance_variable_set("@#{name}", object)
244
- end
245
- instance_variable_get("@#{name}")
223
+ define_method(name) do
224
+ if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
225
+ attrs = mapping.collect {|pair| read_attribute(pair.first)}
226
+ object = constructor.respond_to?(:call) ?
227
+ constructor.call(*attrs) :
228
+ class_name.constantize.send(constructor, *attrs)
229
+ @aggregation_cache[name] = object
246
230
  end
231
+ @aggregation_cache[name]
247
232
  end
248
-
249
233
  end
250
234
 
251
235
  def writer_method(name, class_name, mapping, allow_nil, converter)
252
- module_eval do
253
- define_method("#{name}=") do |part|
254
- if part.nil? && allow_nil
255
- mapping.each { |pair| self[pair.first] = nil }
256
- instance_variable_set("@#{name}", nil)
257
- else
258
- unless part.is_a?(class_name.constantize) || converter.nil?
259
- part = case converter
260
- when Symbol
261
- class_name.constantize.send(converter, part)
262
- when Proc, Method
263
- converter.call(part)
264
- else
265
- raise ArgumentError, 'Converter must be a symbol denoting the converter method to call or a Proc to be invoked.'
266
- end
267
- end
268
-
269
- mapping.each { |pair| self[pair.first] = part.send(pair.last) }
270
- instance_variable_set("@#{name}", part.freeze)
236
+ define_method("#{name}=") do |part|
237
+ if part.nil? && allow_nil
238
+ mapping.each { |pair| self[pair.first] = nil }
239
+ @aggregation_cache[name] = nil
240
+ else
241
+ unless part.is_a?(class_name.constantize) || converter.nil?
242
+ part = converter.respond_to?(:call) ?
243
+ converter.call(part) :
244
+ class_name.constantize.send(converter, part)
271
245
  end
246
+
247
+ mapping.each { |pair| self[pair.first] = part.send(pair.last) }
248
+ @aggregation_cache[name] = part.freeze
272
249
  end
273
250
  end
274
251
  end
@@ -4,6 +4,7 @@ require 'active_support/core_ext/module/delegation'
4
4
  require 'active_support/core_ext/object/blank'
5
5
  require 'active_support/core_ext/string/conversions'
6
6
  require 'active_support/core_ext/module/remove_method'
7
+ require 'active_support/core_ext/class/attribute'
7
8
 
8
9
  module ActiveRecord
9
10
  class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
@@ -18,18 +19,30 @@ module ActiveRecord
18
19
  end
19
20
  end
20
21
 
21
- class HasManyThroughAssociationPolymorphicError < ActiveRecordError #:nodoc:
22
+ class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc:
22
23
  def initialize(owner_class_name, reflection, source_reflection)
23
24
  super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.")
24
25
  end
25
26
  end
26
27
 
28
+ class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError #:nodoc:
29
+ def initialize(owner_class_name, reflection)
30
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.")
31
+ end
32
+ end
33
+
27
34
  class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
28
35
  def initialize(owner_class_name, reflection, source_reflection)
29
36
  super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
30
37
  end
31
38
  end
32
39
 
40
+ class HasOneThroughCantAssociateThroughCollection < ActiveRecordError #:nodoc:
41
+ def initialize(owner_class_name, reflection, through_reflection)
42
+ super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.")
43
+ end
44
+ end
45
+
33
46
  class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
34
47
  def initialize(reflection)
35
48
  through_reflection = reflection.through_reflection
@@ -39,14 +52,6 @@ module ActiveRecord
39
52
  end
40
53
  end
41
54
 
42
- class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc:
43
- def initialize(reflection)
44
- through_reflection = reflection.through_reflection
45
- source_reflection = reflection.source_reflection
46
- super("Invalid source reflection macro :#{source_reflection.macro}#{" :through" if source_reflection.options[:through]} for has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}. Use :source to specify the source reflection.")
47
- end
48
- end
49
-
50
55
  class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
51
56
  def initialize(owner, reflection)
52
57
  super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
@@ -65,6 +70,12 @@ module ActiveRecord
65
70
  end
66
71
  end
67
72
 
73
+ class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc
74
+ def initialize(owner, reflection)
75
+ super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
76
+ end
77
+ end
78
+
68
79
  class HasAndBelongsToManyAssociationWithPrimaryKeyError < ActiveRecordError #:nodoc:
69
80
  def initialize(reflection)
70
81
  super("Primary key is not allowed in a has_and_belongs_to_many join table (#{reflection.options[:join_table]}).")
@@ -93,8 +104,8 @@ module ActiveRecord
93
104
  # (has_many, has_one) when there is at least 1 child associated instance.
94
105
  # ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
95
106
  class DeleteRestrictionError < ActiveRecordError #:nodoc:
96
- def initialize(reflection)
97
- super("Cannot delete record because of dependent #{reflection.name}")
107
+ def initialize(name)
108
+ super("Cannot delete record because of dependent #{name}")
98
109
  end
99
110
  end
100
111
 
@@ -104,36 +115,67 @@ module ActiveRecord
104
115
 
105
116
  # These classes will be loaded when associations are created.
106
117
  # So there is no need to eager load them.
107
- autoload :AssociationCollection, 'active_record/associations/association_collection'
108
- autoload :AssociationProxy, 'active_record/associations/association_proxy'
109
- autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
118
+ autoload :Association, 'active_record/associations/association'
119
+ autoload :SingularAssociation, 'active_record/associations/singular_association'
120
+ autoload :CollectionAssociation, 'active_record/associations/collection_association'
121
+ autoload :CollectionProxy, 'active_record/associations/collection_proxy'
122
+
123
+ autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
110
124
  autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
111
- autoload :HasAndBelongsToManyAssociation, 'active_record/associations/has_and_belongs_to_many_association'
112
- autoload :HasManyAssociation, 'active_record/associations/has_many_association'
113
- autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association'
114
- autoload :HasOneAssociation, 'active_record/associations/has_one_association'
115
- autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
125
+ autoload :HasAndBelongsToManyAssociation, 'active_record/associations/has_and_belongs_to_many_association'
126
+ autoload :HasManyAssociation, 'active_record/associations/has_many_association'
127
+ autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association'
128
+ autoload :HasOneAssociation, 'active_record/associations/has_one_association'
129
+ autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
130
+ autoload :ThroughAssociation, 'active_record/associations/through_association'
131
+
132
+ module Builder #:nodoc:
133
+ autoload :Association, 'active_record/associations/builder/association'
134
+ autoload :SingularAssociation, 'active_record/associations/builder/singular_association'
135
+ autoload :CollectionAssociation, 'active_record/associations/builder/collection_association'
136
+
137
+ autoload :BelongsTo, 'active_record/associations/builder/belongs_to'
138
+ autoload :HasOne, 'active_record/associations/builder/has_one'
139
+ autoload :HasMany, 'active_record/associations/builder/has_many'
140
+ autoload :HasAndBelongsToMany, 'active_record/associations/builder/has_and_belongs_to_many'
141
+ end
142
+
143
+ autoload :Preloader, 'active_record/associations/preloader'
144
+ autoload :JoinDependency, 'active_record/associations/join_dependency'
145
+ autoload :AssociationScope, 'active_record/associations/association_scope'
146
+ autoload :AliasTracker, 'active_record/associations/alias_tracker'
147
+ autoload :JoinHelper, 'active_record/associations/join_helper'
116
148
 
117
149
  # Clears out the association cache.
118
150
  def clear_association_cache #:nodoc:
119
- self.class.reflect_on_all_associations.to_a.each do |assoc|
120
- instance_variable_set "@#{assoc.name}", nil
121
- end unless self.new_record?
151
+ @association_cache.clear if persisted?
152
+ end
153
+
154
+ # :nodoc:
155
+ attr_reader :association_cache
156
+
157
+ # Returns the association instance for the given name, instantiating it if it doesn't already exist
158
+ def association(name) #:nodoc:
159
+ association = association_instance_get(name)
160
+
161
+ if association.nil?
162
+ reflection = self.class.reflect_on_association(name)
163
+ association = reflection.association_class.new(self, reflection)
164
+ association_instance_set(name, association)
165
+ end
166
+
167
+ association
122
168
  end
123
169
 
124
170
  private
125
171
  # Returns the specified association instance if it responds to :loaded?, nil otherwise.
126
172
  def association_instance_get(name)
127
- ivar = "@#{name}"
128
- if instance_variable_defined?(ivar)
129
- association = instance_variable_get(ivar)
130
- association if association.respond_to?(:loaded?)
131
- end
173
+ @association_cache[name.to_sym]
132
174
  end
133
175
 
134
176
  # Set the specified association instance.
135
177
  def association_instance_set(name, association)
136
- instance_variable_set("@#{name}", association)
178
+ @association_cache[name] = association
137
179
  end
138
180
 
139
181
  # Associations are a set of macro-like class methods for tying objects together through
@@ -177,7 +219,7 @@ module ActiveRecord
177
219
  # other=(other) | X | X | X
178
220
  # build_other(attributes={}) | X | | X
179
221
  # create_other(attributes={}) | X | | X
180
- # other.create!(attributes={}) | | | X
222
+ # create_other!(attributes={}) | X | | X
181
223
  #
182
224
  # ===Collection associations (one-to-many / many-to-many)
183
225
  # | | | has_many
@@ -200,10 +242,9 @@ module ActiveRecord
200
242
  # others.empty? | X | X | X
201
243
  # others.clear | X | X | X
202
244
  # others.delete(other,other,...) | X | X | X
203
- # others.delete_all | X | X |
245
+ # others.delete_all | X | X | X
204
246
  # others.destroy_all | X | X | X
205
247
  # others.find(*args) | X | X | X
206
- # others.find_first | X | |
207
248
  # others.exists? | X | X | X
208
249
  # others.uniq | X | X | X
209
250
  # others.reset | X | X | X
@@ -317,26 +358,31 @@ module ActiveRecord
317
358
  # === One-to-one associations
318
359
  #
319
360
  # * Assigning an object to a +has_one+ association automatically saves that object and
320
- # the object being replaced (if there is one), in order to update their primary
321
- # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
322
- # * If either of these saves fail (due to one of the objects being invalid) the assignment
323
- # statement returns +false+ and the assignment is cancelled.
361
+ # the object being replaced (if there is one), in order to update their foreign
362
+ # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
363
+ # * If either of these saves fail (due to one of the objects being invalid), an
364
+ # <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is
365
+ # cancelled.
324
366
  # * If you wish to assign an object to a +has_one+ association without saving it,
325
- # use the <tt>association.build</tt> method (documented below).
367
+ # use the <tt>build_association</tt> method (documented below). The object being
368
+ # replaced will still be saved to update its foreign key.
326
369
  # * Assigning an object to a +belongs_to+ association does not save the object, since
327
- # the foreign key field belongs on the parent. It does not save the parent either.
370
+ # the foreign key field belongs on the parent. It does not save the parent either.
328
371
  #
329
372
  # === Collections
330
373
  #
331
374
  # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically
332
- # saves that object, except if the parent object (the owner of the collection) is not yet
333
- # stored in the database.
375
+ # saves that object, except if the parent object (the owner of the collection) is not yet
376
+ # stored in the database.
334
377
  # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar)
335
- # fails, then <tt>push</tt> returns +false+.
378
+ # fails, then <tt>push</tt> returns +false+.
379
+ # * If saving fails while replacing the collection (via <tt>association=</tt>), an
380
+ # <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is
381
+ # cancelled.
336
382
  # * You can add an object to a collection without automatically saving it by using the
337
- # <tt>collection.build</tt> method (documented below).
383
+ # <tt>collection.build</tt> method (documented below).
338
384
  # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically
339
- # saved when the parent is saved.
385
+ # saved when the parent is saved.
340
386
  #
341
387
  # === Association callbacks
342
388
  #
@@ -487,6 +533,65 @@ module ActiveRecord
487
533
  # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
488
534
  # @group.avatars.delete(@group.avatars.last) # so would this
489
535
  #
536
+ # If you are using a +belongs_to+ on the join model, it is a good idea to set the
537
+ # <tt>:inverse_of</tt> option on the +belongs_to+, which will mean that the following example
538
+ # works correctly (where <tt>tags</tt> is a +has_many+ <tt>:through</tt> association):
539
+ #
540
+ # @post = Post.first
541
+ # @tag = @post.tags.build :name => "ruby"
542
+ # @tag.save
543
+ #
544
+ # The last line ought to save the through record (a <tt>Taggable</tt>). This will only work if the
545
+ # <tt>:inverse_of</tt> is set:
546
+ #
547
+ # class Taggable < ActiveRecord::Base
548
+ # belongs_to :post
549
+ # belongs_to :tag, :inverse_of => :taggings
550
+ # end
551
+ #
552
+ # === Nested Associations
553
+ #
554
+ # You can actually specify *any* association with the <tt>:through</tt> option, including an
555
+ # association which has a <tt>:through</tt> option itself. For example:
556
+ #
557
+ # class Author < ActiveRecord::Base
558
+ # has_many :posts
559
+ # has_many :comments, :through => :posts
560
+ # has_many :commenters, :through => :comments
561
+ # end
562
+ #
563
+ # class Post < ActiveRecord::Base
564
+ # has_many :comments
565
+ # end
566
+ #
567
+ # class Comment < ActiveRecord::Base
568
+ # belongs_to :commenter
569
+ # end
570
+ #
571
+ # @author = Author.first
572
+ # @author.commenters # => People who commented on posts written by the author
573
+ #
574
+ # An equivalent way of setting up this association this would be:
575
+ #
576
+ # class Author < ActiveRecord::Base
577
+ # has_many :posts
578
+ # has_many :commenters, :through => :posts
579
+ # end
580
+ #
581
+ # class Post < ActiveRecord::Base
582
+ # has_many :comments
583
+ # has_many :commenters, :through => :comments
584
+ # end
585
+ #
586
+ # class Comment < ActiveRecord::Base
587
+ # belongs_to :commenter
588
+ # end
589
+ #
590
+ # When using nested association, you will not be able to modify the association because there
591
+ # is not enough information to know what modification to make. For example, if you tried to
592
+ # add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the
593
+ # intermediate <tt>Post</tt> and <tt>Comment</tt> objects.
594
+ #
490
595
  # === Polymorphic Associations
491
596
  #
492
597
  # Polymorphic associations on models are not restricted on what types of models they
@@ -756,7 +861,7 @@ module ActiveRecord
756
861
  # belongs_to :dungeon
757
862
  # end
758
863
  #
759
- # The +traps+ association on +Dungeon+ and the the +dungeon+ association on +Trap+ are
864
+ # The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are
760
865
  # the inverse of each other and the inverse of the +dungeon+ association on +EvilWizard+
761
866
  # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
762
867
  # Active Record doesn't know anything about these inverse relationships and so no object
@@ -796,6 +901,73 @@ module ActiveRecord
796
901
  # * does not work with <tt>:polymorphic</tt> associations.
797
902
  # * for +belongs_to+ associations +has_many+ inverse associations are ignored.
798
903
  #
904
+ # == Deleting from associations
905
+ #
906
+ # === Dependent associations
907
+ #
908
+ # +has_many+, +has_one+ and +belongs_to+ associations support the <tt>:dependent</tt> option.
909
+ # This allows you to specify that associated records should be deleted when the owner is
910
+ # deleted.
911
+ #
912
+ # For example:
913
+ #
914
+ # class Author
915
+ # has_many :posts, :dependent => :destroy
916
+ # end
917
+ # Author.find(1).destroy # => Will destroy all of the author's posts, too
918
+ #
919
+ # The <tt>:dependent</tt> option can have different values which specify how the deletion
920
+ # is done. For more information, see the documentation for this option on the different
921
+ # specific association types.
922
+ #
923
+ # === Delete or destroy?
924
+ #
925
+ # +has_many+ and +has_and_belongs_to_many+ associations have the methods <tt>destroy</tt>,
926
+ # <tt>delete</tt>, <tt>destroy_all</tt> and <tt>delete_all</tt>.
927
+ #
928
+ # For +has_and_belongs_to_many+, <tt>delete</tt> and <tt>destroy</tt> are the same: they
929
+ # cause the records in the join table to be removed.
930
+ #
931
+ # For +has_many+, <tt>destroy</tt> will always call the <tt>destroy</tt> method of the
932
+ # record(s) being removed so that callbacks are run. However <tt>delete</tt> will either
933
+ # do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or
934
+ # if no <tt>:dependent</tt> option is given, then it will follow the default strategy.
935
+ # The default strategy is <tt>:nullify</tt> (set the foreign keys to <tt>nil</tt>), except for
936
+ # +has_many+ <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete
937
+ # the join records, without running their callbacks).
938
+ #
939
+ # There is also a <tt>clear</tt> method which is the same as <tt>delete_all</tt>, except that
940
+ # it returns the association rather than the records which have been deleted.
941
+ #
942
+ # === What gets deleted?
943
+ #
944
+ # There is a potential pitfall here: +has_and_belongs_to_many+ and +has_many+ <tt>:through</tt>
945
+ # associations have records in join tables, as well as the associated records. So when we
946
+ # call one of these deletion methods, what exactly should be deleted?
947
+ #
948
+ # The answer is that it is assumed that deletion on an association is about removing the
949
+ # <i>link</i> between the owner and the associated object(s), rather than necessarily the
950
+ # associated objects themselves. So with +has_and_belongs_to_many+ and +has_many+
951
+ # <tt>:through</tt>, the join records will be deleted, but the associated records won't.
952
+ #
953
+ # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by_name('food'))</tt>
954
+ # you would want the 'food' tag to be unlinked from the post, rather than for the tag itself
955
+ # to be removed from the database.
956
+ #
957
+ # However, there are examples where this strategy doesn't make sense. For example, suppose
958
+ # a person has many projects, and each project has many tasks. If we deleted one of a person's
959
+ # tasks, we would probably not want the project to be deleted. In this scenario, the delete method
960
+ # won't actually work: it can only be used if the association on the join model is a
961
+ # +belongs_to+. In other situations you are expected to perform operations directly on
962
+ # either the associated records or the <tt>:through</tt> association.
963
+ #
964
+ # With a regular +has_many+ there is no distinction between the "associated records"
965
+ # and the "link", so there is only one choice for what gets deleted.
966
+ #
967
+ # With +has_and_belongs_to_many+ and +has_many+ <tt>:through</tt>, if you want to delete the
968
+ # associated records themselves, you can always do something along the lines of
969
+ # <tt>person.tasks.each(&:destroy)</tt>.
970
+ #
799
971
  # == Type safety with <tt>ActiveRecord::AssociationTypeMismatch</tt>
800
972
  #
801
973
  # If you attempt to assign an object to an association that doesn't match the inferred
@@ -820,6 +992,10 @@ module ActiveRecord
820
992
  # Removes one or more objects from the collection by setting their foreign keys to +NULL+.
821
993
  # Objects will be in addition destroyed if they're associated with <tt>:dependent => :destroy</tt>,
822
994
  # and deleted if they're associated with <tt>:dependent => :delete_all</tt>.
995
+ #
996
+ # If the <tt>:through</tt> option is used, then the join records are deleted (rather than
997
+ # nullified) by default, but you can specify <tt>:dependent => :destroy</tt> or
998
+ # <tt>:dependent => :nullify</tt> to override this.
823
999
  # [collection=objects]
824
1000
  # Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt>
825
1001
  # option is true callbacks in the join models are triggered except destroy callbacks, since deletion is
@@ -875,7 +1051,7 @@ module ActiveRecord
875
1051
  # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
876
1052
  # The declaration can also include an options hash to specialize the behavior of the association.
877
1053
  #
878
- # === Supported options
1054
+ # === Options
879
1055
  # [:class_name]
880
1056
  # Specify the class name of the association. Use it only if that name can't be inferred
881
1057
  # from the association name. So <tt>has_many :products</tt> will by default be linked
@@ -903,7 +1079,9 @@ module ActiveRecord
903
1079
  # objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. If set to
904
1080
  # <tt>:restrict</tt> this object cannot be deleted if it has any associated object.
905
1081
  #
906
- # *Warning:* This option is ignored when used with <tt>:through</tt> option.
1082
+ # If using with the <tt>:through</tt> option, the association on the join model must be
1083
+ # a +belongs_to+, and the records which get deleted are the join records, rather than
1084
+ # the associated records.
907
1085
  #
908
1086
  # [:finder_sql]
909
1087
  # Specify a complete SQL statement to fetch the association. This is a good way to go for complex
@@ -934,13 +1112,21 @@ module ActiveRecord
934
1112
  # [:as]
935
1113
  # Specifies a polymorphic interface (See <tt>belongs_to</tt>).
936
1114
  # [:through]
937
- # Specifies a join model through which to perform the query. Options for <tt>:class_name</tt>
938
- # and <tt>:foreign_key</tt> are ignored, as the association uses the source reflection. You
939
- # can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>, <tt>has_one</tt>
940
- # or <tt>has_many</tt> association on the join model. The collection of join models
941
- # can be managed via the collection API. For example, new join models are created for
942
- # newly associated objects, and if some are gone their rows are deleted (directly,
943
- # no destroy callbacks are triggered).
1115
+ # Specifies an association through which to perform the query. This can be any other type
1116
+ # of association, including other <tt>:through</tt> associations. Options for <tt>:class_name</tt>,
1117
+ # <tt>:primary_key</tt> and <tt>:foreign_key</tt> are ignored, as the association uses the
1118
+ # source reflection.
1119
+ #
1120
+ # If the association on the join model is a +belongs_to+, the collection can be modified
1121
+ # and the records on the <tt>:through</tt> model will be automatically created and removed
1122
+ # as appropriate. Otherwise, the collection is read-only, so you should manipulate the
1123
+ # <tt>:through</tt> association directly.
1124
+ #
1125
+ # If you are going to modify the association (rather than just read from it), then it is
1126
+ # a good idea to set the <tt>:inverse_of</tt> option on the source association on the
1127
+ # join model. This allows associated records to be built which will automatically create
1128
+ # the appropriate join model records when they are saved. (See the 'Association Join Models'
1129
+ # section above.)
944
1130
  # [:source]
945
1131
  # Specifies the source association name used by <tt>has_many :through</tt> queries.
946
1132
  # Only use it if the name cannot be inferred from the association.
@@ -979,16 +1165,8 @@ module ActiveRecord
979
1165
  # 'FROM people p, post_subscriptions ps ' +
980
1166
  # 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
981
1167
  # 'ORDER BY p.first_name'
982
- def has_many(association_id, options = {}, &extension)
983
- reflection = create_has_many_reflection(association_id, options, &extension)
984
- configure_dependency_for_has_many(reflection)
985
- add_association_callbacks(reflection.name, reflection.options)
986
-
987
- if options[:through]
988
- collection_accessor_methods(reflection, HasManyThroughAssociation)
989
- else
990
- collection_accessor_methods(reflection, HasManyAssociation)
991
- end
1168
+ def has_many(name, options = {}, &extension)
1169
+ Builder::HasMany.build(self, name, options, &extension)
992
1170
  end
993
1171
 
994
1172
  # Specifies a one-to-one association with another class. This method should only be used
@@ -1006,12 +1184,14 @@ module ActiveRecord
1006
1184
  # [build_association(attributes = {})]
1007
1185
  # Returns a new object of the associated type that has been instantiated
1008
1186
  # with +attributes+ and linked to this object through a foreign key, but has not
1009
- # yet been saved. <b>Note:</b> This ONLY works if an association already exists.
1010
- # It will NOT work if the association is +nil+.
1187
+ # yet been saved.
1011
1188
  # [create_association(attributes = {})]
1012
1189
  # Returns a new object of the associated type that has been instantiated
1013
1190
  # with +attributes+, linked to this object through a foreign key, and that
1014
1191
  # has already been saved (if it passed the validation).
1192
+ # [create_association!(attributes = {})]
1193
+ # Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
1194
+ # if the record is invalid.
1015
1195
  #
1016
1196
  # (+association+ is replaced with the symbol passed as the first argument, so
1017
1197
  # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.)
@@ -1023,6 +1203,7 @@ module ActiveRecord
1023
1203
  # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
1024
1204
  # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
1025
1205
  # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
1206
+ # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>)
1026
1207
  #
1027
1208
  # === Options
1028
1209
  #
@@ -1061,10 +1242,10 @@ module ActiveRecord
1061
1242
  # you want to do a join but not include the joined columns. Do not forget to include the
1062
1243
  # primary and foreign keys, otherwise it will raise an error.
1063
1244
  # [:through]
1064
- # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>
1065
- # and <tt>:foreign_key</tt> are ignored, as the association uses the source reflection. You
1066
- # can only use a <tt>:through</tt> query through a <tt>has_one</tt> or <tt>belongs_to</tt>
1067
- # association on the join model.
1245
+ # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>,
1246
+ # <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the
1247
+ # source reflection. You can only use a <tt>:through</tt> query through a <tt>has_one</tt>
1248
+ # or <tt>belongs_to</tt> association on the join model.
1068
1249
  # [:source]
1069
1250
  # Specifies the source association name used by <tt>has_one :through</tt> queries.
1070
1251
  # Only use it if the name cannot be inferred from the association.
@@ -1097,17 +1278,8 @@ module ActiveRecord
1097
1278
  # has_one :boss, :readonly => :true
1098
1279
  # has_one :club, :through => :membership
1099
1280
  # has_one :primary_address, :through => :addressables, :conditions => ["addressable.primary = ?", true], :source => :addressable
1100
- def has_one(association_id, options = {})
1101
- if options[:through]
1102
- reflection = create_has_one_through_reflection(association_id, options)
1103
- association_accessor_methods(reflection, ActiveRecord::Associations::HasOneThroughAssociation)
1104
- else
1105
- reflection = create_has_one_reflection(association_id, options)
1106
- association_accessor_methods(reflection, HasOneAssociation)
1107
- association_constructor_method(:build, reflection, HasOneAssociation)
1108
- association_constructor_method(:create, reflection, HasOneAssociation)
1109
- configure_dependency_for_has_one(reflection)
1110
- end
1281
+ def has_one(name, options = {})
1282
+ Builder::HasOne.build(self, name, options)
1111
1283
  end
1112
1284
 
1113
1285
  # Specifies a one-to-one association with another class. This method should only be used
@@ -1129,6 +1301,9 @@ module ActiveRecord
1129
1301
  # Returns a new object of the associated type that has been instantiated
1130
1302
  # with +attributes+, linked to this object through a foreign key, and that
1131
1303
  # has already been saved (if it passed the validation).
1304
+ # [create_association!(attributes = {})]
1305
+ # Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
1306
+ # if the record is invalid.
1132
1307
  #
1133
1308
  # (+association+ is replaced with the symbol passed as the first argument, so
1134
1309
  # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.)
@@ -1140,6 +1315,7 @@ module ActiveRecord
1140
1315
  # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
1141
1316
  # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
1142
1317
  # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
1318
+ # * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>)
1143
1319
  # The declaration can also include an options hash to specialize the behavior of the association.
1144
1320
  #
1145
1321
  # === Options
@@ -1161,6 +1337,11 @@ module ActiveRecord
1161
1337
  # association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly,
1162
1338
  # <tt>belongs_to :favorite_person, :class_name => "Person"</tt> will use a foreign key
1163
1339
  # of "favorite_person_id".
1340
+ # [:foreign_type]
1341
+ # Specify the column used to store the associated object's type, if this is a polymorphic
1342
+ # association. By default this is guessed to be the name of the association with a "_type"
1343
+ # suffix. So a class that defines a <tt>belongs_to :taggable, :polymorphic => true</tt>
1344
+ # association will use "taggable_type" as the default <tt>:foreign_type</tt>.
1164
1345
  # [:primary_key]
1165
1346
  # Specify the method that returns the primary key of associated object used for the association.
1166
1347
  # By default this is id.
@@ -1216,21 +1397,8 @@ module ActiveRecord
1216
1397
  # belongs_to :post, :counter_cache => true
1217
1398
  # belongs_to :company, :touch => true
1218
1399
  # belongs_to :company, :touch => :employees_last_updated_at
1219
- def belongs_to(association_id, options = {})
1220
- reflection = create_belongs_to_reflection(association_id, options)
1221
-
1222
- if reflection.options[:polymorphic]
1223
- association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
1224
- else
1225
- association_accessor_methods(reflection, BelongsToAssociation)
1226
- association_constructor_method(:build, reflection, BelongsToAssociation)
1227
- association_constructor_method(:create, reflection, BelongsToAssociation)
1228
- end
1229
-
1230
- add_counter_cache_callbacks(reflection) if options[:counter_cache]
1231
- add_touch_callbacks(reflection, options[:touch]) if options[:touch]
1232
-
1233
- configure_dependency_for_belongs_to(reflection)
1400
+ def belongs_to(name, options = {})
1401
+ Builder::BelongsTo.build(self, name, options)
1234
1402
  end
1235
1403
 
1236
1404
  # Specifies a many-to-many relationship with another class. This associates two classes via an
@@ -1261,12 +1429,6 @@ module ActiveRecord
1261
1429
  # end
1262
1430
  # end
1263
1431
  #
1264
- # Deprecated: Any additional fields added to the join table will be placed as attributes when
1265
- # pulling records out through +has_and_belongs_to_many+ associations. Records returned from join
1266
- # tables with additional attributes will be marked as readonly (because we can't save changes
1267
- # to the additional attributes). It's strongly recommended that you upgrade any
1268
- # associations with attributes to a real join model (see introduction).
1269
- #
1270
1432
  # Adds the following methods for retrieval and query:
1271
1433
  #
1272
1434
  # [collection(force_reload = false)]
@@ -1407,898 +1569,9 @@ module ActiveRecord
1407
1569
  # has_and_belongs_to_many :categories, :readonly => true
1408
1570
  # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
1409
1571
  # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
1410
- def has_and_belongs_to_many(association_id, options = {}, &extension)
1411
- reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
1412
- collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
1413
-
1414
- configure_destroy_hook_for_has_and_belongs_to_many(reflection)
1415
-
1416
- add_association_callbacks(reflection.name, options)
1572
+ def has_and_belongs_to_many(name, options = {}, &extension)
1573
+ Builder::HasAndBelongsToMany.build(self, name, options, &extension)
1417
1574
  end
1418
-
1419
- private
1420
- # Generates a join table name from two provided table names.
1421
- # The names in the join table names end up in lexicographic order.
1422
- #
1423
- # join_table_name("members", "clubs") # => "clubs_members"
1424
- # join_table_name("members", "special_clubs") # => "members_special_clubs"
1425
- def join_table_name(first_table_name, second_table_name)
1426
- if first_table_name < second_table_name
1427
- join_table = "#{first_table_name}_#{second_table_name}"
1428
- else
1429
- join_table = "#{second_table_name}_#{first_table_name}"
1430
- end
1431
-
1432
- table_name_prefix + join_table + table_name_suffix
1433
- end
1434
-
1435
- def association_accessor_methods(reflection, association_proxy_class)
1436
- redefine_method(reflection.name) do |*params|
1437
- force_reload = params.first unless params.empty?
1438
- association = association_instance_get(reflection.name)
1439
-
1440
- if association.nil? || force_reload
1441
- association = association_proxy_class.new(self, reflection)
1442
- retval = force_reload ? reflection.klass.uncached { association.reload } : association._load
1443
- if retval.nil? and association_proxy_class == BelongsToAssociation
1444
- association_instance_set(reflection.name, nil)
1445
- return nil
1446
- end
1447
- association_instance_set(reflection.name, association)
1448
- end
1449
-
1450
- association.target.nil? ? nil : association
1451
- end
1452
-
1453
- redefine_method("loaded_#{reflection.name}?") do
1454
- association = association_instance_get(reflection.name)
1455
- association && association.loaded?
1456
- end
1457
-
1458
- redefine_method("#{reflection.name}=") do |new_value|
1459
- association = association_instance_get(reflection.name)
1460
-
1461
- if association.nil? || association.target != new_value
1462
- association = association_proxy_class.new(self, reflection)
1463
- end
1464
-
1465
- association.replace(new_value)
1466
- association_instance_set(reflection.name, new_value.nil? ? nil : association)
1467
- end
1468
-
1469
- redefine_method("set_#{reflection.name}_target") do |target|
1470
- return if target.nil? and association_proxy_class == BelongsToAssociation
1471
- association = association_proxy_class.new(self, reflection)
1472
- association.target = target
1473
- association_instance_set(reflection.name, association)
1474
- end
1475
- end
1476
-
1477
- def collection_reader_method(reflection, association_proxy_class)
1478
- redefine_method(reflection.name) do |*params|
1479
- force_reload = params.first unless params.empty?
1480
- association = association_instance_get(reflection.name)
1481
-
1482
- unless association
1483
- association = association_proxy_class.new(self, reflection)
1484
- association_instance_set(reflection.name, association)
1485
- end
1486
-
1487
- reflection.klass.uncached { association.reload } if force_reload
1488
-
1489
- association
1490
- end
1491
-
1492
- redefine_method("#{reflection.name.to_s.singularize}_ids") do
1493
- if send(reflection.name).loaded? || reflection.options[:finder_sql]
1494
- send(reflection.name).map { |r| r.id }
1495
- else
1496
- if reflection.through_reflection && reflection.source_reflection.belongs_to?
1497
- through = reflection.through_reflection
1498
- primary_key = reflection.source_reflection.primary_key_name
1499
- send(through.name).select("DISTINCT #{through.quoted_table_name}.#{primary_key}").map! { |r| r.send(primary_key) }
1500
- else
1501
- send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").except(:includes).map! { |r| r.id }
1502
- end
1503
- end
1504
- end
1505
-
1506
- end
1507
-
1508
- def collection_accessor_methods(reflection, association_proxy_class, writer = true)
1509
- collection_reader_method(reflection, association_proxy_class)
1510
-
1511
- if writer
1512
- redefine_method("#{reflection.name}=") do |new_value|
1513
- # Loads proxy class instance (defined in collection_reader_method) if not already loaded
1514
- association = send(reflection.name)
1515
- association.replace(new_value)
1516
- association
1517
- end
1518
-
1519
- redefine_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
1520
- pk_column = reflection.primary_key_column
1521
- ids = (new_value || []).reject { |nid| nid.blank? }
1522
- ids.map!{ |i| pk_column.type_cast(i) }
1523
- send("#{reflection.name}=", reflection.klass.find(ids).index_by{ |r| r.id }.values_at(*ids))
1524
- end
1525
- end
1526
- end
1527
-
1528
- def association_constructor_method(constructor, reflection, association_proxy_class)
1529
- redefine_method("#{constructor}_#{reflection.name}") do |*params|
1530
- attributees = params.first unless params.empty?
1531
- replace_existing = params[1].nil? ? true : params[1]
1532
- association = association_instance_get(reflection.name)
1533
-
1534
- unless association
1535
- association = association_proxy_class.new(self, reflection)
1536
- association_instance_set(reflection.name, association)
1537
- end
1538
-
1539
- if association_proxy_class == HasOneAssociation
1540
- association.send(constructor, attributees, replace_existing)
1541
- else
1542
- association.send(constructor, attributees)
1543
- end
1544
- end
1545
- end
1546
-
1547
- def add_counter_cache_callbacks(reflection)
1548
- cache_column = reflection.counter_cache_column
1549
-
1550
- method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
1551
- define_method(method_name) do
1552
- association = send(reflection.name)
1553
- association.class.increment_counter(cache_column, association.id) unless association.nil?
1554
- end
1555
- after_create(method_name)
1556
-
1557
- method_name = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".to_sym
1558
- define_method(method_name) do
1559
- association = send(reflection.name)
1560
- association.class.decrement_counter(cache_column, association.id) unless association.nil?
1561
- end
1562
- before_destroy(method_name)
1563
-
1564
- module_eval(
1565
- "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)", __FILE__, __LINE__
1566
- )
1567
- end
1568
-
1569
- def add_touch_callbacks(reflection, touch_attribute)
1570
- method_name = :"belongs_to_touch_after_save_or_destroy_for_#{reflection.name}"
1571
- redefine_method(method_name) do
1572
- association = send(reflection.name)
1573
-
1574
- if touch_attribute == true
1575
- association.touch unless association.nil?
1576
- else
1577
- association.touch(touch_attribute) unless association.nil?
1578
- end
1579
- end
1580
- after_save(method_name)
1581
- after_touch(method_name)
1582
- after_destroy(method_name)
1583
- end
1584
-
1585
- # Creates before_destroy callback methods that nullify, delete or destroy
1586
- # has_many associated objects, according to the defined :dependent rule.
1587
- # If the association is marked as :dependent => :restrict, create a callback
1588
- # that prevents deleting entirely.
1589
- #
1590
- # See HasManyAssociation#delete_records. Dependent associations
1591
- # delete children, otherwise foreign key is set to NULL.
1592
- # See HasManyAssociation#delete_records. Dependent associations
1593
- # delete children if the option is set to :destroy or :delete_all, set the
1594
- # foreign key to NULL if the option is set to :nullify, and do not touch the
1595
- # child records if the option is set to :restrict.
1596
- #
1597
- # The +extra_conditions+ parameter, which is not used within the main
1598
- # Active Record codebase, is meant to allow plugins to define extra
1599
- # finder conditions.
1600
- def configure_dependency_for_has_many(reflection, extra_conditions = nil)
1601
- if reflection.options.include?(:dependent)
1602
- case reflection.options[:dependent]
1603
- when :destroy
1604
- method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
1605
- define_method(method_name) do
1606
- send(reflection.name).each do |o|
1607
- # No point in executing the counter update since we're going to destroy the parent anyway
1608
- counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym
1609
- if(o.respond_to? counter_method) then
1610
- class << o
1611
- self
1612
- end.send(:define_method, counter_method, Proc.new {})
1613
- end
1614
- o.destroy
1615
- end
1616
- end
1617
- before_destroy method_name
1618
- when :delete_all
1619
- before_destroy do |record|
1620
- self.class.send(:delete_all_has_many_dependencies,
1621
- record,
1622
- reflection.name,
1623
- reflection.klass,
1624
- reflection.dependent_conditions(record, self.class, extra_conditions))
1625
- end
1626
- when :nullify
1627
- before_destroy do |record|
1628
- self.class.send(:nullify_has_many_dependencies,
1629
- record,
1630
- reflection.name,
1631
- reflection.klass,
1632
- reflection.primary_key_name,
1633
- reflection.dependent_conditions(record, self.class, extra_conditions))
1634
- end
1635
- when :restrict
1636
- method_name = "has_many_dependent_restrict_for_#{reflection.name}".to_sym
1637
- define_method(method_name) do
1638
- unless send(reflection.name).empty?
1639
- raise DeleteRestrictionError.new(reflection)
1640
- end
1641
- end
1642
- before_destroy method_name
1643
- else
1644
- raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, :nullify or :restrict (#{reflection.options[:dependent].inspect})"
1645
- end
1646
- end
1647
- end
1648
-
1649
- # Creates before_destroy callback methods that nullify, delete or destroy
1650
- # has_one associated objects, according to the defined :dependent rule.
1651
- # If the association is marked as :dependent => :restrict, create a callback
1652
- # that prevents deleting entirely.
1653
- def configure_dependency_for_has_one(reflection)
1654
- if reflection.options.include?(:dependent)
1655
- name = reflection.options[:dependent]
1656
- method_name = :"has_one_dependent_#{name}_for_#{reflection.name}"
1657
-
1658
- case name
1659
- when :destroy, :delete
1660
- class_eval <<-eoruby, __FILE__, __LINE__ + 1
1661
- def #{method_name}
1662
- association = #{reflection.name}
1663
- association.#{name} if association
1664
- end
1665
- eoruby
1666
- when :nullify
1667
- class_eval <<-eoruby, __FILE__, __LINE__ + 1
1668
- def #{method_name}
1669
- association = #{reflection.name}
1670
- association.update_attribute(#{reflection.primary_key_name.inspect}, nil) if association
1671
- end
1672
- eoruby
1673
- when :restrict
1674
- method_name = "has_one_dependent_restrict_for_#{reflection.name}".to_sym
1675
- define_method(method_name) do
1676
- unless send(reflection.name).nil?
1677
- raise DeleteRestrictionError.new(reflection)
1678
- end
1679
- end
1680
- before_destroy method_name
1681
- else
1682
- raise ArgumentError, "The :dependent option expects either :destroy, :delete, :nullify or :restrict (#{reflection.options[:dependent].inspect})"
1683
- end
1684
-
1685
- before_destroy method_name
1686
- end
1687
- end
1688
-
1689
- def configure_dependency_for_belongs_to(reflection)
1690
- if reflection.options.include?(:dependent)
1691
- name = reflection.options[:dependent]
1692
-
1693
- unless [:destroy, :delete].include?(name)
1694
- raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{reflection.options[:dependent].inspect})"
1695
- end
1696
-
1697
- method_name = :"belongs_to_dependent_#{name}_for_#{reflection.name}"
1698
- class_eval <<-eoruby, __FILE__, __LINE__ + 1
1699
- def #{method_name}
1700
- association = #{reflection.name}
1701
- association.#{name} if association
1702
- end
1703
- eoruby
1704
- after_destroy method_name
1705
- end
1706
- end
1707
-
1708
- def configure_destroy_hook_for_has_and_belongs_to_many(reflection)
1709
- include(Module.new {
1710
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
1711
- def destroy_associations
1712
- association = #{reflection.name}
1713
- association.delete_all if association
1714
- super
1715
- end
1716
- RUBY
1717
- })
1718
- end
1719
-
1720
- def delete_all_has_many_dependencies(record, reflection_name, association_class, dependent_conditions)
1721
- association_class.delete_all(dependent_conditions)
1722
- end
1723
-
1724
- def nullify_has_many_dependencies(record, reflection_name, association_class, primary_key_name, dependent_conditions)
1725
- association_class.update_all("#{primary_key_name} = NULL", dependent_conditions)
1726
- end
1727
-
1728
- mattr_accessor :valid_keys_for_has_many_association
1729
- @@valid_keys_for_has_many_association = [
1730
- :class_name, :table_name, :foreign_key, :primary_key,
1731
- :dependent,
1732
- :select, :conditions, :include, :order, :group, :having, :limit, :offset,
1733
- :as, :through, :source, :source_type,
1734
- :uniq,
1735
- :finder_sql, :counter_sql,
1736
- :before_add, :after_add, :before_remove, :after_remove,
1737
- :extend, :readonly,
1738
- :validate, :inverse_of
1739
- ]
1740
-
1741
- def create_has_many_reflection(association_id, options, &extension)
1742
- options.assert_valid_keys(valid_keys_for_has_many_association)
1743
- options[:extend] = create_extension_modules(association_id, extension, options[:extend])
1744
-
1745
- create_reflection(:has_many, association_id, options, self)
1746
- end
1747
-
1748
- mattr_accessor :valid_keys_for_has_one_association
1749
- @@valid_keys_for_has_one_association = [
1750
- :class_name, :foreign_key, :remote, :select, :conditions, :order,
1751
- :include, :dependent, :counter_cache, :extend, :as, :readonly,
1752
- :validate, :primary_key, :inverse_of
1753
- ]
1754
-
1755
- def create_has_one_reflection(association_id, options)
1756
- options.assert_valid_keys(valid_keys_for_has_one_association)
1757
- create_reflection(:has_one, association_id, options, self)
1758
- end
1759
-
1760
- def create_has_one_through_reflection(association_id, options)
1761
- options.assert_valid_keys(
1762
- :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate
1763
- )
1764
- create_reflection(:has_one, association_id, options, self)
1765
- end
1766
-
1767
- mattr_accessor :valid_keys_for_belongs_to_association
1768
- @@valid_keys_for_belongs_to_association = [
1769
- :class_name, :primary_key, :foreign_key, :foreign_type, :remote, :select, :conditions,
1770
- :include, :dependent, :counter_cache, :extend, :polymorphic, :readonly,
1771
- :validate, :touch, :inverse_of
1772
- ]
1773
-
1774
- def create_belongs_to_reflection(association_id, options)
1775
- options.assert_valid_keys(valid_keys_for_belongs_to_association)
1776
- reflection = create_reflection(:belongs_to, association_id, options, self)
1777
-
1778
- if options[:polymorphic]
1779
- reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
1780
- end
1781
-
1782
- reflection
1783
- end
1784
-
1785
- mattr_accessor :valid_keys_for_has_and_belongs_to_many_association
1786
- @@valid_keys_for_has_and_belongs_to_many_association = [
1787
- :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
1788
- :select, :conditions, :include, :order, :group, :having, :limit, :offset,
1789
- :uniq,
1790
- :finder_sql, :counter_sql, :delete_sql, :insert_sql,
1791
- :before_add, :after_add, :before_remove, :after_remove,
1792
- :extend, :readonly,
1793
- :validate
1794
- ]
1795
-
1796
- def create_has_and_belongs_to_many_reflection(association_id, options, &extension)
1797
- options.assert_valid_keys(valid_keys_for_has_and_belongs_to_many_association)
1798
- options[:extend] = create_extension_modules(association_id, extension, options[:extend])
1799
-
1800
- reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
1801
-
1802
- if reflection.association_foreign_key == reflection.primary_key_name
1803
- raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
1804
- end
1805
-
1806
- reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
1807
- if connection.supports_primary_key? && (connection.primary_key(reflection.options[:join_table]) rescue false)
1808
- raise HasAndBelongsToManyAssociationWithPrimaryKeyError.new(reflection)
1809
- end
1810
-
1811
- reflection
1812
- end
1813
-
1814
- def add_association_callbacks(association_name, options)
1815
- callbacks = %w(before_add after_add before_remove after_remove)
1816
- callbacks.each do |callback_name|
1817
- full_callback_name = "#{callback_name}_for_#{association_name}"
1818
- defined_callbacks = options[callback_name.to_sym]
1819
- if options.has_key?(callback_name.to_sym)
1820
- class_inheritable_reader full_callback_name.to_sym
1821
- write_inheritable_attribute(full_callback_name.to_sym, [defined_callbacks].flatten)
1822
- else
1823
- write_inheritable_attribute(full_callback_name.to_sym, [])
1824
- end
1825
- end
1826
- end
1827
-
1828
- def create_extension_modules(association_id, block_extension, extensions)
1829
- if block_extension
1830
- extension_module_name = "#{self.to_s.demodulize}#{association_id.to_s.camelize}AssociationExtension"
1831
-
1832
- silence_warnings do
1833
- self.parent.const_set(extension_module_name, Module.new(&block_extension))
1834
- end
1835
- Array.wrap(extensions).push("#{self.parent}::#{extension_module_name}".constantize)
1836
- else
1837
- Array.wrap(extensions)
1838
- end
1839
- end
1840
-
1841
- class JoinDependency # :nodoc:
1842
- attr_reader :joins, :reflections, :table_aliases
1843
-
1844
- def initialize(base, associations, joins)
1845
- @joins = [JoinBase.new(base, joins)]
1846
- @associations = {}
1847
- @reflections = []
1848
- @base_records_hash = {}
1849
- @base_records_in_order = []
1850
- @table_aliases = Hash.new { |aliases, table| aliases[table] = 0 }
1851
- @table_aliases[base.table_name] = 1
1852
- build(associations)
1853
- end
1854
-
1855
- def graft(*associations)
1856
- associations.each do |association|
1857
- join_associations.detect {|a| association == a} ||
1858
- build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
1859
- end
1860
- self
1861
- end
1862
-
1863
- def join_associations
1864
- @joins[1..-1].to_a
1865
- end
1866
-
1867
- def join_base
1868
- @joins[0]
1869
- end
1870
-
1871
- def count_aliases_from_table_joins(name)
1872
- # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
1873
- quoted_name = join_base.active_record.connection.quote_table_name(name.downcase).downcase
1874
- join_sql = join_base.table_joins.to_s.downcase
1875
- join_sql.blank? ? 0 :
1876
- # Table names
1877
- join_sql.scan(/join(?:\s+\w+)?\s+#{quoted_name}\son/).size +
1878
- # Table aliases
1879
- join_sql.scan(/join(?:\s+\w+)?\s+\S+\s+#{quoted_name}\son/).size
1880
- end
1881
-
1882
- def instantiate(rows)
1883
- rows.each_with_index do |row, i|
1884
- primary_id = join_base.record_id(row)
1885
- unless @base_records_hash[primary_id]
1886
- @base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row))
1887
- end
1888
- construct(@base_records_hash[primary_id], @associations, join_associations.dup, row)
1889
- end
1890
- remove_duplicate_results!(join_base.active_record, @base_records_in_order, @associations)
1891
- return @base_records_in_order
1892
- end
1893
-
1894
- def remove_duplicate_results!(base, records, associations)
1895
- case associations
1896
- when Symbol, String
1897
- reflection = base.reflections[associations]
1898
- remove_uniq_by_reflection(reflection, records)
1899
- when Array
1900
- associations.each do |association|
1901
- remove_duplicate_results!(base, records, association)
1902
- end
1903
- when Hash
1904
- associations.keys.each do |name|
1905
- reflection = base.reflections[name]
1906
- remove_uniq_by_reflection(reflection, records)
1907
-
1908
- parent_records = []
1909
- records.each do |record|
1910
- if descendant = record.send(reflection.name)
1911
- if reflection.collection?
1912
- parent_records.concat descendant.target.uniq
1913
- else
1914
- parent_records << descendant
1915
- end
1916
- end
1917
- end
1918
-
1919
- remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
1920
- end
1921
- end
1922
- end
1923
-
1924
- protected
1925
-
1926
- def cache_joined_association(association)
1927
- associations = []
1928
- parent = association.parent
1929
- while parent != join_base
1930
- associations.unshift(parent.reflection.name)
1931
- parent = parent.parent
1932
- end
1933
- ref = @associations
1934
- associations.each do |key|
1935
- ref = ref[key]
1936
- end
1937
- ref[association.reflection.name] ||= {}
1938
- end
1939
-
1940
- def build(associations, parent = nil, join_type = Arel::InnerJoin)
1941
- parent ||= @joins.last
1942
- case associations
1943
- when Symbol, String
1944
- reflection = parent.reflections[associations.to_s.intern] or
1945
- raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
1946
- unless join_association = find_join_association(reflection, parent)
1947
- @reflections << reflection
1948
- join_association = build_join_association(reflection, parent)
1949
- join_association.join_type = join_type
1950
- @joins << join_association
1951
- cache_joined_association(join_association)
1952
- end
1953
- join_association
1954
- when Array
1955
- associations.each do |association|
1956
- build(association, parent, join_type)
1957
- end
1958
- when Hash
1959
- associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
1960
- join_association = build(name, parent, join_type)
1961
- build(associations[name], join_association, join_type)
1962
- end
1963
- else
1964
- raise ConfigurationError, associations.inspect
1965
- end
1966
- end
1967
-
1968
- def find_join_association(name_or_reflection, parent)
1969
- case name_or_reflection
1970
- when Symbol, String
1971
- join_associations.detect {|j| (j.reflection.name == name_or_reflection.to_s.intern) && (j.parent == parent)}
1972
- else
1973
- join_associations.detect {|j| (j.reflection == name_or_reflection) && (j.parent == parent)}
1974
- end
1975
- end
1976
-
1977
- def remove_uniq_by_reflection(reflection, records)
1978
- if reflection && reflection.collection?
1979
- records.each { |record| record.send(reflection.name).target.uniq! }
1980
- end
1981
- end
1982
-
1983
- def build_join_association(reflection, parent)
1984
- JoinAssociation.new(reflection, self, parent)
1985
- end
1986
-
1987
- def construct(parent, associations, joins, row)
1988
- case associations
1989
- when Symbol, String
1990
- join = joins.detect{|j| j.reflection.name.to_s == associations.to_s && j.parent_table_name == parent.class.table_name }
1991
- raise(ConfigurationError, "No such association") if join.nil?
1992
-
1993
- joins.delete(join)
1994
- construct_association(parent, join, row)
1995
- when Array
1996
- associations.each do |association|
1997
- construct(parent, association, joins, row)
1998
- end
1999
- when Hash
2000
- associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
2001
- join = joins.detect{|j| j.reflection.name.to_s == name.to_s && j.parent_table_name == parent.class.table_name }
2002
- raise(ConfigurationError, "No such association") if join.nil?
2003
-
2004
- association = construct_association(parent, join, row)
2005
- joins.delete(join)
2006
- construct(association, associations[name], joins, row) if association
2007
- end
2008
- else
2009
- raise ConfigurationError, associations.inspect
2010
- end
2011
- end
2012
-
2013
- def construct_association(record, join, row)
2014
- case join.reflection.macro
2015
- when :has_many, :has_and_belongs_to_many
2016
- collection = record.send(join.reflection.name)
2017
- collection.loaded
2018
-
2019
- return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
2020
- association = join.instantiate(row)
2021
- collection.target.push(association)
2022
- collection.__send__(:set_inverse_instance, association, record)
2023
- when :has_one
2024
- return if record.id.to_s != join.parent.record_id(row).to_s
2025
- return if record.instance_variable_defined?("@#{join.reflection.name}")
2026
- association = join.instantiate(row) unless row[join.aliased_primary_key].nil?
2027
- set_target_and_inverse(join, association, record)
2028
- when :belongs_to
2029
- return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
2030
- association = join.instantiate(row)
2031
- set_target_and_inverse(join, association, record)
2032
- else
2033
- raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
2034
- end
2035
- return association
2036
- end
2037
-
2038
- def set_target_and_inverse(join, association, record)
2039
- association_proxy = record.send("set_#{join.reflection.name}_target", association)
2040
- association_proxy.__send__(:set_inverse_instance, association, record)
2041
- end
2042
-
2043
- class JoinBase # :nodoc:
2044
- attr_reader :active_record, :table_joins
2045
- delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :arel_engine, :to => :active_record
2046
-
2047
- def initialize(active_record, joins = nil)
2048
- @active_record = active_record
2049
- @cached_record = {}
2050
- @table_joins = joins
2051
- end
2052
-
2053
- def ==(other)
2054
- other.class == self.class &&
2055
- other.active_record == active_record
2056
- end
2057
-
2058
- def aliased_prefix
2059
- "t0"
2060
- end
2061
-
2062
- def aliased_primary_key
2063
- "#{aliased_prefix}_r0"
2064
- end
2065
-
2066
- def aliased_table_name
2067
- active_record.table_name
2068
- end
2069
-
2070
- def column_names_with_alias
2071
- unless defined?(@column_names_with_alias)
2072
- @column_names_with_alias = []
2073
-
2074
- ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
2075
- @column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
2076
- end
2077
- end
2078
-
2079
- @column_names_with_alias
2080
- end
2081
-
2082
- def extract_record(row)
2083
- Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}]
2084
- end
2085
-
2086
- def record_id(row)
2087
- row[aliased_primary_key]
2088
- end
2089
-
2090
- def instantiate(row)
2091
- @cached_record[record_id(row)] ||= active_record.send(:instantiate, extract_record(row))
2092
- end
2093
- end
2094
-
2095
- class JoinAssociation < JoinBase # :nodoc:
2096
- attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name
2097
- # What type of join will be generated, either Arel::InnerJoin (default) or Arel::OuterJoin
2098
- attr_accessor :join_type
2099
- delegate :options, :klass, :through_reflection, :source_reflection, :to => :reflection
2100
-
2101
- def initialize(reflection, join_dependency, parent = nil)
2102
- reflection.check_validity!
2103
- if reflection.options[:polymorphic]
2104
- raise EagerLoadPolymorphicError.new(reflection)
2105
- end
2106
-
2107
- super(reflection.klass)
2108
- @join_dependency = join_dependency
2109
- @parent = parent
2110
- @reflection = reflection
2111
- @aliased_prefix = "t#{ join_dependency.joins.size }"
2112
- @parent_table_name = parent.active_record.table_name
2113
- @aliased_table_name = aliased_table_name_for(table_name)
2114
- @join = nil
2115
- @join_type = Arel::InnerJoin
2116
-
2117
- if reflection.macro == :has_and_belongs_to_many
2118
- @aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
2119
- end
2120
-
2121
- if [:has_many, :has_one].include?(reflection.macro) && reflection.options[:through]
2122
- @aliased_join_table_name = aliased_table_name_for(reflection.through_reflection.klass.table_name, "_join")
2123
- end
2124
- end
2125
-
2126
- def ==(other)
2127
- other.class == self.class &&
2128
- other.reflection == reflection &&
2129
- other.parent == parent
2130
- end
2131
-
2132
- def find_parent_in(other_join_dependency)
2133
- other_join_dependency.joins.detect do |join|
2134
- self.parent == join
2135
- end
2136
- end
2137
-
2138
- def association_join
2139
- return @join if @join
2140
-
2141
- aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name,
2142
- :engine => arel_engine,
2143
- :columns => klass.columns)
2144
-
2145
- parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name,
2146
- :engine => arel_engine,
2147
- :columns => parent.active_record.columns)
2148
- as_conditions = reflection.options[:conditions] && process_conditions(reflection.options[:conditions], aliased_table_name)
2149
-
2150
- @join = case reflection.macro
2151
- when :has_and_belongs_to_many
2152
- join_table = Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine)
2153
- fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key
2154
- klass_fk = options[:association_foreign_key] || klass.to_s.foreign_key
2155
-
2156
- [
2157
- join_table[fk].eq(parent_table[reflection.active_record.primary_key]),
2158
- [aliased_table[klass.primary_key].eq(join_table[klass_fk]), as_conditions].reject{ |x| x.blank? }
2159
- ]
2160
- when :has_many, :has_one
2161
- if reflection.options[:through]
2162
- join_table = Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine)
2163
- jt_as_conditions = through_reflection.options[:conditions] && process_conditions(through_reflection.options[:conditions], aliased_table_name)
2164
- jt_as_extra = jt_source_extra = jt_sti_extra = nil
2165
- first_key = second_key = as_extra = nil
2166
-
2167
- if through_reflection.macro == :belongs_to
2168
- jt_primary_key = through_reflection.primary_key_name
2169
- jt_foreign_key = through_reflection.association_primary_key
2170
- else
2171
- jt_primary_key = through_reflection.active_record_primary_key
2172
- jt_foreign_key = through_reflection.primary_key_name
2173
-
2174
- if through_reflection.options[:as] # has_many :through against a polymorphic join
2175
- jt_as_extra = join_table[through_reflection.options[:as].to_s + '_type'].eq(parent.active_record.base_class.name)
2176
- end
2177
- end
2178
-
2179
- case source_reflection.macro
2180
- when :has_many, :has_one
2181
- if source_reflection.options[:as]
2182
- first_key = "#{source_reflection.options[:as]}_id"
2183
- second_key = options[:foreign_key] || primary_key
2184
- as_extra = aliased_table["#{source_reflection.options[:as]}_type"].eq(source_reflection.active_record.base_class.name)
2185
- else
2186
- first_key = through_reflection.klass.base_class.to_s.foreign_key
2187
- second_key = options[:foreign_key] || primary_key
2188
- end
2189
-
2190
- unless through_reflection.klass.descends_from_active_record?
2191
- jt_sti_extra = join_table[through_reflection.active_record.inheritance_column].eq(through_reflection.klass.sti_name)
2192
- end
2193
- when :belongs_to
2194
- first_key = primary_key
2195
- if reflection.options[:source_type]
2196
- second_key = source_reflection.association_foreign_key
2197
- jt_source_extra = join_table[reflection.source_reflection.options[:foreign_type]].eq(reflection.options[:source_type])
2198
- else
2199
- second_key = source_reflection.primary_key_name
2200
- end
2201
- end
2202
-
2203
- [
2204
- [parent_table[jt_primary_key].eq(join_table[jt_foreign_key]), jt_as_extra, jt_source_extra, jt_sti_extra, jt_as_conditions].reject{|x| x.blank? },
2205
- [aliased_table[first_key].eq(join_table[second_key]), as_extra, as_conditions].reject{ |x| x.blank? }
2206
- ]
2207
- elsif reflection.options[:as]
2208
- id_rel = aliased_table["#{reflection.options[:as]}_id"].eq(parent_table[parent.primary_key])
2209
- type_rel = aliased_table["#{reflection.options[:as]}_type"].eq(parent.active_record.base_class.name)
2210
- [id_rel, type_rel, as_conditions].reject{ |x| x.blank?}
2211
- else
2212
- foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
2213
- [aliased_table[foreign_key].eq(parent_table[reflection.options[:primary_key] || parent.primary_key]), as_conditions].reject{ |x| x.blank? }
2214
- end
2215
- when :belongs_to
2216
- [aliased_table[options[:primary_key] || reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name]), as_conditions].reject{ |x| x.blank? }
2217
- end
2218
-
2219
- unless klass.descends_from_active_record?
2220
- sti_column = aliased_table[klass.inheritance_column]
2221
- sti_condition = sti_column.eq(klass.sti_name)
2222
- klass.descendants.each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
2223
-
2224
- @join << sti_condition
2225
- end
2226
-
2227
- @join
2228
- end
2229
-
2230
- def relation
2231
- aliased = Arel::Table.new(table_name, :as => @aliased_table_name,
2232
- :engine => arel_engine,
2233
- :columns => klass.columns)
2234
-
2235
- if reflection.macro == :has_and_belongs_to_many
2236
- [Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine), aliased]
2237
- elsif reflection.options[:through]
2238
- [Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine), aliased]
2239
- else
2240
- aliased
2241
- end
2242
- end
2243
-
2244
- def join_relation(joining_relation)
2245
- self.join_type = Arel::OuterJoin
2246
- joining_relation.joins(self)
2247
- end
2248
-
2249
- protected
2250
-
2251
- def aliased_table_name_for(name, suffix = nil)
2252
- if @join_dependency.table_aliases[name].zero?
2253
- @join_dependency.table_aliases[name] = @join_dependency.count_aliases_from_table_joins(name)
2254
- end
2255
-
2256
- if !@join_dependency.table_aliases[name].zero? # We need an alias
2257
- name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
2258
- @join_dependency.table_aliases[name] += 1
2259
- if @join_dependency.table_aliases[name] == 1 # First time we've seen this name
2260
- # Also need to count the aliases from the table_aliases to avoid incorrect count
2261
- @join_dependency.table_aliases[name] += @join_dependency.count_aliases_from_table_joins(name)
2262
- end
2263
- table_index = @join_dependency.table_aliases[name]
2264
- name = name[0..active_record.connection.table_alias_length-3] + "_#{table_index}" if table_index > 1
2265
- else
2266
- @join_dependency.table_aliases[name] += 1
2267
- end
2268
-
2269
- name
2270
- end
2271
-
2272
- def pluralize(table_name)
2273
- ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
2274
- end
2275
-
2276
- def table_alias_for(table_name, table_alias)
2277
- "#{table_name} #{table_alias if table_name != table_alias}".strip
2278
- end
2279
-
2280
- def table_name_and_alias
2281
- table_alias_for table_name, @aliased_table_name
2282
- end
2283
-
2284
- def process_conditions(conditions, table_name)
2285
- sanitized = sanitize_sql(conditions, table_name)
2286
-
2287
- if sanitized =~ /\#\{.*\}/
2288
- ActiveSupport::Deprecation.warn(
2289
- 'String-based interpolation of association conditions is deprecated. Please use a ' \
2290
- 'proc instead. So, for example, has_many :older_friends, :conditions => \'age > #{age}\' ' \
2291
- 'should be changed to has_many :older_friends, :conditions => proc { "age > #{age}" }.'
2292
- )
2293
- instance_eval("%@#{sanitized.gsub('@', '\@')}@", __FILE__, __LINE__)
2294
- elsif conditions.respond_to?(:to_proc)
2295
- conditions = sanitize_sql(instance_eval(&conditions), table_name)
2296
- else
2297
- sanitized
2298
- end
2299
- end
2300
- end
2301
- end
2302
1575
  end
2303
1576
  end
2304
1577
  end