activerecord 1.15.6 → 2.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 (185) hide show
  1. data/CHANGELOG +2454 -34
  2. data/README +1 -1
  3. data/RUNNING_UNIT_TESTS +3 -34
  4. data/Rakefile +98 -77
  5. data/install.rb +1 -1
  6. data/lib/active_record.rb +13 -22
  7. data/lib/active_record/aggregations.rb +38 -49
  8. data/lib/active_record/associations.rb +452 -333
  9. data/lib/active_record/associations/association_collection.rb +66 -20
  10. data/lib/active_record/associations/association_proxy.rb +9 -8
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
  12. data/lib/active_record/associations/has_many_association.rb +21 -57
  13. data/lib/active_record/associations/has_many_through_association.rb +38 -18
  14. data/lib/active_record/associations/has_one_association.rb +30 -14
  15. data/lib/active_record/attribute_methods.rb +253 -0
  16. data/lib/active_record/base.rb +719 -494
  17. data/lib/active_record/calculations.rb +62 -63
  18. data/lib/active_record/callbacks.rb +57 -83
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
  26. data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
  27. data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
  28. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
  30. data/lib/active_record/fixtures.rb +503 -113
  31. data/lib/active_record/locking/optimistic.rb +72 -34
  32. data/lib/active_record/migration.rb +80 -57
  33. data/lib/active_record/observer.rb +13 -10
  34. data/lib/active_record/query_cache.rb +16 -57
  35. data/lib/active_record/reflection.rb +35 -38
  36. data/lib/active_record/schema.rb +5 -5
  37. data/lib/active_record/schema_dumper.rb +35 -13
  38. data/lib/active_record/serialization.rb +98 -0
  39. data/lib/active_record/serializers/json_serializer.rb +71 -0
  40. data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
  41. data/lib/active_record/timestamp.rb +20 -21
  42. data/lib/active_record/transactions.rb +39 -43
  43. data/lib/active_record/validations.rb +256 -107
  44. data/lib/active_record/version.rb +3 -3
  45. data/lib/activerecord.rb +1 -0
  46. data/test/aaa_create_tables_test.rb +15 -2
  47. data/test/abstract_unit.rb +24 -17
  48. data/test/active_schema_test_mysql.rb +20 -8
  49. data/test/adapter_test.rb +23 -5
  50. data/test/adapter_test_sqlserver.rb +15 -1
  51. data/test/aggregations_test.rb +16 -1
  52. data/test/all.sh +2 -2
  53. data/test/associations/ar_joins_test.rb +0 -0
  54. data/test/associations/callbacks_test.rb +51 -30
  55. data/test/associations/cascaded_eager_loading_test.rb +1 -29
  56. data/test/associations/eager_singularization_test.rb +145 -0
  57. data/test/associations/eager_test.rb +42 -6
  58. data/test/associations/extension_test.rb +6 -1
  59. data/test/associations/inner_join_association_test.rb +88 -0
  60. data/test/associations/join_model_test.rb +47 -16
  61. data/test/associations_test.rb +449 -226
  62. data/test/attribute_methods_test.rb +97 -0
  63. data/test/base_test.rb +251 -105
  64. data/test/binary_test.rb +22 -27
  65. data/test/calculations_test.rb +37 -5
  66. data/test/callbacks_test.rb +23 -0
  67. data/test/connection_test_firebird.rb +2 -2
  68. data/test/connection_test_mysql.rb +30 -0
  69. data/test/connections/native_mysql/connection.rb +3 -0
  70. data/test/connections/native_sqlite/connection.rb +5 -14
  71. data/test/connections/native_sqlite3/connection.rb +5 -14
  72. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  73. data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
  74. data/test/datatype_test_postgresql.rb +178 -27
  75. data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
  76. data/test/defaults_test.rb +8 -1
  77. data/test/deprecated_finder_test.rb +7 -128
  78. data/test/finder_test.rb +192 -54
  79. data/test/fixtures/all/developers.yml +0 -0
  80. data/test/fixtures/all/people.csv +0 -0
  81. data/test/fixtures/all/tasks.yml +0 -0
  82. data/test/fixtures/author.rb +12 -5
  83. data/test/fixtures/binaries.yml +130 -435
  84. data/test/fixtures/category.rb +6 -0
  85. data/test/fixtures/company.rb +8 -1
  86. data/test/fixtures/computer.rb +1 -0
  87. data/test/fixtures/contact.rb +16 -0
  88. data/test/fixtures/customer.rb +2 -2
  89. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  90. data/test/fixtures/db_definitions/db2.sql +4 -0
  91. data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
  92. data/test/fixtures/db_definitions/firebird.sql +6 -0
  93. data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
  94. data/test/fixtures/db_definitions/frontbase.sql +5 -0
  95. data/test/fixtures/db_definitions/openbase.sql +41 -25
  96. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  97. data/test/fixtures/db_definitions/oracle.sql +5 -0
  98. data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
  99. data/test/fixtures/db_definitions/postgresql.sql +87 -58
  100. data/test/fixtures/db_definitions/postgresql2.sql +1 -2
  101. data/test/fixtures/db_definitions/schema.rb +280 -0
  102. data/test/fixtures/db_definitions/schema2.rb +11 -0
  103. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  104. data/test/fixtures/db_definitions/sqlite.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/sybase.sql +4 -0
  107. data/test/fixtures/developer.rb +10 -0
  108. data/test/fixtures/example.log +1 -0
  109. data/test/fixtures/flowers.jpg +0 -0
  110. data/test/fixtures/item.rb +7 -0
  111. data/test/fixtures/items.yml +4 -0
  112. data/test/fixtures/joke.rb +0 -3
  113. data/test/fixtures/matey.rb +4 -0
  114. data/test/fixtures/mateys.yml +4 -0
  115. data/test/fixtures/minimalistic.rb +2 -0
  116. data/test/fixtures/minimalistics.yml +2 -0
  117. data/test/fixtures/mixins.yml +2 -100
  118. data/test/fixtures/parrot.rb +13 -0
  119. data/test/fixtures/parrots.yml +27 -0
  120. data/test/fixtures/parrots_pirates.yml +7 -0
  121. data/test/fixtures/pirate.rb +5 -0
  122. data/test/fixtures/pirates.yml +9 -0
  123. data/test/fixtures/post.rb +1 -0
  124. data/test/fixtures/project.rb +3 -2
  125. data/test/fixtures/reserved_words/distinct.yml +5 -0
  126. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  127. data/test/fixtures/reserved_words/group.yml +14 -0
  128. data/test/fixtures/reserved_words/select.yml +8 -0
  129. data/test/fixtures/reserved_words/values.yml +7 -0
  130. data/test/fixtures/ship.rb +3 -0
  131. data/test/fixtures/ships.yml +5 -0
  132. data/test/fixtures/tagging.rb +4 -0
  133. data/test/fixtures/taggings.yml +8 -1
  134. data/test/fixtures/topic.rb +13 -1
  135. data/test/fixtures/treasure.rb +4 -0
  136. data/test/fixtures/treasures.yml +10 -0
  137. data/test/fixtures_test.rb +205 -24
  138. data/test/inheritance_test.rb +7 -1
  139. data/test/json_serialization_test.rb +180 -0
  140. data/test/lifecycle_test.rb +1 -1
  141. data/test/locking_test.rb +85 -2
  142. data/test/migration_test.rb +206 -40
  143. data/test/mixin_test.rb +13 -515
  144. data/test/pk_test.rb +3 -6
  145. data/test/query_cache_test.rb +104 -0
  146. data/test/reflection_test.rb +16 -0
  147. data/test/reserved_word_test_mysql.rb +177 -0
  148. data/test/schema_dumper_test.rb +38 -3
  149. data/test/serialization_test.rb +47 -0
  150. data/test/transactions_test.rb +74 -23
  151. data/test/unconnected_test.rb +1 -1
  152. data/test/validations_test.rb +322 -32
  153. data/test/xml_serialization_test.rb +121 -44
  154. metadata +48 -41
  155. data/examples/associations.rb +0 -87
  156. data/examples/shared_setup.rb +0 -15
  157. data/examples/validation.rb +0 -85
  158. data/lib/active_record/acts/list.rb +0 -256
  159. data/lib/active_record/acts/nested_set.rb +0 -211
  160. data/lib/active_record/acts/tree.rb +0 -96
  161. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
  162. data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
  163. data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
  164. data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
  165. data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
  166. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
  167. data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
  168. data/lib/active_record/deprecated_associations.rb +0 -104
  169. data/lib/active_record/deprecated_finders.rb +0 -44
  170. data/lib/active_record/vendor/simple.rb +0 -693
  171. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  172. data/lib/active_record/wrappings.rb +0 -58
  173. data/test/connections/native_sqlserver/connection.rb +0 -23
  174. data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
  175. data/test/deprecated_associations_test.rb +0 -396
  176. data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
  177. data/test/fixtures/db_definitions/mysql.sql +0 -234
  178. data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
  179. data/test/fixtures/db_definitions/mysql2.sql +0 -5
  180. data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
  181. data/test/fixtures/db_definitions/sqlserver.sql +0 -243
  182. data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
  183. data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
  184. data/test/fixtures/mixin.rb +0 -63
  185. data/test/mixin_nested_set_test.rb +0 -196
@@ -6,7 +6,6 @@ require 'active_record/associations/has_one_association'
6
6
  require 'active_record/associations/has_many_association'
7
7
  require 'active_record/associations/has_many_through_association'
8
8
  require 'active_record/associations/has_and_belongs_to_many_association'
9
- require 'active_record/deprecated_associations'
10
9
 
11
10
  module ActiveRecord
12
11
  class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
@@ -55,7 +54,7 @@ module ActiveRecord
55
54
  super("Cannot dissociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.")
56
55
  end
57
56
  end
58
-
57
+
59
58
  class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
60
59
  def initialize(reflection)
61
60
  super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
@@ -82,7 +81,7 @@ module ActiveRecord
82
81
 
83
82
  # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
84
83
  # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
85
- # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own attr*
84
+ # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own <tt>attr*</tt>
86
85
  # methods. Example:
87
86
  #
88
87
  # class Project < ActiveRecord::Base
@@ -101,14 +100,121 @@ module ActiveRecord
101
100
  # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
102
101
  # <tt>Project#categories.delete(category1)</tt>
103
102
  #
104
- # == Example
103
+ # === A word of warning
104
+ #
105
+ # Don't create associations that have the same name as instance methods of ActiveRecord::Base. Since the association
106
+ # adds a method with that name to its model, it will override the inherited method and break things.
107
+ # For instance, #attributes and #connection would be bad choices for association names.
108
+ #
109
+ # == Auto-generated methods
110
+ #
111
+ # ===Singular associations (one-to-one)
112
+ # | | belongs_to |
113
+ # generated methods | belongs_to | :polymorphic | has_one
114
+ # ----------------------------------+------------+--------------+---------
115
+ # #other | X | X | X
116
+ # #other=(other) | X | X | X
117
+ # #build_other(attributes={}) | X | | X
118
+ # #create_other(attributes={}) | X | | X
119
+ # #other.create!(attributes={}) | | | X
120
+ # #other.nil? | X | X |
121
+ #
122
+ # ===Collection associations (one-to-many / many-to-many)
123
+ # | | | has_many
124
+ # generated methods | habtm | has_many | :through
125
+ # ----------------------------------+-------+----------+----------
126
+ # #others | X | X | X
127
+ # #others=(other,other,...) | X | X |
128
+ # #other_ids | X | X | X
129
+ # #other_ids=(id,id,...) | X | X |
130
+ # #others<< | X | X | X
131
+ # #others.push | X | X | X
132
+ # #others.concat | X | X | X
133
+ # #others.build(attributes={}) | X | X | X
134
+ # #others.create(attributes={}) | X | X |
135
+ # #others.create!(attributes={}) | X | X | X
136
+ # #others.size | X | X | X
137
+ # #others.length | X | X | X
138
+ # #others.count | | X | X
139
+ # #others.sum(args*,&block) | X | X | X
140
+ # #others.empty? | X | X | X
141
+ # #others.clear | X | X |
142
+ # #others.delete(other,other,...) | X | X | X
143
+ # #others.delete_all | X | X |
144
+ # #others.destroy_all | X | X | X
145
+ # #others.find(*args) | X | X | X
146
+ # #others.find_first | X | |
147
+ # #others.uniq | X | X |
148
+ # #others.reset | X | X | X
149
+ #
150
+ # == Cardinality and associations
151
+ #
152
+ # ActiveRecord associations can be used to describe relations with one-to-one, one-to-many
153
+ # and many-to-many cardinality. Each model uses an association to describe its role in
154
+ # the relation. In each case, the +belongs_to+ association is used in the model that has
155
+ # the foreign key.
156
+ #
157
+ # === One-to-one
158
+ #
159
+ # Use +has_one+ in the base, and +belongs_to+ in the associated model.
160
+ #
161
+ # class Employee < ActiveRecord::Base
162
+ # has_one :office
163
+ # end
164
+ # class Office < ActiveRecord::Base
165
+ # belongs_to :employee # foreign key - employee_id
166
+ # end
167
+ #
168
+ # === One-to-many
169
+ #
170
+ # Use +has_many+ in the base, and +belongs_to+ in the associated model.
171
+ #
172
+ # class Manager < ActiveRecord::Base
173
+ # has_many :employees
174
+ # end
175
+ # class Employee < ActiveRecord::Base
176
+ # belongs_to :manager # foreign key - manager_id
177
+ # end
178
+ #
179
+ # === Many-to-many
180
+ #
181
+ # There are two ways to build a many-to-many relationship.
182
+ #
183
+ # The first way uses a +has_many+ association with the <tt>:through</tt> option and a join model, so
184
+ # there are two stages of associations.
185
+ #
186
+ # class Assignment < ActiveRecord::Base
187
+ # belongs_to :programmer # foreign key - programmer_id
188
+ # belongs_to :project # foreign key - project_id
189
+ # end
190
+ # class Programmer < ActiveRecord::Base
191
+ # has_many :assignments
192
+ # has_many :projects, :through => :assignments
193
+ # end
194
+ # class Project < ActiveRecord::Base
195
+ # has_many :assignments
196
+ # has_many :programmers, :through => :assignments
197
+ # end
198
+ #
199
+ # For the second way, use +has_and_belongs_to_many+ in both models. This requires a join table
200
+ # that has no corresponding model or primary key.
201
+ #
202
+ # class Programmer < ActiveRecord::Base
203
+ # has_and_belongs_to_many :projects # foreign keys in the join table
204
+ # end
205
+ # class Project < ActiveRecord::Base
206
+ # has_and_belongs_to_many :programmers # foreign keys in the join table
207
+ # end
105
208
  #
106
- # link:files/examples/associations.png
209
+ # Choosing which way to build a many-to-many relationship is not always simple.
210
+ # If you need to work with the relationship model as its own entity,
211
+ # use <tt>has_many :through</tt>. Use +has_and_belongs_to_many+ when working with legacy schemas or when
212
+ # you never work directly with the relationship itself.
107
213
  #
108
- # == Is it belongs_to or has_one?
214
+ # == Is it a +belongs_to+ or +has_one+ association?
109
215
  #
110
- # Both express a 1-1 relationship, the difference is mostly where to place the foreign key, which goes on the table for the class
111
- # saying belongs_to. Example:
216
+ # Both express a 1-1 relationship. The difference is mostly where to place the foreign key, which goes on the table for the class
217
+ # declaring the +belongs_to+ relationship. Example:
112
218
  #
113
219
  # class User < ActiveRecord::Base
114
220
  # # I reference an account.
@@ -137,31 +243,31 @@ module ActiveRecord
137
243
  #
138
244
  # == Unsaved objects and associations
139
245
  #
140
- # You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be
246
+ # You can manipulate objects and associations before they are saved to the database, but there is some special behavior you should be
141
247
  # aware of, mostly involving the saving of associated objects.
142
248
  #
143
249
  # === One-to-one associations
144
250
  #
145
- # * Assigning an object to a has_one association automatically saves that object and the object being replaced (if there is one), in
146
- # order to update their primary keys - except if the parent object is unsaved (new_record? == true).
147
- # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
251
+ # * Assigning an object to a +has_one+ association automatically saves that object and the object being replaced (if there is one), in
252
+ # order to update their primary keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
253
+ # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns +false+ and the assignment
148
254
  # is cancelled.
149
- # * If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below).
150
- # * Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does
151
- # not save the parent either.
255
+ # * If you wish to assign an object to a +has_one+ association without saving it, use the <tt>#association.build</tt> method (documented below).
256
+ # * Assigning an object to a +belongs_to+ association does not save the object, since the foreign key field belongs on the parent. It
257
+ # does not save the parent either.
152
258
  #
153
259
  # === Collections
154
260
  #
155
- # * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object
261
+ # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically saves that object, except if the parent object
156
262
  # (the owner of the collection) is not yet stored in the database.
157
- # * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false.
158
- # * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below).
159
- # * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
263
+ # * If saving any of the objects being added to a collection (via <tt>#push</tt> or similar) fails, then <tt>#push</tt> returns +false+.
264
+ # * You can add an object to a collection without automatically saving it by using the <tt>#collection.build</tt> method (documented below).
265
+ # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically saved when the parent is saved.
160
266
  #
161
267
  # === Association callbacks
162
268
  #
163
- # Similiar to the normal callbacks that hook into the lifecycle of an Active Record object, you can also define callbacks that get
164
- # trigged when you add an object to or removing an object from a association collection. Example:
269
+ # Similar to the normal callbacks that hook into the lifecycle of an Active Record object, you can also define callbacks that get
270
+ # triggered when you add an object to or remove an object from an association collection. Example:
165
271
  #
166
272
  # class Project
167
273
  # has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
@@ -177,14 +283,14 @@ module ActiveRecord
177
283
  # has_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
178
284
  # end
179
285
  #
180
- # Possible callbacks are: before_add, after_add, before_remove and after_remove.
286
+ # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
181
287
  #
182
- # Should any of the before_add callbacks throw an exception, the object does not get added to the collection. Same with
183
- # the before_remove callbacks, if an exception is thrown the object doesn't get removed.
288
+ # Should any of the +before_add+ callbacks throw an exception, the object does not get added to the collection. Same with
289
+ # the +before_remove+ callbacks; if an exception is thrown the object doesn't get removed.
184
290
  #
185
291
  # === Association extensions
186
292
  #
187
- # The proxy objects that controls the access to associations can be extended through anonymous modules. This is especially
293
+ # The proxy objects that control the access to associations can be extended through anonymous modules. This is especially
188
294
  # beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this association.
189
295
  # Example:
190
296
  #
@@ -218,7 +324,7 @@ module ActiveRecord
218
324
  # has_many :people, :extend => FindOrCreateByNameExtension
219
325
  # end
220
326
  #
221
- # If you need to use multiple named extension modules, you can specify an array of modules with the :extend option.
327
+ # If you need to use multiple named extension modules, you can specify an array of modules with the <tt>:extend</tt> option.
222
328
  # In the case of name conflicts between methods in the modules, methods in modules later in the array supercede
223
329
  # those earlier in the array. Example:
224
330
  #
@@ -231,12 +337,12 @@ module ActiveRecord
231
337
  #
232
338
  # * +proxy_owner+ - Returns the object the association is part of.
233
339
  # * +proxy_reflection+ - Returns the reflection object that describes the association.
234
- # * +proxy_target+ - Returns the associated object for belongs_to and has_one, or the collection of associated objects for has_many and has_and_belongs_to_many.
340
+ # * +proxy_target+ - Returns the associated object for +belongs_to+ and +has_one+, or the collection of associated objects for +has_many+ and +has_and_belongs_to_many+.
235
341
  #
236
342
  # === Association Join Models
237
343
  #
238
- # Has Many associations can be configured with the :through option to use an explicit join model to retrieve the data. This
239
- # operates similarly to a <tt>has_and_belongs_to_many</tt> association. The advantage is that you're able to add validations,
344
+ # Has Many associations can be configured with the <tt>:through</tt> option to use an explicit join model to retrieve the data. This
345
+ # operates similarly to a +has_and_belongs_to_many+ association. The advantage is that you're able to add validations,
240
346
  # callbacks, and extra attributes on the join model. Consider the following schema:
241
347
  #
242
348
  # class Author < ActiveRecord::Base
@@ -253,7 +359,7 @@ module ActiveRecord
253
359
  # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to.
254
360
  # @author.books # selects all books by using the Authorship join model
255
361
  #
256
- # You can also go through a has_many association on the join model:
362
+ # You can also go through a +has_many+ association on the join model:
257
363
  #
258
364
  # class Firm < ActiveRecord::Base
259
365
  # has_many :clients
@@ -276,25 +382,25 @@ module ActiveRecord
276
382
  # === Polymorphic Associations
277
383
  #
278
384
  # Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
279
- # specify an interface that a has_many association must adhere to.
385
+ # specify an interface that a +has_many+ association must adhere to.
280
386
  #
281
387
  # class Asset < ActiveRecord::Base
282
388
  # belongs_to :attachable, :polymorphic => true
283
389
  # end
284
390
  #
285
391
  # class Post < ActiveRecord::Base
286
- # has_many :assets, :as => :attachable # The <tt>:as</tt> option specifies the polymorphic interface to use.
392
+ # has_many :assets, :as => :attachable # The :as option specifies the polymorphic interface to use.
287
393
  # end
288
394
  #
289
395
  # @asset.attachable = @post
290
396
  #
291
397
  # This works by using a type column in addition to a foreign key to specify the associated record. In the Asset example, you'd need
292
- # an attachable_id integer column and an attachable_type string column.
398
+ # an +attachable_id+ integer column and an +attachable_type+ string column.
293
399
  #
294
400
  # Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order
295
401
  # for the associations to work as expected, ensure that you store the base model for the STI models in the
296
402
  # type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts
297
- # and member posts that use the posts table for STI. So there will be an additional 'type' column in the posts table.
403
+ # and member posts that use the posts table for STI. In this case, there must be a +type+ column in the posts table.
298
404
  #
299
405
  # class Asset < ActiveRecord::Base
300
406
  # belongs_to :attachable, :polymorphic => true
@@ -309,10 +415,10 @@ module ActiveRecord
309
415
  # has_many :assets, :as => :attachable, :dependent => :destroy
310
416
  # end
311
417
  #
312
- # class GuestPost < ActiveRecord::Base
418
+ # class GuestPost < Post
313
419
  # end
314
420
  #
315
- # class MemberPost < ActiveRecord::Base
421
+ # class MemberPost < Post
316
422
  # end
317
423
  #
318
424
  # == Caching
@@ -330,7 +436,7 @@ module ActiveRecord
330
436
  # == Eager loading of associations
331
437
  #
332
438
  # Eager loading is a way to find objects of a certain class and a number of named associations along with it in a single SQL call. This is
333
- # one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each needs to display their author
439
+ # one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each need to display their author
334
440
  # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 1. Example:
335
441
  #
336
442
  # class Post < ActiveRecord::Base
@@ -350,16 +456,16 @@ module ActiveRecord
350
456
  #
351
457
  # for post in Post.find(:all, :include => :author)
352
458
  #
353
- # This references the name of the belongs_to association that also used the :author symbol, so the find will now weave in a join something
354
- # like this: LEFT OUTER JOIN authors ON authors.id = posts.author_id. Doing so will cut down the number of queries from 201 to 101.
459
+ # This references the name of the +belongs_to+ association that also used the <tt>:author</tt> symbol, so the find will now weave in a join something
460
+ # like this: <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Doing so will cut down the number of queries from 201 to 101.
355
461
  #
356
462
  # We can improve upon the situation further by referencing both associations in the finder with:
357
463
  #
358
464
  # for post in Post.find(:all, :include => [ :author, :comments ])
359
465
  #
360
- # That'll add another join along the lines of: LEFT OUTER JOIN comments ON comments.post_id = posts.id. And we'll be down to 1 query.
466
+ # That'll add another join along the lines of: <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt>. And we'll be down to 1 query.
361
467
  #
362
- # To include a deep hierarchy of associations, using a hash:
468
+ # To include a deep hierarchy of associations, use a hash:
363
469
  #
364
470
  # for post in Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ])
365
471
  #
@@ -371,12 +477,12 @@ module ActiveRecord
371
477
  # catch-all for performance problems, but it's a great way to cut down on the number of queries in a situation as the one described above.
372
478
  #
373
479
  # Since the eager loading pulls from multiple tables, you'll have to disambiguate any column references in both conditions and orders. So
374
- # :order => "posts.id DESC" will work while :order => "id DESC" will not. Because eager loading generates the SELECT statement too, the
375
- # :select option is ignored.
480
+ # <tt>:order => "posts.id DESC"</tt> will work while <tt>:order => "id DESC"</tt> will not. Because eager loading generates the +SELECT+ statement too, the
481
+ # <tt>:select</tt> option is ignored.
376
482
  #
377
483
  # You can use eager loading on multiple associations from the same table, but you cannot use those associations in orders and conditions
378
484
  # as there is currently not any way to disambiguate them. Eager loading will not pull additional attributes on join tables, so "rich
379
- # associations" with has_and_belongs_to_many are not a good fit for eager loading.
485
+ # associations" with +has_and_belongs_to_many+ are not a good fit for eager loading.
380
486
  #
381
487
  # When eager loaded, conditions are interpolated in the context of the model class, not the model instance. Conditions are lazily interpolated
382
488
  # before the actual model exists.
@@ -384,7 +490,7 @@ module ActiveRecord
384
490
  # == Table Aliasing
385
491
  #
386
492
  # ActiveRecord uses table aliasing in the case that a table is referenced multiple times in a join. If a table is referenced only once,
387
- # the standard table name is used. The second time, the table is aliased as #{reflection_name}_#{parent_table_name}. Indexes are appended
493
+ # the standard table name is used. The second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>. Indexes are appended
388
494
  # for any more successive uses of the table name.
389
495
  #
390
496
  # Post.find :all, :include => :comments
@@ -404,9 +510,9 @@ module ActiveRecord
404
510
  # TreeMixin.find :all, :include => {:children => {:parent => :children}}
405
511
  # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
406
512
  # LEFT OUTER JOIN parents_mixins ...
407
- # LEFT OUTER JOIN mixins childrens_mixins_2
513
+ # LEFT OUTER JOIN mixins childrens_mixins_2
408
514
  #
409
- # Has and Belongs to Many join tables use the same idea, but add a _join suffix:
515
+ # Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
410
516
  #
411
517
  # Post.find :all, :include => :categories
412
518
  # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
@@ -418,7 +524,7 @@ module ActiveRecord
418
524
  # LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
419
525
  # LEFT OUTER JOIN categories_posts categories_posts_join LEFT OUTER JOIN categories categories_posts
420
526
  #
421
- # If you wish to specify your own custom joins using a :joins option, those table names will take precedence over the eager associations..
527
+ # If you wish to specify your own custom joins using a <tt>:joins</tt> option, those table names will take precedence over the eager associations:
422
528
  #
423
529
  # Post.find :all, :include => :comments, :joins => "inner join comments ..."
424
530
  # # => SELECT ... FROM posts LEFT OUTER JOIN comments_posts ON ... INNER JOIN comments ...
@@ -443,8 +549,8 @@ module ActiveRecord
443
549
  # end
444
550
  # end
445
551
  #
446
- # When Firm#clients is called, it'll in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate
447
- # with a class in another module scope this can be done by specifying the complete class name, such as:
552
+ # When <tt>Firm#clients</tt> is called, it will in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate
553
+ # with a class in another module scope, this can be done by specifying the complete class name. Example:
448
554
  #
449
555
  # module MyApplication
450
556
  # module Business
@@ -458,41 +564,41 @@ module ActiveRecord
458
564
  # end
459
565
  # end
460
566
  #
461
- # == Type safety with ActiveRecord::AssociationTypeMismatch
567
+ # == Type safety with <tt>ActiveRecord::AssociationTypeMismatch</tt>
462
568
  #
463
569
  # If you attempt to assign an object to an association that doesn't match the inferred or specified <tt>:class_name</tt>, you'll
464
- # get a ActiveRecord::AssociationTypeMismatch.
570
+ # get an <tt>ActiveRecord::AssociationTypeMismatch</tt>.
465
571
  #
466
572
  # == Options
467
573
  #
468
- # All of the association macros can be specialized through options which makes more complex cases than the simple and guessable ones
574
+ # All of the association macros can be specialized through options. This makes cases more complex than the simple and guessable ones
469
575
  # possible.
470
576
  module ClassMethods
471
- # Adds the following methods for retrieval and query of collections of associated objects.
577
+ # Adds the following methods for retrieval and query of collections of associated objects:
472
578
  # +collection+ is replaced with the symbol passed as the first argument, so
473
579
  # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
474
580
  # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
475
581
  # An empty array is returned if none are found.
476
582
  # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
477
583
  # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL.
478
- # This will also destroy the objects if they're declared as belongs_to and dependent on this model.
584
+ # This will also destroy the objects if they're declared as +belongs_to+ and dependent on this model.
479
585
  # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
480
- # * <tt>collection_singular_ids</tt> - returns an array of the associated objects ids
481
- # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
586
+ # * <tt>collection_singular_ids</tt> - returns an array of the associated objects' ids
587
+ # * <tt>collection_singular_ids=ids</tt> - replace the collection with the objects identified by the primary keys in +ids+
482
588
  # * <tt>collection.clear</tt> - removes every object from the collection. This destroys the associated objects if they
483
- # are <tt>:dependent</tt>, deletes them directly from the database if they are <tt>:dependent => :delete_all</tt>,
484
- # and sets their foreign keys to NULL otherwise.
485
- # * <tt>collection.empty?</tt> - returns true if there are no associated objects.
589
+ # are associated with <tt>:dependent => :destroy</tt>, deletes them directly from the database if <tt>:dependent => :delete_all</tt>,
590
+ # otherwise sets their foreign keys to NULL.
591
+ # * <tt>collection.empty?</tt> - returns +true+ if there are no associated objects.
486
592
  # * <tt>collection.size</tt> - returns the number of associated objects.
487
593
  # * <tt>collection.find</tt> - finds an associated object according to the same rules as Base.find.
488
- # * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
489
- # with +attributes+ and linked to this object through a foreign key but has not yet been saved. *Note:* This only works if an
490
- # associated object already exists, not if it's nil!
594
+ # * <tt>collection.build(attributes = {}, ...)</tt> - returns one or more new objects of the collection type that have been instantiated
595
+ # with +attributes+ and linked to this object through a foreign key, but have not yet been saved. *Note:* This only works if an
596
+ # associated object already exists, not if it's +nil+!
491
597
  # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
492
- # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
493
- # *Note:* This only works if an associated object already exists, not if it's nil!
598
+ # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
599
+ # *Note:* This only works if an associated object already exists, not if it's +nil+!
494
600
  #
495
- # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
601
+ # Example: A +Firm+ class declares <tt>has_many :clients</tt>, which will add:
496
602
  # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>)
497
603
  # * <tt>Firm#clients<<</tt>
498
604
  # * <tt>Firm#clients.delete</tt>
@@ -511,47 +617,38 @@ module ActiveRecord
511
617
  # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
512
618
  # from the association name. So <tt>has_many :products</tt> will by default be linked to the +Product+ class, but
513
619
  # if the real class name is +SpecialProduct+, you'll have to specify it with this option.
514
- # * <tt>:conditions</tt> - specify the conditions that the associated objects must meet in order to be included as a "WHERE"
515
- # sql fragment, such as "price > 5 AND name LIKE 'B%'".
516
- # * <tt>:order</tt> - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment,
517
- # such as "last_name, first_name DESC"
518
- # * <tt>:group</tt> - specify the attribute by which the associated objects are returned as a "GROUP BY" sql fragment,
519
- # such as "category"
620
+ # * <tt>:conditions</tt> - specify the conditions that the associated objects must meet in order to be included as a +WHERE+
621
+ # SQL fragment, such as <tt>price > 5 AND name LIKE 'B%'</tt>.
622
+ # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
623
+ # such as <tt>last_name, first_name DESC</tt>
520
624
  # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
521
- # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_many association will use "person_id"
522
- # as the default foreign_key.
523
- # * <tt>:dependent</tt> - if set to :destroy all the associated objects are destroyed
524
- # alongside this object by calling their destroy method. If set to :delete_all all associated
525
- # objects are deleted *without* calling their destroy method. If set to :nullify all associated
526
- # objects' foreign keys are set to NULL *without* calling their save callbacks.
527
- # NOTE: :dependent => true is deprecated and has been replaced with :dependent => :destroy.
528
- # May not be set if :exclusively_dependent is also set.
529
- # * <tt>:exclusively_dependent</tt> - Deprecated; equivalent to :dependent => :delete_all. If set to true all
530
- # the associated object are deleted in one SQL statement without having their
531
- # before_destroy callback run. This should only be used on associations that depend solely on this class and don't need to do any
532
- # clean-up in before_destroy. The upside is that it's much faster, especially if there's a counter_cache involved.
533
- # May not be set if :dependent is also set.
625
+ # of this class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +has_many+ association will use +person_id+
626
+ # as the default +foreign_key+.
627
+ # * <tt>:dependent</tt> - if set to <tt>:destroy</tt> all the associated objects are destroyed
628
+ # alongside this object by calling their destroy method. If set to <tt>:delete_all</tt> all associated
629
+ # objects are deleted *without* calling their destroy method. If set to <tt>:nullify</tt> all associated
630
+ # objects' foreign keys are set to +NULL+ *without* calling their save callbacks.
534
631
  # * <tt>:finder_sql</tt> - specify a complete SQL statement to fetch the association. This is a good way to go for complex
535
632
  # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
536
- # * <tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If +:finder_sql+ is
537
- # specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
538
- # * <tt>:extend</tt> - specify a named module for extending the proxy, see "Association extensions".
633
+ # * <tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
634
+ # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
635
+ # * <tt>:extend</tt> - specify a named module for extending the proxy. See "Association extensions".
539
636
  # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
540
- # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
637
+ # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
541
638
  # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
542
639
  # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
543
- # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
544
- # include the joined columns.
545
- # * <tt>:as</tt>: Specifies a polymorphic interface (See #belongs_to).
546
- # * <tt>:through</tt>: Specifies a Join Model to perform the query through. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
547
- # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
548
- # or <tt>has_many</tt> association.
640
+ # * <tt>:select</tt>: By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if you, for example, want to do a join
641
+ # but not include the joined columns.
642
+ # * <tt>:as</tt>: Specifies a polymorphic interface (See <tt>#belongs_to</tt>).
643
+ # * <tt>:through</tt>: Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
644
+ # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
645
+ # or <tt>has_many</tt> association on the join model.
549
646
  # * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
550
- # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either +:subscribers+ or
551
- # +:subscriber+ on +Subscription+, unless a +:source+ is given.
552
- # * <tt>:source_type</tt>: Specifies type of the source association used by <tt>has_many :through</tt> queries where the source association
553
- # is a polymorphic belongs_to.
554
- # * <tt>:uniq</tt> - if set to true, duplicates will be omitted from the collection. Useful in conjunction with :through.
647
+ # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
648
+ # <tt>:subscriber</tt> on +Subscription+, unless a <tt>:source</tt> is given.
649
+ # * <tt>:source_type</tt>: Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
650
+ # association is a polymorphic +belongs_to+.
651
+ # * <tt>:uniq</tt> - if set to +true+, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
555
652
  #
556
653
  # Option examples:
557
654
  # has_many :comments, :order => "posted_on"
@@ -573,27 +670,26 @@ module ActiveRecord
573
670
 
574
671
  if options[:through]
575
672
  collection_reader_method(reflection, HasManyThroughAssociation)
673
+ collection_accessor_methods(reflection, HasManyThroughAssociation, false)
576
674
  else
577
675
  add_multiple_associated_save_callbacks(reflection.name)
578
676
  add_association_callbacks(reflection.name, reflection.options)
579
677
  collection_accessor_methods(reflection, HasManyAssociation)
580
678
  end
581
-
582
- add_deprecated_api_for_has_many(reflection.name)
583
679
  end
584
680
 
585
- # Adds the following methods for retrieval and query of a single associated object.
681
+ # Adds the following methods for retrieval and query of a single associated object:
586
682
  # +association+ is replaced with the symbol passed as the first argument, so
587
683
  # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
588
- # * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
684
+ # * <tt>association(force_reload = false)</tt> - returns the associated object. +nil+ is returned if none is found.
589
685
  # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key,
590
686
  # and saves the associate object.
591
- # * <tt>association.nil?</tt> - returns true if there is no associated object.
687
+ # * <tt>association.nil?</tt> - returns +true+ if there is no associated object.
592
688
  # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
593
- # with +attributes+ and linked to this object through a foreign key but has not yet been saved. Note: This ONLY works if
594
- # an association already exists. It will NOT work if the association is nil.
689
+ # with +attributes+ and linked to this object through a foreign key, but has not yet been saved. Note: This ONLY works if
690
+ # an association already exists. It will NOT work if the association is +nil+.
595
691
  # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
596
- # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
692
+ # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
597
693
  #
598
694
  # Example: An Account class declares <tt>has_one :beneficiary</tt>, which will add:
599
695
  # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>)
@@ -608,22 +704,22 @@ module ActiveRecord
608
704
  # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
609
705
  # from the association name. So <tt>has_one :manager</tt> will by default be linked to the +Manager+ class, but
610
706
  # if the real class name is +Person+, you'll have to specify it with this option.
611
- # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE"
612
- # sql fragment, such as "rank = 5".
613
- # * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as
614
- # an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
615
- # * <tt>:dependent</tt> - if set to :destroy (or true) the associated object is destroyed when this object is. If set to
616
- # :delete the associated object is deleted *without* calling its destroy method. If set to :nullify the associated
617
- # object's foreign key is set to NULL. Also, association is assigned.
707
+ # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a +WHERE+
708
+ # SQL fragment, such as <tt>rank = 5</tt>.
709
+ # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
710
+ # such as <tt>last_name, first_name DESC</tt>
711
+ # * <tt>:dependent</tt> - if set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
712
+ # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. If set to <tt>:nullify</tt>, the associated
713
+ # object's foreign key is set to +NULL+. Also, association is assigned.
618
714
  # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
619
- # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_one association will use "person_id"
620
- # as the default foreign_key.
715
+ # of this class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +has_one+ association will use +person_id+
716
+ # as the default +foreign_key+.
621
717
  # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
622
- # * <tt>:as</tt>: Specifies a polymorphic interface (See #belongs_to).
718
+ # * <tt>:as</tt>: Specifies a polymorphic interface (See <tt>#belongs_to</tt>).
623
719
  #
624
720
  # Option examples:
625
721
  # has_one :credit_card, :dependent => :destroy # destroys the associated credit card
626
- # has_one :credit_card, :dependent => :nullify # updates the associated records foriegn key value to null rather than destroying it
722
+ # has_one :credit_card, :dependent => :nullify # updates the associated records foreign key value to NULL rather than destroying it
627
723
  # has_one :last_comment, :class_name => "Comment", :order => "posted_on"
628
724
  # has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
629
725
  # has_one :attachment, :as => :attachable
@@ -645,22 +741,18 @@ module ActiveRecord
645
741
  association_constructor_method(:create, reflection, HasOneAssociation)
646
742
 
647
743
  configure_dependency_for_has_one(reflection)
648
-
649
- # deprecated api
650
- deprecated_has_association_method(reflection.name)
651
- deprecated_association_comparison_method(reflection.name, reflection.class_name)
652
744
  end
653
745
 
654
- # Adds the following methods for retrieval and query for a single associated object that this object holds an id to.
746
+ # Adds the following methods for retrieval and query for a single associated object for which this object holds an id:
655
747
  # +association+ is replaced with the symbol passed as the first argument, so
656
748
  # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
657
- # * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
749
+ # * <tt>association(force_reload = false)</tt> - returns the associated object. +nil+ is returned if none is found.
658
750
  # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, and sets it as the foreign key.
659
- # * <tt>association.nil?</tt> - returns true if there is no associated object.
751
+ # * <tt>association.nil?</tt> - returns +true+ if there is no associated object.
660
752
  # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
661
- # with +attributes+ and linked to this object through a foreign key but has not yet been saved.
753
+ # with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
662
754
  # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
663
- # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
755
+ # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
664
756
  #
665
757
  # Example: A Post class declares <tt>belongs_to :author</tt>, which will add:
666
758
  # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
@@ -675,20 +767,23 @@ module ActiveRecord
675
767
  # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
676
768
  # from the association name. So <tt>has_one :author</tt> will by default be linked to the +Author+ class, but
677
769
  # if the real class name is +Person+, you'll have to specify it with this option.
678
- # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE"
679
- # sql fragment, such as "authorized = 1".
680
- # * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as
681
- # an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
770
+ # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a +WHERE+
771
+ # SQL fragment, such as <tt>authorized = 1</tt>.
772
+ # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
773
+ # such as <tt>last_name, first_name DESC</tt>
682
774
  # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
683
- # of the associated class in lower-case and "_id" suffixed. So a +Person+ class that makes a belongs_to association to a
684
- # +Boss+ class will use "boss_id" as the default foreign_key.
685
- # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through use of increment_counter
686
- # and decrement_counter. The counter cache is incremented when an object of this class is created and decremented when it's
687
- # destroyed. This requires that a column named "#{table_name}_count" (such as comments_count for a belonging Comment class)
688
- # is used on the associate class (such as a Post class). You can also specify a custom counter cache column by given that
689
- # name instead of a true/false value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
775
+ # of the associated class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +belongs_to+ association to a
776
+ # +Boss+ class will use +boss_id+ as the default +foreign_key+.
777
+ # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through the use of +increment_counter+
778
+ # and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's
779
+ # destroyed. This requires that a column named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging +Comment+ class)
780
+ # is used on the associate class (such as a +Post+ class). You can also specify a custom counter cache column by providing
781
+ # a column name instead of a +true+/+false+ value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
782
+ # Note: Specifying a counter_cache will add it to that model's list of readonly attributes using #attr_readonly.
690
783
  # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
691
- # * <tt>:polymorphic</tt> - specify this association is a polymorphic association by passing true.
784
+ # * <tt>:polymorphic</tt> - specify this association is a polymorphic association by passing +true+.
785
+ # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
786
+ # to the attr_readonly list in the associated classes (e.g. class Post; attr_readonly :comments_count; end).
692
787
  #
693
788
  # Option examples:
694
789
  # belongs_to :firm, :foreign_key => "client_of"
@@ -697,12 +792,6 @@ module ActiveRecord
697
792
  # :conditions => 'discounts > #{payments_count}'
698
793
  # belongs_to :attachable, :polymorphic => true
699
794
  def belongs_to(association_id, options = {})
700
- if options.include?(:class_name) && !options.include?(:foreign_key)
701
- ::ActiveSupport::Deprecation.warn(
702
- "The inferred foreign_key name will change in Rails 2.0 to use the association name instead of its class name when they differ. When using :class_name in belongs_to, use the :foreign_key option to explicitly set the key name to avoid problems in the transition.",
703
- caller)
704
- end
705
-
706
795
  reflection = create_belongs_to_reflection(association_id, options)
707
796
 
708
797
  if reflection.options[:polymorphic]
@@ -742,10 +831,6 @@ module ActiveRecord
742
831
  end
743
832
  EOF
744
833
  end
745
-
746
- # deprecated api
747
- deprecated_has_association_method(reflection.name)
748
- deprecated_association_comparison_method(reflection.name, reflection.class_name)
749
834
  end
750
835
 
751
836
  # Create the callbacks to update counter cache
@@ -762,13 +847,17 @@ module ActiveRecord
762
847
  module_eval(
763
848
  "before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
764
849
  " unless #{reflection.name}.nil?'"
765
- )
850
+ )
851
+
852
+ module_eval(
853
+ "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)"
854
+ )
766
855
  end
767
856
  end
768
857
 
769
858
  # Associates two classes via an intermediate join table. Unless the join table is explicitly specified as
770
- # an option, it is guessed using the lexical order of the class names. So a join between Developer and Project
771
- # will give the default join table name of "developers_projects" because "D" outranks "P". Note that this precedence
859
+ # an option, it is guessed using the lexical order of the class names. So a join between +Developer+ and +Project+
860
+ # will give the default join table name of +developers_projects+ because "D" outranks "P". Note that this precedence
772
861
  # is calculated using the <tt><</tt> operator for <tt>String</tt>. This means that if the strings are of different lengths,
773
862
  # and the strings are equal when compared up to the shortest length, then the longer string is considered of higher
774
863
  # lexical precedence than the shorter one. For example, one would expect the tables <tt>paper_boxes</tt> and <tt>papers</tt>
@@ -777,37 +866,33 @@ module ActiveRecord
777
866
  # custom <tt>join_table</tt> option if you need to.
778
867
  #
779
868
  # Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through
780
- # has_and_belongs_to_many associations. Records returned from join tables with additional attributes will be marked as
781
- # ReadOnly (because we can't save changes to the additional attrbutes). It's strongly recommended that you upgrade any
869
+ # +has_and_belongs_to_many+ associations. Records returned from join tables with additional attributes will be marked as
870
+ # +ReadOnly+ (because we can't save changes to the additional attributes). It's strongly recommended that you upgrade any
782
871
  # associations with attributes to a real join model (see introduction).
783
872
  #
784
- # Adds the following methods for retrieval and query.
873
+ # Adds the following methods for retrieval and query:
785
874
  # +collection+ is replaced with the symbol passed as the first argument, so
786
875
  # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
787
876
  # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
788
- # An empty array is returned if none is found.
877
+ # An empty array is returned if none are found.
789
878
  # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table
790
- # (collection.push and collection.concat are aliases to this method).
791
- # * <tt>collection.push_with_attributes(object, join_attributes)</tt> - adds one to the collection by creating an association in the join table that
792
- # also holds the attributes from <tt>join_attributes</tt> (should be a hash with the column names as keys). This can be used to have additional
793
- # attributes on the join, which will be injected into the associated objects when they are retrieved through the collection.
794
- # (collection.concat_with_attributes is an alias to this method). This method is now deprecated.
879
+ # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
795
880
  # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table.
796
881
  # This does not destroy the objects.
797
- # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
798
- # * <tt>collection_singular_ids</tt> - returns an array of the associated objects ids
882
+ # * <tt>collection=objects</tt> - replaces the collection's content by deleting and adding objects as appropriate.
883
+ # * <tt>collection_singular_ids</tt> - returns an array of the associated objects' ids
799
884
  # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
800
885
  # * <tt>collection.clear</tt> - removes every object from the collection. This does not destroy the objects.
801
- # * <tt>collection.empty?</tt> - returns true if there are no associated objects.
886
+ # * <tt>collection.empty?</tt> - returns +true+ if there are no associated objects.
802
887
  # * <tt>collection.size</tt> - returns the number of associated objects.
803
888
  # * <tt>collection.find(id)</tt> - finds an associated object responding to the +id+ and that
804
889
  # meets the condition that it has to be associated with this object.
805
890
  # * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
806
- # with +attributes+ and linked to this object through the join table but has not yet been saved.
891
+ # with +attributes+ and linked to this object through the join table, but has not yet been saved.
807
892
  # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
808
- # with +attributes+ and linked to this object through the join table and that has already been saved (if it passed the validation).
893
+ # with +attributes+, linked to this object through the join table, and that has already been saved (if it passed the validation).
809
894
  #
810
- # Example: An Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
895
+ # Example: A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
811
896
  # * <tt>Developer#projects</tt>
812
897
  # * <tt>Developer#projects<<</tt>
813
898
  # * <tt>Developer#projects.delete</tt>
@@ -827,30 +912,31 @@ module ActiveRecord
827
912
  # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
828
913
  # +Project+ class, but if the real class name is +SuperProject+, you'll have to specify it with this option.
829
914
  # * <tt>:join_table</tt> - specify the name of the join table if the default based on lexical order isn't what you want.
830
- # WARNING: If you're overwriting the table name of either class, the table_name method MUST be declared underneath any
831
- # has_and_belongs_to_many declaration in order to work.
915
+ # WARNING: If you're overwriting the table name of either class, the +table_name+ method MUST be declared underneath any
916
+ # +has_and_belongs_to_many+ declaration in order to work.
832
917
  # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
833
- # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_and_belongs_to_many association
834
- # will use "person_id" as the default foreign_key.
918
+ # of this class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +has_and_belongs_to_many+ association
919
+ # will use +person_id+ as the default +foreign_key+.
835
920
  # * <tt>:association_foreign_key</tt> - specify the association foreign key used for the association. By default this is
836
- # guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is +Project+,
837
- # the has_and_belongs_to_many association will use "project_id" as the default association foreign_key.
838
- # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE"
839
- # sql fragment, such as "authorized = 1".
840
- # * <tt>:order</tt> - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment, such as "last_name, first_name DESC"
841
- # * <tt>:uniq</tt> - if set to true, duplicate associated objects will be ignored by accessors and query methods
842
- # * <tt>:finder_sql</tt> - overwrite the default generated SQL used to fetch the association with a manual one
843
- # * <tt>:delete_sql</tt> - overwrite the default generated SQL used to remove links between the associated
844
- # classes with a manual one
845
- # * <tt>:insert_sql</tt> - overwrite the default generated SQL used to add links between the associated classes
846
- # with a manual one
921
+ # guessed to be the name of the associated class in lower-case and +_id+ suffixed. So if the associated class is +Project+,
922
+ # the +has_and_belongs_to_many+ association will use +project_id+ as the default association +foreign_key+.
923
+ # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a +WHERE+
924
+ # SQL fragment, such as <tt>authorized = 1</tt>.
925
+ # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
926
+ # such as <tt>last_name, first_name DESC</tt>
927
+ # * <tt>:uniq</tt> - if set to +true+, duplicate associated objects will be ignored by accessors and query methods
928
+ # * <tt>:finder_sql</tt> - overwrite the default generated SQL statement used to fetch the association with a manual statement
929
+ # * <tt>:delete_sql</tt> - overwrite the default generated SQL statement used to remove links between the associated
930
+ # classes with a manual statement
931
+ # * <tt>:insert_sql</tt> - overwrite the default generated SQL statement used to add links between the associated classes
932
+ # with a manual statement
847
933
  # * <tt>:extend</tt> - anonymous module for extending the proxy, see "Association extensions".
848
934
  # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
849
- # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
935
+ # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
850
936
  # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
851
937
  # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
852
- # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
853
- # include the joined columns.
938
+ # * <tt>:select</tt>: By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
939
+ # but not include the joined columns.
854
940
  #
855
941
  # Option examples:
856
942
  # has_and_belongs_to_many :projects
@@ -877,12 +963,6 @@ module ActiveRecord
877
963
  end_eval
878
964
 
879
965
  add_association_callbacks(reflection.name, options)
880
-
881
- # deprecated api
882
- deprecated_collection_count_method(reflection.name)
883
- deprecated_add_association_relation(reflection.name)
884
- deprecated_remove_association_relation(reflection.name)
885
- deprecated_has_collection_method(reflection.name)
886
966
  end
887
967
 
888
968
  private
@@ -959,7 +1039,7 @@ module ActiveRecord
959
1039
  end
960
1040
  end
961
1041
 
962
- def collection_accessor_methods(reflection, association_proxy_class)
1042
+ def collection_accessor_methods(reflection, association_proxy_class, writer = true)
963
1043
  collection_reader_method(reflection, association_proxy_class)
964
1044
 
965
1045
  define_method("#{reflection.name}=") do |new_value|
@@ -976,7 +1056,7 @@ module ActiveRecord
976
1056
  define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
977
1057
  ids = (new_value || []).reject { |nid| nid.blank? }
978
1058
  send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
979
- end
1059
+ end if writer
980
1060
  end
981
1061
 
982
1062
  def add_multiple_associated_save_callbacks(association_name)
@@ -1047,74 +1127,49 @@ module ActiveRecord
1047
1127
  []
1048
1128
  end
1049
1129
 
1130
+ # See HasManyAssociation#delete_records. Dependent associations
1131
+ # delete children, otherwise foreign key is set to NULL.
1050
1132
  def configure_dependency_for_has_many(reflection)
1051
- if reflection.options[:dependent] == true
1052
- ::ActiveSupport::Deprecation.warn("The :dependent => true option is deprecated and will be removed from Rails 2.0. Please use :dependent => :destroy instead. See http://www.rubyonrails.org/deprecation for details.", caller)
1053
- end
1054
-
1055
- if reflection.options[:dependent] && reflection.options[:exclusively_dependent]
1056
- raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options. You may specify one or the other.'
1057
- end
1058
-
1059
- if reflection.options[:exclusively_dependent]
1060
- reflection.options[:dependent] = :delete_all
1061
- ::ActiveSupport::Deprecation.warn("The :exclusively_dependent option is deprecated and will be removed from Rails 2.0. Please use :dependent => :delete_all instead. See http://www.rubyonrails.org/deprecation for details.", caller)
1062
- end
1063
-
1064
- # See HasManyAssociation#delete_records. Dependent associations
1065
- # delete children, otherwise foreign key is set to NULL.
1066
-
1067
- # Add polymorphic type if the :as option is present
1068
- dependent_conditions = %(#{reflection.primary_key_name} = \#{record.quoted_id})
1069
- if reflection.options[:as]
1070
- dependent_conditions += " AND #{reflection.options[:as]}_type = '#{base_class.name}'"
1071
- end
1072
-
1073
- case reflection.options[:dependent]
1074
- when :destroy, true
1075
- module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'"
1076
- when :delete_all
1077
- module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }"
1078
- when :nullify
1079
- module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL), %(#{dependent_conditions})) }"
1080
- when nil, false
1081
- # pass
1082
- else
1083
- raise ArgumentError, 'The :dependent option expects either :destroy, :delete_all, or :nullify'
1133
+ if reflection.options.include?(:dependent)
1134
+ # Add polymorphic type if the :as option is present
1135
+ dependent_conditions = []
1136
+ dependent_conditions << "#{reflection.primary_key_name} = \#{record.quoted_id}"
1137
+ dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as]
1138
+ dependent_conditions << sanitize_sql(reflection.options[:conditions]) if reflection.options[:conditions]
1139
+ dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
1140
+
1141
+ case reflection.options[:dependent]
1142
+ when :destroy
1143
+ module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'"
1144
+ when :delete_all
1145
+ module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }"
1146
+ when :nullify
1147
+ module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL), %(#{dependent_conditions})) }"
1148
+ else
1149
+ raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})"
1150
+ end
1084
1151
  end
1085
1152
  end
1086
1153
 
1087
1154
  def configure_dependency_for_has_one(reflection)
1088
- case reflection.options[:dependent]
1089
- when :destroy, true
1090
- module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'"
1091
- when :delete
1092
- module_eval "before_destroy '#{reflection.class_name}.delete(#{reflection.name}.id) unless #{reflection.name}.nil?'"
1093
- when :nullify
1094
- module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil) unless #{reflection.name}.nil?'"
1095
- when nil, false
1096
- # pass
1097
- else
1098
- raise ArgumentError, "The :dependent option expects either :destroy, :delete or :nullify."
1155
+ if reflection.options.include?(:dependent)
1156
+ case reflection.options[:dependent]
1157
+ when :destroy
1158
+ module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'"
1159
+ when :delete
1160
+ module_eval "before_destroy '#{reflection.class_name}.delete(#{reflection.name}.id) unless #{reflection.name}.nil?'"
1161
+ when :nullify
1162
+ module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil) unless #{reflection.name}.nil?'"
1163
+ else
1164
+ raise ArgumentError, "The :dependent option expects either :destroy, :delete or :nullify (#{reflection.options[:dependent].inspect})"
1165
+ end
1099
1166
  end
1100
1167
  end
1101
-
1102
-
1103
- def add_deprecated_api_for_has_many(association_name)
1104
- deprecated_collection_count_method(association_name)
1105
- deprecated_add_association_relation(association_name)
1106
- deprecated_remove_association_relation(association_name)
1107
- deprecated_has_collection_method(association_name)
1108
- deprecated_find_in_collection_method(association_name)
1109
- deprecated_find_all_in_collection_method(association_name)
1110
- deprecated_collection_create_method(association_name)
1111
- deprecated_collection_build_method(association_name)
1112
- end
1113
1168
 
1114
1169
  def create_has_many_reflection(association_id, options, &extension)
1115
1170
  options.assert_valid_keys(
1116
1171
  :class_name, :table_name, :foreign_key,
1117
- :exclusively_dependent, :dependent,
1172
+ :dependent,
1118
1173
  :select, :conditions, :include, :order, :group, :limit, :offset,
1119
1174
  :as, :through, :source, :source_type,
1120
1175
  :uniq,
@@ -1123,7 +1178,7 @@ module ActiveRecord
1123
1178
  :extend
1124
1179
  )
1125
1180
 
1126
- options[:extend] = create_extension_module(association_id, extension) if block_given?
1181
+ options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given?
1127
1182
 
1128
1183
  create_reflection(:has_many, association_id, options, self)
1129
1184
  end
@@ -1161,7 +1216,7 @@ module ActiveRecord
1161
1216
  :extend
1162
1217
  )
1163
1218
 
1164
- options[:extend] = create_extension_module(association_id, extension) if block_given?
1219
+ options[:extend] = create_extension_modules(association_id, extension, options[:extend]) if block_given?
1165
1220
 
1166
1221
  reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
1167
1222
 
@@ -1192,15 +1247,14 @@ module ActiveRecord
1192
1247
 
1193
1248
  def construct_finder_sql_with_included_associations(options, join_dependency)
1194
1249
  scope = scope(:find)
1195
- sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || table_name} "
1250
+ sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
1196
1251
  sql << join_dependency.join_associations.collect{|join| join.association_join }.join
1197
1252
 
1198
1253
  add_joins!(sql, options, scope)
1199
1254
  add_conditions!(sql, options[:conditions], scope)
1200
1255
  add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
1201
1256
 
1202
- sql << "GROUP BY #{options[:group]} " if options[:group]
1203
-
1257
+ add_group!(sql, options[:group], scope)
1204
1258
  add_order!(sql, options[:order], scope)
1205
1259
  add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
1206
1260
  add_lock!(sql, options, scope)
@@ -1210,29 +1264,31 @@ module ActiveRecord
1210
1264
 
1211
1265
  def add_limited_ids_condition!(sql, options, join_dependency)
1212
1266
  unless (id_list = select_limited_ids_list(options, join_dependency)).empty?
1213
- sql << "#{condition_word(sql)} #{table_name}.#{primary_key} IN (#{id_list}) "
1267
+ sql << "#{condition_word(sql)} #{connection.quote_table_name table_name}.#{primary_key} IN (#{id_list}) "
1214
1268
  else
1215
1269
  throw :invalid_query
1216
1270
  end
1217
1271
  end
1218
-
1272
+
1219
1273
  def select_limited_ids_list(options, join_dependency)
1274
+ pk = columns_hash[primary_key]
1275
+
1220
1276
  connection.select_all(
1221
1277
  construct_finder_sql_for_association_limiting(options, join_dependency),
1222
1278
  "#{name} Load IDs For Limited Eager Loading"
1223
- ).collect { |row| connection.quote(row[primary_key]) }.join(", ")
1279
+ ).collect { |row| connection.quote(row[primary_key], pk) }.join(", ")
1224
1280
  end
1225
1281
 
1226
1282
  def construct_finder_sql_for_association_limiting(options, join_dependency)
1227
1283
  scope = scope(:find)
1228
- is_distinct = include_eager_conditions?(options) || include_eager_order?(options)
1284
+ is_distinct = !options[:joins].blank? || include_eager_conditions?(options) || include_eager_order?(options)
1229
1285
  sql = "SELECT "
1230
1286
  if is_distinct
1231
- sql << connection.distinct("#{table_name}.#{primary_key}", options[:order])
1287
+ sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", options[:order])
1232
1288
  else
1233
1289
  sql << primary_key
1234
1290
  end
1235
- sql << " FROM #{table_name} "
1291
+ sql << " FROM #{connection.quote_table_name table_name} "
1236
1292
 
1237
1293
  if is_distinct
1238
1294
  sql << join_dependency.join_associations.collect(&:association_join).join
@@ -1240,14 +1296,16 @@ module ActiveRecord
1240
1296
  end
1241
1297
 
1242
1298
  add_conditions!(sql, options[:conditions], scope)
1243
- if options[:order]
1244
- if is_distinct
1245
- connection.add_order_by_for_association_limiting!(sql, options)
1246
- else
1247
- sql << "ORDER BY #{options[:order]}"
1248
- end
1299
+ add_group!(sql, options[:group], scope)
1300
+
1301
+ if options[:order] && is_distinct
1302
+ connection.add_order_by_for_association_limiting!(sql, options)
1303
+ else
1304
+ add_order!(sql, options[:order], scope)
1249
1305
  end
1306
+
1250
1307
  add_limit!(sql, options, scope)
1308
+
1251
1309
  return sanitize_sql(sql)
1252
1310
  end
1253
1311
 
@@ -1282,7 +1340,7 @@ module ActiveRecord
1282
1340
 
1283
1341
  def column_aliases(join_dependency)
1284
1342
  join_dependency.joins.collect{|join| join.column_names_with_alias.collect{|column_name, aliased_name|
1285
- "#{join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ")
1343
+ "#{connection.quote_table_name join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ")
1286
1344
  end
1287
1345
 
1288
1346
  def add_association_callbacks(association_name, options)
@@ -1303,14 +1361,14 @@ module ActiveRecord
1303
1361
  sql =~ /where/i ? " AND " : "WHERE "
1304
1362
  end
1305
1363
 
1306
- def create_extension_module(association_id, extension)
1364
+ def create_extension_modules(association_id, block_extension, extensions)
1307
1365
  extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension"
1308
1366
 
1309
1367
  silence_warnings do
1310
- Object.const_set(extension_module_name, Module.new(&extension))
1368
+ Object.const_set(extension_module_name, Module.new(&block_extension))
1311
1369
  end
1312
-
1313
- extension_module_name.constantize
1370
+
1371
+ Array(extensions).push(extension_module_name.constantize)
1314
1372
  end
1315
1373
 
1316
1374
  class JoinDependency # :nodoc:
@@ -1343,11 +1401,34 @@ module ActiveRecord
1343
1401
  end
1344
1402
  construct(@base_records_hash[primary_id], @associations, join_associations.dup, row)
1345
1403
  end
1404
+ remove_duplicate_results!(join_base.active_record, @base_records_in_order, @associations)
1346
1405
  return @base_records_in_order
1347
1406
  end
1348
1407
 
1349
- def aliased_table_names_for(table_name)
1350
- joins.select{|join| join.table_name == table_name }.collect{|join| join.aliased_table_name}
1408
+ def remove_duplicate_results!(base, records, associations)
1409
+ case associations
1410
+ when Symbol, String
1411
+ reflection = base.reflections[associations]
1412
+ if reflection && [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
1413
+ records.each { |record| record.send(reflection.name).target.uniq! }
1414
+ end
1415
+ when Array
1416
+ associations.each do |association|
1417
+ remove_duplicate_results!(base, records, association)
1418
+ end
1419
+ when Hash
1420
+ associations.keys.each do |name|
1421
+ reflection = base.reflections[name]
1422
+ is_collection = [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
1423
+
1424
+ parent_records = records.map do |record|
1425
+ next unless record.send(reflection.name)
1426
+ is_collection ? record.send(reflection.name).target.uniq! : record.send(reflection.name)
1427
+ end.flatten.compact
1428
+
1429
+ remove_duplicate_results!(reflection.class_name.constantize, parent_records, associations[name]) unless parent_records.empty?
1430
+ end
1431
+ end
1351
1432
  end
1352
1433
 
1353
1434
  protected
@@ -1358,7 +1439,7 @@ module ActiveRecord
1358
1439
  reflection = parent.reflections[associations.to_s.intern] or
1359
1440
  raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
1360
1441
  @reflections << reflection
1361
- @joins << JoinAssociation.new(reflection, self, parent)
1442
+ @joins << build_join_association(reflection, parent)
1362
1443
  when Array
1363
1444
  associations.each do |association|
1364
1445
  build(association, parent)
@@ -1373,6 +1454,11 @@ module ActiveRecord
1373
1454
  end
1374
1455
  end
1375
1456
 
1457
+ # overridden in InnerJoinDependency subclass
1458
+ def build_join_association(reflection, parent)
1459
+ JoinAssociation.new(reflection, self, parent)
1460
+ end
1461
+
1376
1462
  def construct(parent, associations, joins, row)
1377
1463
  case associations
1378
1464
  when Symbol, String
@@ -1402,7 +1488,7 @@ module ActiveRecord
1402
1488
 
1403
1489
  return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
1404
1490
  association = join.instantiate(row)
1405
- collection.target.push(association) unless collection.target.include?(association)
1491
+ collection.target.push(association)
1406
1492
  when :has_one
1407
1493
  return if record.id.to_s != join.parent.record_id(row).to_s
1408
1494
  association = join.instantiate(row) unless row[join.aliased_primary_key].nil?
@@ -1458,7 +1544,7 @@ module ActiveRecord
1458
1544
  end
1459
1545
 
1460
1546
  def instantiate(row)
1461
- @cached_record[record_id(row)] ||= active_record.instantiate(extract_record(row))
1547
+ @cached_record[record_id(row)] ||= active_record.send(:instantiate, extract_record(row))
1462
1548
  end
1463
1549
  end
1464
1550
 
@@ -1507,53 +1593,60 @@ module ActiveRecord
1507
1593
  end
1508
1594
 
1509
1595
  def association_join
1596
+ connection = reflection.active_record.connection
1510
1597
  join = case reflection.macro
1511
1598
  when :has_and_belongs_to_many
1512
- " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1599
+ " #{join_type} %s ON %s.%s = %s.%s " % [
1513
1600
  table_alias_for(options[:join_table], aliased_join_table_name),
1514
- aliased_join_table_name,
1515
- options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key,
1516
- parent.aliased_table_name, reflection.active_record.primary_key] +
1517
- " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1518
- table_name_and_alias, aliased_table_name, klass.primary_key,
1519
- aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key
1601
+ connection.quote_table_name(aliased_join_table_name),
1602
+ options[:foreign_key] || reflection.active_record.to_s.foreign_key,
1603
+ connection.quote_table_name(parent.aliased_table_name),
1604
+ reflection.active_record.primary_key] +
1605
+ " #{join_type} %s ON %s.%s = %s.%s " % [
1606
+ table_name_and_alias,
1607
+ connection.quote_table_name(aliased_table_name),
1608
+ klass.primary_key,
1609
+ connection.quote_table_name(aliased_join_table_name),
1610
+ options[:association_foreign_key] || klass.to_s.foreign_key
1520
1611
  ]
1521
1612
  when :has_many, :has_one
1522
1613
  case
1523
1614
  when reflection.macro == :has_many && reflection.options[:through]
1524
1615
  through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
1525
-
1526
- jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
1527
- first_key = second_key = as_extra = nil
1616
+
1617
+ jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
1618
+ first_key = second_key = as_extra = nil
1528
1619
 
1529
1620
  if through_reflection.options[:as] # has_many :through against a polymorphic join
1530
1621
  jt_foreign_key = through_reflection.options[:as].to_s + '_id'
1531
1622
  jt_as_extra = " AND %s.%s = %s" % [
1532
- aliased_join_table_name, reflection.active_record.connection.quote_column_name(through_reflection.options[:as].to_s + '_type'),
1533
- klass.quote_value(parent.active_record.base_class.name)
1623
+ connection.quote_table_name(aliased_join_table_name),
1624
+ connection.quote_column_name(through_reflection.options[:as].to_s + '_type'),
1625
+ klass.quote_value(parent.active_record.base_class.name)
1534
1626
  ]
1535
1627
  else
1536
- jt_foreign_key = through_reflection.primary_key_name
1628
+ jt_foreign_key = through_reflection.primary_key_name
1537
1629
  end
1538
-
1630
+
1539
1631
  case source_reflection.macro
1540
1632
  when :has_many
1541
- if source_reflection.options[:as]
1542
- first_key = "#{source_reflection.options[:as]}_id"
1543
- second_key = options[:foreign_key] || primary_key
1633
+ if source_reflection.options[:as]
1634
+ first_key = "#{source_reflection.options[:as]}_id"
1635
+ second_key = options[:foreign_key] || primary_key
1544
1636
  as_extra = " AND %s.%s = %s" % [
1545
- aliased_table_name, reflection.active_record.connection.quote_column_name("#{source_reflection.options[:as]}_type"),
1546
- klass.quote_value(source_reflection.active_record.base_class.name)
1637
+ connection.quote_table_name(aliased_table_name),
1638
+ connection.quote_column_name("#{source_reflection.options[:as]}_type"),
1639
+ klass.quote_value(source_reflection.active_record.base_class.name)
1547
1640
  ]
1548
1641
  else
1549
- first_key = through_reflection.klass.base_class.to_s.classify.foreign_key
1642
+ first_key = through_reflection.klass.base_class.to_s.foreign_key
1550
1643
  second_key = options[:foreign_key] || primary_key
1551
1644
  end
1552
1645
 
1553
1646
  unless through_reflection.klass.descends_from_active_record?
1554
1647
  jt_sti_extra = " AND %s.%s = %s" % [
1555
- aliased_join_table_name,
1556
- reflection.active_record.connection.quote_column_name(through_reflection.active_record.inheritance_column),
1648
+ connection.quote_table_name(aliased_join_table_name),
1649
+ connection.quote_column_name(through_reflection.active_record.inheritance_column),
1557
1650
  through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
1558
1651
  end
1559
1652
  when :belongs_to
@@ -1561,62 +1654,67 @@ module ActiveRecord
1561
1654
  if reflection.options[:source_type]
1562
1655
  second_key = source_reflection.association_foreign_key
1563
1656
  jt_source_extra = " AND %s.%s = %s" % [
1564
- aliased_join_table_name, reflection.active_record.connection.quote_column_name(reflection.source_reflection.options[:foreign_type]),
1565
- klass.quote_value(reflection.options[:source_type])
1657
+ connection.quote_table_name(aliased_join_table_name),
1658
+ connection.quote_column_name(reflection.source_reflection.options[:foreign_type]),
1659
+ klass.quote_value(reflection.options[:source_type])
1566
1660
  ]
1567
1661
  else
1568
- second_key = source_reflection.options[:foreign_key] || klass.to_s.classify.foreign_key
1662
+ second_key = source_reflection.primary_key_name
1569
1663
  end
1570
1664
  end
1571
-
1572
- " LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s%s%s) " % [
1665
+
1666
+ " #{join_type} %s ON (%s.%s = %s.%s%s%s%s) " % [
1573
1667
  table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
1574
- parent.aliased_table_name, reflection.active_record.connection.quote_column_name(parent.primary_key),
1575
- aliased_join_table_name, reflection.active_record.connection.quote_column_name(jt_foreign_key),
1668
+ connection.quote_table_name(parent.aliased_table_name),
1669
+ connection.quote_column_name(parent.primary_key),
1670
+ connection.quote_table_name(aliased_join_table_name),
1671
+ connection.quote_column_name(jt_foreign_key),
1576
1672
  jt_as_extra, jt_source_extra, jt_sti_extra
1577
1673
  ] +
1578
- " LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s) " % [
1674
+ " #{join_type} %s ON (%s.%s = %s.%s%s) " % [
1579
1675
  table_name_and_alias,
1580
- aliased_table_name, reflection.active_record.connection.quote_column_name(first_key),
1581
- aliased_join_table_name, reflection.active_record.connection.quote_column_name(second_key),
1676
+ connection.quote_table_name(aliased_table_name),
1677
+ connection.quote_column_name(first_key),
1678
+ connection.quote_table_name(aliased_join_table_name),
1679
+ connection.quote_column_name(second_key),
1582
1680
  as_extra
1583
1681
  ]
1584
-
1585
- when reflection.macro == :has_many && reflection.options[:as]
1586
- " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s" % [
1682
+
1683
+ when reflection.options[:as] && [:has_many, :has_one].include?(reflection.macro)
1684
+ " #{join_type} %s ON %s.%s = %s.%s AND %s.%s = %s" % [
1587
1685
  table_name_and_alias,
1588
- aliased_table_name, "#{reflection.options[:as]}_id",
1589
- parent.aliased_table_name, parent.primary_key,
1590
- aliased_table_name, "#{reflection.options[:as]}_type",
1686
+ connection.quote_table_name(aliased_table_name),
1687
+ "#{reflection.options[:as]}_id",
1688
+ connection.quote_table_name(parent.aliased_table_name),
1689
+ parent.primary_key,
1690
+ connection.quote_table_name(aliased_table_name),
1691
+ "#{reflection.options[:as]}_type",
1591
1692
  klass.quote_value(parent.active_record.base_class.name)
1592
1693
  ]
1593
- when reflection.macro == :has_one && reflection.options[:as]
1594
- " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s " % [
1595
- table_name_and_alias,
1596
- aliased_table_name, "#{reflection.options[:as]}_id",
1597
- parent.aliased_table_name, parent.primary_key,
1598
- aliased_table_name, "#{reflection.options[:as]}_type",
1599
- klass.quote_value(reflection.active_record.base_class.name)
1600
- ]
1601
1694
  else
1602
1695
  foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
1603
- " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1696
+ " #{join_type} %s ON %s.%s = %s.%s " % [
1604
1697
  table_name_and_alias,
1605
- aliased_table_name, foreign_key,
1606
- parent.aliased_table_name, parent.primary_key
1698
+ aliased_table_name,
1699
+ foreign_key,
1700
+ parent.aliased_table_name,
1701
+ parent.primary_key
1607
1702
  ]
1608
1703
  end
1609
1704
  when :belongs_to
1610
- " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1611
- table_name_and_alias, aliased_table_name, reflection.klass.primary_key,
1612
- parent.aliased_table_name, options[:foreign_key] || klass.to_s.foreign_key
1705
+ " #{join_type} %s ON %s.%s = %s.%s " % [
1706
+ table_name_and_alias,
1707
+ connection.quote_table_name(aliased_table_name),
1708
+ reflection.klass.primary_key,
1709
+ connection.quote_table_name(parent.aliased_table_name),
1710
+ options[:foreign_key] || klass.to_s.foreign_key
1613
1711
  ]
1614
1712
  else
1615
1713
  ""
1616
1714
  end || ''
1617
1715
  join << %(AND %s.%s = %s ) % [
1618
- aliased_table_name,
1619
- reflection.active_record.connection.quote_column_name(klass.inheritance_column),
1716
+ connection.quote_table_name(aliased_table_name),
1717
+ connection.quote_column_name(klass.inheritance_column),
1620
1718
  klass.quote_value(klass.name.demodulize)] unless klass.descends_from_active_record?
1621
1719
 
1622
1720
  [through_reflection, reflection].each do |ref|
@@ -1633,7 +1731,7 @@ module ActiveRecord
1633
1731
  end
1634
1732
 
1635
1733
  def table_alias_for(table_name, table_alias)
1636
- "#{table_name} #{table_alias if table_name != table_alias}".strip
1734
+ "#{reflection.active_record.connection.quote_table_name(table_name)} #{table_alias if table_name != table_alias}".strip
1637
1735
  end
1638
1736
 
1639
1737
  def table_name_and_alias
@@ -1643,8 +1741,29 @@ module ActiveRecord
1643
1741
  def interpolate_sql(sql)
1644
1742
  instance_eval("%@#{sql.gsub('@', '\@')}@")
1645
1743
  end
1744
+
1745
+ private
1746
+
1747
+ def join_type
1748
+ "LEFT OUTER JOIN"
1749
+ end
1750
+ end
1751
+ end
1752
+
1753
+ class InnerJoinDependency < JoinDependency # :nodoc:
1754
+ protected
1755
+ def build_join_association(reflection, parent)
1756
+ InnerJoinAssociation.new(reflection, self, parent)
1757
+ end
1758
+
1759
+ class InnerJoinAssociation < JoinAssociation
1760
+ private
1761
+ def join_type
1762
+ "INNER JOIN"
1763
+ end
1646
1764
  end
1647
1765
  end
1766
+
1648
1767
  end
1649
1768
  end
1650
1769
  end