activerecord 1.0.0 → 3.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 (178) hide show
  1. data/CHANGELOG +5518 -76
  2. data/README.rdoc +222 -0
  3. data/examples/performance.rb +162 -0
  4. data/examples/simple.rb +14 -0
  5. data/lib/active_record/aggregations.rb +192 -80
  6. data/lib/active_record/association_preload.rb +403 -0
  7. data/lib/active_record/associations/association_collection.rb +545 -53
  8. data/lib/active_record/associations/association_proxy.rb +295 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +127 -36
  12. data/lib/active_record/associations/has_many_association.rb +108 -84
  13. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  14. data/lib/active_record/associations/has_one_association.rb +143 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  16. data/lib/active_record/associations/through_association_scope.rb +154 -0
  17. data/lib/active_record/associations.rb +2086 -368
  18. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  19. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  20. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  21. data/lib/active_record/attribute_methods/query.rb +39 -0
  22. data/lib/active_record/attribute_methods/read.rb +116 -0
  23. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  24. data/lib/active_record/attribute_methods/write.rb +37 -0
  25. data/lib/active_record/attribute_methods.rb +60 -0
  26. data/lib/active_record/autosave_association.rb +369 -0
  27. data/lib/active_record/base.rb +1603 -721
  28. data/lib/active_record/callbacks.rb +176 -225
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  30. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  31. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  32. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  33. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  34. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  35. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  36. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  37. data/lib/active_record/connection_adapters/abstract_adapter.rb +165 -279
  38. data/lib/active_record/connection_adapters/mysql_adapter.rb +594 -82
  39. data/lib/active_record/connection_adapters/postgresql_adapter.rb +988 -135
  40. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +365 -71
  42. data/lib/active_record/counter_cache.rb +115 -0
  43. data/lib/active_record/dynamic_finder_match.rb +53 -0
  44. data/lib/active_record/dynamic_scope_match.rb +32 -0
  45. data/lib/active_record/errors.rb +172 -0
  46. data/lib/active_record/fixtures.rb +941 -105
  47. data/lib/active_record/locale/en.yml +40 -0
  48. data/lib/active_record/locking/optimistic.rb +172 -0
  49. data/lib/active_record/locking/pessimistic.rb +55 -0
  50. data/lib/active_record/log_subscriber.rb +48 -0
  51. data/lib/active_record/migration.rb +617 -0
  52. data/lib/active_record/named_scope.rb +138 -0
  53. data/lib/active_record/nested_attributes.rb +417 -0
  54. data/lib/active_record/observer.rb +105 -36
  55. data/lib/active_record/persistence.rb +291 -0
  56. data/lib/active_record/query_cache.rb +36 -0
  57. data/lib/active_record/railtie.rb +91 -0
  58. data/lib/active_record/railties/controller_runtime.rb +38 -0
  59. data/lib/active_record/railties/databases.rake +512 -0
  60. data/lib/active_record/reflection.rb +364 -87
  61. data/lib/active_record/relation/batches.rb +89 -0
  62. data/lib/active_record/relation/calculations.rb +286 -0
  63. data/lib/active_record/relation/finder_methods.rb +355 -0
  64. data/lib/active_record/relation/predicate_builder.rb +41 -0
  65. data/lib/active_record/relation/query_methods.rb +261 -0
  66. data/lib/active_record/relation/spawn_methods.rb +112 -0
  67. data/lib/active_record/relation.rb +393 -0
  68. data/lib/active_record/schema.rb +59 -0
  69. data/lib/active_record/schema_dumper.rb +195 -0
  70. data/lib/active_record/serialization.rb +60 -0
  71. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  72. data/lib/active_record/session_store.rb +340 -0
  73. data/lib/active_record/test_case.rb +67 -0
  74. data/lib/active_record/timestamp.rb +88 -0
  75. data/lib/active_record/transactions.rb +329 -75
  76. data/lib/active_record/validations/associated.rb +48 -0
  77. data/lib/active_record/validations/uniqueness.rb +185 -0
  78. data/lib/active_record/validations.rb +58 -179
  79. data/lib/active_record/version.rb +9 -0
  80. data/lib/active_record.rb +100 -24
  81. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  82. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  83. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  84. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  85. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  86. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  87. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  88. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  89. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  90. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  91. data/lib/rails/generators/active_record.rb +27 -0
  92. metadata +216 -158
  93. data/README +0 -361
  94. data/RUNNING_UNIT_TESTS +0 -36
  95. data/dev-utils/eval_debugger.rb +0 -9
  96. data/examples/associations.rb +0 -87
  97. data/examples/shared_setup.rb +0 -15
  98. data/examples/validation.rb +0 -88
  99. data/install.rb +0 -60
  100. data/lib/active_record/deprecated_associations.rb +0 -70
  101. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  102. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  103. data/lib/active_record/support/clean_logger.rb +0 -10
  104. data/lib/active_record/support/inflector.rb +0 -70
  105. data/lib/active_record/vendor/mysql.rb +0 -1117
  106. data/lib/active_record/vendor/simple.rb +0 -702
  107. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  108. data/lib/active_record/wrappings.rb +0 -59
  109. data/rakefile +0 -122
  110. data/test/abstract_unit.rb +0 -16
  111. data/test/aggregations_test.rb +0 -34
  112. data/test/all.sh +0 -8
  113. data/test/associations_test.rb +0 -477
  114. data/test/base_test.rb +0 -513
  115. data/test/class_inheritable_attributes_test.rb +0 -33
  116. data/test/connections/native_mysql/connection.rb +0 -24
  117. data/test/connections/native_postgresql/connection.rb +0 -24
  118. data/test/connections/native_sqlite/connection.rb +0 -24
  119. data/test/deprecated_associations_test.rb +0 -336
  120. data/test/finder_test.rb +0 -67
  121. data/test/fixtures/accounts/signals37 +0 -3
  122. data/test/fixtures/accounts/unknown +0 -2
  123. data/test/fixtures/auto_id.rb +0 -4
  124. data/test/fixtures/column_name.rb +0 -3
  125. data/test/fixtures/companies/first_client +0 -6
  126. data/test/fixtures/companies/first_firm +0 -4
  127. data/test/fixtures/companies/second_client +0 -6
  128. data/test/fixtures/company.rb +0 -37
  129. data/test/fixtures/company_in_module.rb +0 -33
  130. data/test/fixtures/course.rb +0 -3
  131. data/test/fixtures/courses/java +0 -2
  132. data/test/fixtures/courses/ruby +0 -2
  133. data/test/fixtures/customer.rb +0 -30
  134. data/test/fixtures/customers/david +0 -6
  135. data/test/fixtures/db_definitions/mysql.sql +0 -96
  136. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  137. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  138. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  139. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  140. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  141. data/test/fixtures/default.rb +0 -2
  142. data/test/fixtures/developer.rb +0 -8
  143. data/test/fixtures/developers/david +0 -2
  144. data/test/fixtures/developers/jamis +0 -2
  145. data/test/fixtures/developers_projects/david_action_controller +0 -2
  146. data/test/fixtures/developers_projects/david_active_record +0 -2
  147. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  148. data/test/fixtures/entrant.rb +0 -3
  149. data/test/fixtures/entrants/first +0 -3
  150. data/test/fixtures/entrants/second +0 -3
  151. data/test/fixtures/entrants/third +0 -3
  152. data/test/fixtures/fixture_database.sqlite +0 -0
  153. data/test/fixtures/fixture_database_2.sqlite +0 -0
  154. data/test/fixtures/movie.rb +0 -5
  155. data/test/fixtures/movies/first +0 -2
  156. data/test/fixtures/movies/second +0 -2
  157. data/test/fixtures/project.rb +0 -3
  158. data/test/fixtures/projects/action_controller +0 -2
  159. data/test/fixtures/projects/active_record +0 -2
  160. data/test/fixtures/reply.rb +0 -21
  161. data/test/fixtures/subscriber.rb +0 -5
  162. data/test/fixtures/subscribers/first +0 -2
  163. data/test/fixtures/subscribers/second +0 -2
  164. data/test/fixtures/topic.rb +0 -20
  165. data/test/fixtures/topics/first +0 -9
  166. data/test/fixtures/topics/second +0 -8
  167. data/test/fixtures_test.rb +0 -20
  168. data/test/inflector_test.rb +0 -104
  169. data/test/inheritance_test.rb +0 -125
  170. data/test/lifecycle_test.rb +0 -110
  171. data/test/modules_test.rb +0 -21
  172. data/test/multiple_db_test.rb +0 -46
  173. data/test/pk_test.rb +0 -57
  174. data/test/reflection_test.rb +0 -78
  175. data/test/thread_safety_test.rb +0 -33
  176. data/test/transactions_test.rb +0 -83
  177. data/test/unconnected_test.rb +0 -24
  178. data/test/validations_test.rb +0 -126
@@ -1,74 +1,544 @@
1
- require 'active_record/associations/association_collection'
2
- require 'active_record/associations/has_many_association'
3
- require 'active_record/associations/has_and_belongs_to_many_association'
4
- require 'active_record/deprecated_associations'
1
+ require 'active_support/core_ext/array/wrap'
2
+ require 'active_support/core_ext/enumerable'
3
+ require 'active_support/core_ext/module/delegation'
4
+ require 'active_support/core_ext/object/blank'
5
+ require 'active_support/core_ext/string/conversions'
6
+ require 'active_support/core_ext/module/remove_method'
5
7
 
6
8
  module ActiveRecord
9
+ class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
10
+ def initialize(reflection, associated_class = nil)
11
+ super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
12
+ end
13
+ end
14
+
15
+ class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
16
+ def initialize(owner_class_name, reflection)
17
+ super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
18
+ end
19
+ end
20
+
21
+ class HasManyThroughAssociationPolymorphicError < ActiveRecordError #:nodoc:
22
+ def initialize(owner_class_name, reflection, source_reflection)
23
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.")
24
+ end
25
+ end
26
+
27
+ class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
28
+ def initialize(owner_class_name, reflection, source_reflection)
29
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
30
+ end
31
+ end
32
+
33
+ class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
34
+ def initialize(reflection)
35
+ through_reflection = reflection.through_reflection
36
+ source_reflection_names = reflection.source_reflection_names
37
+ source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
38
+ super("Could not find the source association(s) #{source_reflection_names.collect{ |a| a.inspect }.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
39
+ end
40
+ end
41
+
42
+ class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc:
43
+ def initialize(reflection)
44
+ through_reflection = reflection.through_reflection
45
+ source_reflection = reflection.source_reflection
46
+ super("Invalid source reflection macro :#{source_reflection.macro}#{" :through" if source_reflection.options[:through]} for has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}. Use :source to specify the source reflection.")
47
+ end
48
+ end
49
+
50
+ class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
51
+ def initialize(owner, reflection)
52
+ super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
53
+ end
54
+ end
55
+
56
+ class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
57
+ def initialize(owner, reflection)
58
+ super("Cannot associate 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 create the has_many :through record associating them.")
59
+ end
60
+ end
61
+
62
+ class HasManyThroughCantDissociateNewRecords < ActiveRecordError #:nodoc:
63
+ def initialize(owner, reflection)
64
+ 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.")
65
+ end
66
+ end
67
+
68
+ class HasAndBelongsToManyAssociationWithPrimaryKeyError < ActiveRecordError #:nodoc:
69
+ def initialize(reflection)
70
+ super("Primary key is not allowed in a has_and_belongs_to_many join table (#{reflection.options[:join_table]}).")
71
+ end
72
+ end
73
+
74
+ class HasAndBelongsToManyAssociationForeignKeyNeeded < ActiveRecordError #:nodoc:
75
+ def initialize(reflection)
76
+ super("Cannot create self referential has_and_belongs_to_many association on '#{reflection.class_name rescue nil}##{reflection.name rescue nil}'. :association_foreign_key cannot be the same as the :foreign_key.")
77
+ end
78
+ end
79
+
80
+ class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
81
+ def initialize(reflection)
82
+ super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
83
+ end
84
+ end
85
+
86
+ class ReadOnlyAssociation < ActiveRecordError #:nodoc:
87
+ def initialize(reflection)
88
+ super("Can not add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
89
+ end
90
+ end
91
+
92
+ # This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations
93
+ # (has_many, has_one) when there is at least 1 child associated instance.
94
+ # ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
95
+ class DeleteRestrictionError < ActiveRecordError #:nodoc:
96
+ def initialize(reflection)
97
+ super("Cannot delete record because of dependent #{reflection.name}")
98
+ end
99
+ end
100
+
101
+ # See ActiveRecord::Associations::ClassMethods for documentation.
7
102
  module Associations # :nodoc:
8
- def self.append_features(base)
9
- super
10
- base.extend(ClassMethods)
103
+ extend ActiveSupport::Concern
104
+
105
+ # These classes will be loaded when associations are created.
106
+ # So there is no need to eager load them.
107
+ autoload :AssociationCollection, 'active_record/associations/association_collection'
108
+ autoload :AssociationProxy, 'active_record/associations/association_proxy'
109
+ autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
110
+ autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
111
+ autoload :HasAndBelongsToManyAssociation, 'active_record/associations/has_and_belongs_to_many_association'
112
+ autoload :HasManyAssociation, 'active_record/associations/has_many_association'
113
+ autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association'
114
+ autoload :HasOneAssociation, 'active_record/associations/has_one_association'
115
+ autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
116
+
117
+ # Clears out the association cache.
118
+ def clear_association_cache #:nodoc:
119
+ self.class.reflect_on_all_associations.to_a.each do |assoc|
120
+ instance_variable_set "@#{assoc.name}", nil
121
+ end unless self.new_record?
11
122
  end
12
123
 
13
- # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
14
- # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
15
- # specialized according to the collection or association symbol and the options hash. It works much the same was as Ruby's own attr*
16
- # methods. Example:
124
+ private
125
+ # Returns the specified association instance if it responds to :loaded?, nil otherwise.
126
+ def association_instance_get(name)
127
+ ivar = "@#{name}"
128
+ if instance_variable_defined?(ivar)
129
+ association = instance_variable_get(ivar)
130
+ association if association.respond_to?(:loaded?)
131
+ end
132
+ end
133
+
134
+ # Set the specified association instance.
135
+ def association_instance_set(name, association)
136
+ instance_variable_set("@#{name}", association)
137
+ end
138
+
139
+ # Associations are a set of macro-like class methods for tying objects together through
140
+ # foreign keys. They express relationships like "Project has one Project Manager"
141
+ # or "Project belongs to a Portfolio". Each macro adds a number of methods to the
142
+ # class which are specialized according to the collection or association symbol and the
143
+ # options hash. It works much the same way as Ruby's own <tt>attr*</tt>
144
+ # methods.
17
145
  #
18
146
  # class Project < ActiveRecord::Base
19
147
  # belongs_to :portfolio
20
- # has_one :project_manager
148
+ # has_one :project_manager
21
149
  # has_many :milestones
22
150
  # has_and_belongs_to_many :categories
23
151
  # end
24
152
  #
25
- # The project class now has the following methods to ease the traversel and manipulation of its relationships:
26
- # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?, Project#portfolio?(portfolio)</tt>
27
- # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manger.nil?,</tt>
28
- # <tt>Project#project_manager?(project_manager), Project#build_project_manager, Project#create_project_manager</tt>
153
+ # The project class now has the following methods (and more) to ease the traversal and
154
+ # manipulation of its relationships:
155
+ # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
156
+ # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
29
157
  # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
30
- # <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find_all(conditions),</tt>
158
+ # <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find(:all, options),</tt>
31
159
  # <tt>Project#milestones.build, Project#milestones.create</tt>
32
160
  # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
33
161
  # <tt>Project#categories.delete(category1)</tt>
34
162
  #
35
- # == Example
163
+ # === A word of warning
36
164
  #
37
- # link:../examples/associations.png
165
+ # Don't create associations that have the same name as instance methods of
166
+ # <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to
167
+ # its model, it will override the inherited method and break things.
168
+ # For instance, +attributes+ and +connection+ would be bad choices for association names.
38
169
  #
39
- # == Is it belongs_to or has_one?
170
+ # == Auto-generated methods
40
171
  #
41
- # Both express a 1-1 relationship, the difference is mostly where to place the foreign key, which goes on the table for the class
42
- # saying belongs_to. Example:
172
+ # === Singular associations (one-to-one)
173
+ # | | belongs_to |
174
+ # generated methods | belongs_to | :polymorphic | has_one
175
+ # ----------------------------------+------------+--------------+---------
176
+ # other | X | X | X
177
+ # other=(other) | X | X | X
178
+ # build_other(attributes={}) | X | | X
179
+ # create_other(attributes={}) | X | | X
180
+ # other.create!(attributes={}) | | | X
43
181
  #
44
- # class Post < ActiveRecord::Base
45
- # has_one :author
182
+ # ===Collection associations (one-to-many / many-to-many)
183
+ # | | | has_many
184
+ # generated methods | habtm | has_many | :through
185
+ # ----------------------------------+-------+----------+----------
186
+ # others | X | X | X
187
+ # others=(other,other,...) | X | X | X
188
+ # other_ids | X | X | X
189
+ # other_ids=(id,id,...) | X | X | X
190
+ # others<< | X | X | X
191
+ # others.push | X | X | X
192
+ # others.concat | X | X | X
193
+ # others.build(attributes={}) | X | X | X
194
+ # others.create(attributes={}) | X | X | X
195
+ # others.create!(attributes={}) | X | X | X
196
+ # others.size | X | X | X
197
+ # others.length | X | X | X
198
+ # others.count | X | X | X
199
+ # others.sum(args*,&block) | X | X | X
200
+ # others.empty? | X | X | X
201
+ # others.clear | X | X | X
202
+ # others.delete(other,other,...) | X | X | X
203
+ # others.delete_all | X | X |
204
+ # others.destroy_all | X | X | X
205
+ # others.find(*args) | X | X | X
206
+ # others.find_first | X | |
207
+ # others.exists? | X | X | X
208
+ # others.uniq | X | X | X
209
+ # others.reset | X | X | X
210
+ #
211
+ # == Cardinality and associations
212
+ #
213
+ # Active Record associations can be used to describe one-to-one, one-to-many and many-to-many
214
+ # relationships between models. Each model uses an association to describe its role in
215
+ # the relation. The +belongs_to+ association is always used in the model that has
216
+ # the foreign key.
217
+ #
218
+ # === One-to-one
219
+ #
220
+ # Use +has_one+ in the base, and +belongs_to+ in the associated model.
221
+ #
222
+ # class Employee < ActiveRecord::Base
223
+ # has_one :office
224
+ # end
225
+ # class Office < ActiveRecord::Base
226
+ # belongs_to :employee # foreign key - employee_id
46
227
  # end
47
228
  #
48
- # class Author < ActiveRecord::Base
49
- # belongs_to :post
229
+ # === One-to-many
230
+ #
231
+ # Use +has_many+ in the base, and +belongs_to+ in the associated model.
232
+ #
233
+ # class Manager < ActiveRecord::Base
234
+ # has_many :employees
235
+ # end
236
+ # class Employee < ActiveRecord::Base
237
+ # belongs_to :manager # foreign key - manager_id
238
+ # end
239
+ #
240
+ # === Many-to-many
241
+ #
242
+ # There are two ways to build a many-to-many relationship.
243
+ #
244
+ # The first way uses a +has_many+ association with the <tt>:through</tt> option and a join model, so
245
+ # there are two stages of associations.
246
+ #
247
+ # class Assignment < ActiveRecord::Base
248
+ # belongs_to :programmer # foreign key - programmer_id
249
+ # belongs_to :project # foreign key - project_id
250
+ # end
251
+ # class Programmer < ActiveRecord::Base
252
+ # has_many :assignments
253
+ # has_many :projects, :through => :assignments
254
+ # end
255
+ # class Project < ActiveRecord::Base
256
+ # has_many :assignments
257
+ # has_many :programmers, :through => :assignments
258
+ # end
259
+ #
260
+ # For the second way, use +has_and_belongs_to_many+ in both models. This requires a join table
261
+ # that has no corresponding model or primary key.
262
+ #
263
+ # class Programmer < ActiveRecord::Base
264
+ # has_and_belongs_to_many :projects # foreign keys in the join table
265
+ # end
266
+ # class Project < ActiveRecord::Base
267
+ # has_and_belongs_to_many :programmers # foreign keys in the join table
268
+ # end
269
+ #
270
+ # Choosing which way to build a many-to-many relationship is not always simple.
271
+ # If you need to work with the relationship model as its own entity,
272
+ # use <tt>has_many :through</tt>. Use +has_and_belongs_to_many+ when working with legacy schemas or when
273
+ # you never work directly with the relationship itself.
274
+ #
275
+ # == Is it a +belongs_to+ or +has_one+ association?
276
+ #
277
+ # Both express a 1-1 relationship. The difference is mostly where to place the foreign
278
+ # key, which goes on the table for the class declaring the +belongs_to+ relationship.
279
+ #
280
+ # class User < ActiveRecord::Base
281
+ # # I reference an account.
282
+ # belongs_to :account
283
+ # end
284
+ #
285
+ # class Account < ActiveRecord::Base
286
+ # # One user references me.
287
+ # has_one :user
50
288
  # end
51
289
  #
52
290
  # The tables for these classes could look something like:
53
291
  #
54
- # CREATE TABLE posts (
292
+ # CREATE TABLE users (
55
293
  # id int(11) NOT NULL auto_increment,
56
- # title varchar default NULL,
294
+ # account_id int(11) default NULL,
295
+ # name varchar default NULL,
57
296
  # PRIMARY KEY (id)
58
297
  # )
59
298
  #
60
- # CREATE TABLE authors (
299
+ # CREATE TABLE accounts (
61
300
  # id int(11) NOT NULL auto_increment,
62
- # post_id int(11) default NULL,
63
301
  # name varchar default NULL,
64
302
  # PRIMARY KEY (id)
65
303
  # )
66
304
  #
305
+ # == Unsaved objects and associations
306
+ #
307
+ # You can manipulate objects and associations before they are saved to the database, but
308
+ # there is some special behavior you should be aware of, mostly involving the saving of
309
+ # associated objects.
310
+ #
311
+ # You can set the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
312
+ # <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association. Setting it
313
+ # to +true+ will _always_ save the members, whereas setting it to +false+ will
314
+ # _never_ save the members. More details about :autosave option is available at
315
+ # autosave_association.rb .
316
+ #
317
+ # === One-to-one associations
318
+ #
319
+ # * Assigning an object to a +has_one+ association automatically saves that object and
320
+ # the object being replaced (if there is one), in order to update their primary
321
+ # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
322
+ # * If either of these saves fail (due to one of the objects being invalid) the assignment
323
+ # statement returns +false+ and the assignment is cancelled.
324
+ # * If you wish to assign an object to a +has_one+ association without saving it,
325
+ # use the <tt>association.build</tt> method (documented below).
326
+ # * Assigning an object to a +belongs_to+ association does not save the object, since
327
+ # the foreign key field belongs on the parent. It does not save the parent either.
328
+ #
329
+ # === Collections
330
+ #
331
+ # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically
332
+ # saves that object, except if the parent object (the owner of the collection) is not yet
333
+ # stored in the database.
334
+ # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar)
335
+ # fails, then <tt>push</tt> returns +false+.
336
+ # * You can add an object to a collection without automatically saving it by using the
337
+ # <tt>collection.build</tt> method (documented below).
338
+ # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically
339
+ # saved when the parent is saved.
340
+ #
341
+ # === Association callbacks
342
+ #
343
+ # Similar to the normal callbacks that hook into the life cycle of an Active Record object,
344
+ # you can also define callbacks that get triggered when you add an object to or remove an
345
+ # object from an association collection.
346
+ #
347
+ # class Project
348
+ # has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
349
+ #
350
+ # def evaluate_velocity(developer)
351
+ # ...
352
+ # end
353
+ # end
354
+ #
355
+ # It's possible to stack callbacks by passing them as an array. Example:
356
+ #
357
+ # class Project
358
+ # has_and_belongs_to_many :developers,
359
+ # :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
360
+ # end
361
+ #
362
+ # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
363
+ #
364
+ # Should any of the +before_add+ callbacks throw an exception, the object does not get
365
+ # added to the collection. Same with the +before_remove+ callbacks; if an exception is
366
+ # thrown the object doesn't get removed.
367
+ #
368
+ # === Association extensions
369
+ #
370
+ # The proxy objects that control the access to associations can be extended through anonymous
371
+ # modules. This is especially beneficial for adding new finders, creators, and other
372
+ # factory-type methods that are only used as part of this association.
373
+ #
374
+ # class Account < ActiveRecord::Base
375
+ # has_many :people do
376
+ # def find_or_create_by_name(name)
377
+ # first_name, last_name = name.split(" ", 2)
378
+ # find_or_create_by_first_name_and_last_name(first_name, last_name)
379
+ # end
380
+ # end
381
+ # end
382
+ #
383
+ # person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
384
+ # person.first_name # => "David"
385
+ # person.last_name # => "Heinemeier Hansson"
386
+ #
387
+ # If you need to share the same extensions between many associations, you can use a named
388
+ # extension module.
389
+ #
390
+ # module FindOrCreateByNameExtension
391
+ # def find_or_create_by_name(name)
392
+ # first_name, last_name = name.split(" ", 2)
393
+ # find_or_create_by_first_name_and_last_name(first_name, last_name)
394
+ # end
395
+ # end
396
+ #
397
+ # class Account < ActiveRecord::Base
398
+ # has_many :people, :extend => FindOrCreateByNameExtension
399
+ # end
400
+ #
401
+ # class Company < ActiveRecord::Base
402
+ # has_many :people, :extend => FindOrCreateByNameExtension
403
+ # end
404
+ #
405
+ # If you need to use multiple named extension modules, you can specify an array of modules
406
+ # with the <tt>:extend</tt> option.
407
+ # In the case of name conflicts between methods in the modules, methods in modules later
408
+ # in the array supercede those earlier in the array.
409
+ #
410
+ # class Account < ActiveRecord::Base
411
+ # has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
412
+ # end
413
+ #
414
+ # Some extensions can only be made to work with knowledge of the association proxy's internals.
415
+ # Extensions can access relevant state using accessors on the association proxy:
416
+ #
417
+ # * +proxy_owner+ - Returns the object the association is part of.
418
+ # * +proxy_reflection+ - Returns the reflection object that describes the association.
419
+ # * +proxy_target+ - Returns the associated object for +belongs_to+ and +has_one+, or
420
+ # the collection of associated objects for +has_many+ and +has_and_belongs_to_many+.
421
+ #
422
+ # === Association Join Models
423
+ #
424
+ # Has Many associations can be configured with the <tt>:through</tt> option to use an
425
+ # explicit join model to retrieve the data. This operates similarly to a
426
+ # +has_and_belongs_to_many+ association. The advantage is that you're able to add validations,
427
+ # callbacks, and extra attributes on the join model. Consider the following schema:
428
+ #
429
+ # class Author < ActiveRecord::Base
430
+ # has_many :authorships
431
+ # has_many :books, :through => :authorships
432
+ # end
433
+ #
434
+ # class Authorship < ActiveRecord::Base
435
+ # belongs_to :author
436
+ # belongs_to :book
437
+ # end
438
+ #
439
+ # @author = Author.find :first
440
+ # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
441
+ # @author.books # selects all books by using the Authorship join model
442
+ #
443
+ # You can also go through a +has_many+ association on the join model:
444
+ #
445
+ # class Firm < ActiveRecord::Base
446
+ # has_many :clients
447
+ # has_many :invoices, :through => :clients
448
+ # end
449
+ #
450
+ # class Client < ActiveRecord::Base
451
+ # belongs_to :firm
452
+ # has_many :invoices
453
+ # end
454
+ #
455
+ # class Invoice < ActiveRecord::Base
456
+ # belongs_to :client
457
+ # end
458
+ #
459
+ # @firm = Firm.find :first
460
+ # @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
461
+ # @firm.invoices # selects all invoices by going through the Client join model
462
+ #
463
+ # Similarly you can go through a +has_one+ association on the join model:
464
+ #
465
+ # class Group < ActiveRecord::Base
466
+ # has_many :users
467
+ # has_many :avatars, :through => :users
468
+ # end
469
+ #
470
+ # class User < ActiveRecord::Base
471
+ # belongs_to :group
472
+ # has_one :avatar
473
+ # end
474
+ #
475
+ # class Avatar < ActiveRecord::Base
476
+ # belongs_to :user
477
+ # end
478
+ #
479
+ # @group = Group.first
480
+ # @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group
481
+ # @group.avatars # selects all avatars by going through the User join model.
482
+ #
483
+ # An important caveat with going through +has_one+ or +has_many+ associations on the
484
+ # join model is that these associations are *read-only*. For example, the following
485
+ # would not work following the previous example:
486
+ #
487
+ # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
488
+ # @group.avatars.delete(@group.avatars.last) # so would this
489
+ #
490
+ # === Polymorphic Associations
491
+ #
492
+ # Polymorphic associations on models are not restricted on what types of models they
493
+ # can be associated with. Rather, they specify an interface that a +has_many+ association
494
+ # must adhere to.
495
+ #
496
+ # class Asset < ActiveRecord::Base
497
+ # belongs_to :attachable, :polymorphic => true
498
+ # end
499
+ #
500
+ # class Post < ActiveRecord::Base
501
+ # has_many :assets, :as => :attachable # The :as option specifies the polymorphic interface to use.
502
+ # end
503
+ #
504
+ # @asset.attachable = @post
505
+ #
506
+ # This works by using a type column in addition to a foreign key to specify the associated
507
+ # record. In the Asset example, you'd need an +attachable_id+ integer column and an
508
+ # +attachable_type+ string column.
509
+ #
510
+ # Using polymorphic associations in combination with single table inheritance (STI) is
511
+ # a little tricky. In order for the associations to work as expected, ensure that you
512
+ # store the base model for the STI models in the type column of the polymorphic
513
+ # association. To continue with the asset example above, suppose there are guest posts
514
+ # and member posts that use the posts table for STI. In this case, there must be a +type+
515
+ # column in the posts table.
516
+ #
517
+ # class Asset < ActiveRecord::Base
518
+ # belongs_to :attachable, :polymorphic => true
519
+ #
520
+ # def attachable_type=(sType)
521
+ # super(sType.to_s.classify.constantize.base_class.to_s)
522
+ # end
523
+ # end
524
+ #
525
+ # class Post < ActiveRecord::Base
526
+ # # because we store "Post" in attachable_type now :dependent => :destroy will work
527
+ # has_many :assets, :as => :attachable, :dependent => :destroy
528
+ # end
529
+ #
530
+ # class GuestPost < Post
531
+ # end
532
+ #
533
+ # class MemberPost < Post
534
+ # end
535
+ #
67
536
  # == Caching
68
537
  #
69
- # All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
70
- # instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without
71
- # worrying too much about performance at the first go. Example:
538
+ # All of the methods are built on a simple caching principle that will keep the result
539
+ # of the last query around unless specifically instructed not to. The cache is even
540
+ # shared across methods to make it even cheaper to use the macro-added methods without
541
+ # worrying too much about performance at the first go.
72
542
  #
73
543
  # project.milestones # fetches milestones from the database
74
544
  # project.milestones.size # uses the milestone cache
@@ -76,6 +546,167 @@ module ActiveRecord
76
546
  # project.milestones(true).size # fetches milestones from the database
77
547
  # project.milestones # uses the milestone cache
78
548
  #
549
+ # == Eager loading of associations
550
+ #
551
+ # Eager loading is a way to find objects of a certain class and a number of named associations.
552
+ # This is one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100
553
+ # posts that each need to display their author triggers 101 database queries. Through the
554
+ # use of eager loading, the 101 queries can be reduced to 2.
555
+ #
556
+ # class Post < ActiveRecord::Base
557
+ # belongs_to :author
558
+ # has_many :comments
559
+ # end
560
+ #
561
+ # Consider the following loop using the class above:
562
+ #
563
+ # for post in Post.all
564
+ # puts "Post: " + post.title
565
+ # puts "Written by: " + post.author.name
566
+ # puts "Last comment on: " + post.comments.first.created_on
567
+ # end
568
+ #
569
+ # To iterate over these one hundred posts, we'll generate 201 database queries. Let's
570
+ # first just optimize it for retrieving the author:
571
+ #
572
+ # for post in Post.find(:all, :include => :author)
573
+ #
574
+ # This references the name of the +belongs_to+ association that also used the <tt>:author</tt>
575
+ # symbol. After loading the posts, find will collect the +author_id+ from each one and load
576
+ # all the referenced authors with one query. Doing so will cut down the number of queries
577
+ # from 201 to 102.
578
+ #
579
+ # We can improve upon the situation further by referencing both associations in the finder with:
580
+ #
581
+ # for post in Post.find(:all, :include => [ :author, :comments ])
582
+ #
583
+ # This will load all comments with a single query. This reduces the total number of queries
584
+ # to 3. More generally the number of queries will be 1 plus the number of associations
585
+ # named (except if some of the associations are polymorphic +belongs_to+ - see below).
586
+ #
587
+ # To include a deep hierarchy of associations, use a hash:
588
+ #
589
+ # for post in Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ])
590
+ #
591
+ # That'll grab not only all the comments but all their authors and gravatar pictures.
592
+ # You can mix and match symbols, arrays and hashes in any combination to describe the
593
+ # associations you want to load.
594
+ #
595
+ # All of this power shouldn't fool you into thinking that you can pull out huge amounts
596
+ # of data with no performance penalty just because you've reduced the number of queries.
597
+ # The database still needs to send all the data to Active Record and it still needs to
598
+ # be processed. So it's no catch-all for performance problems, but it's a great way to
599
+ # cut down on the number of queries in a situation as the one described above.
600
+ #
601
+ # Since only one table is loaded at a time, conditions or orders cannot reference tables
602
+ # other than the main one. If this is the case Active Record falls back to the previously
603
+ # used LEFT OUTER JOIN based strategy. For example
604
+ #
605
+ # Post.find(:all, :include => [ :author, :comments ], :conditions => ['comments.approved = ?', true])
606
+ #
607
+ # This will result in a single SQL query with joins along the lines of:
608
+ # <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
609
+ # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions
610
+ # like this can have unintended consequences.
611
+ # In the above example posts with no approved comments are not returned at all, because
612
+ # the conditions apply to the SQL statement as a whole and not just to the association.
613
+ # You must disambiguate column references for this fallback to happen, for example
614
+ # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
615
+ #
616
+ # If you do want eager load only some members of an association it is usually more natural
617
+ # to <tt>:include</tt> an association which has conditions defined on it:
618
+ #
619
+ # class Post < ActiveRecord::Base
620
+ # has_many :approved_comments, :class_name => 'Comment', :conditions => ['approved = ?', true]
621
+ # end
622
+ #
623
+ # Post.find(:all, :include => :approved_comments)
624
+ #
625
+ # This will load posts and eager load the +approved_comments+ association, which contains
626
+ # only those comments that have been approved.
627
+ #
628
+ # If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored,
629
+ # returning all the associated objects:
630
+ #
631
+ # class Picture < ActiveRecord::Base
632
+ # has_many :most_recent_comments, :class_name => 'Comment', :order => 'id DESC', :limit => 10
633
+ # end
634
+ #
635
+ # Picture.find(:first, :include => :most_recent_comments).most_recent_comments # => returns all associated comments.
636
+ #
637
+ # When eager loaded, conditions are interpolated in the context of the model class, not
638
+ # the model instance. Conditions are lazily interpolated before the actual model exists.
639
+ #
640
+ # Eager loading is supported with polymorphic associations.
641
+ #
642
+ # class Address < ActiveRecord::Base
643
+ # belongs_to :addressable, :polymorphic => true
644
+ # end
645
+ #
646
+ # A call that tries to eager load the addressable model
647
+ #
648
+ # Address.find(:all, :include => :addressable)
649
+ #
650
+ # This will execute one query to load the addresses and load the addressables with one
651
+ # query per addressable type.
652
+ # For example if all the addressables are either of class Person or Company then a total
653
+ # of 3 queries will be executed. The list of addressable types to load is determined on
654
+ # the back of the addresses loaded. This is not supported if Active Record has to fallback
655
+ # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError.
656
+ # The reason is that the parent model's type is a column value so its corresponding table
657
+ # name cannot be put in the +FROM+/+JOIN+ clauses of that query.
658
+ #
659
+ # == Table Aliasing
660
+ #
661
+ # Active Record uses table aliasing in the case that a table is referenced multiple times
662
+ # in a join. If a table is referenced only once, the standard table name is used. The
663
+ # second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>.
664
+ # Indexes are appended for any more successive uses of the table name.
665
+ #
666
+ # Post.find :all, :joins => :comments
667
+ # # => SELECT ... FROM posts INNER JOIN comments ON ...
668
+ # Post.find :all, :joins => :special_comments # STI
669
+ # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
670
+ # Post.find :all, :joins => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name
671
+ # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
672
+ #
673
+ # Acts as tree example:
674
+ #
675
+ # TreeMixin.find :all, :joins => :children
676
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
677
+ # TreeMixin.find :all, :joins => {:children => :parent}
678
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
679
+ # INNER JOIN parents_mixins ...
680
+ # TreeMixin.find :all, :joins => {:children => {:parent => :children}}
681
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
682
+ # INNER JOIN parents_mixins ...
683
+ # INNER JOIN mixins childrens_mixins_2
684
+ #
685
+ # Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
686
+ #
687
+ # Post.find :all, :joins => :categories
688
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
689
+ # Post.find :all, :joins => {:categories => :posts}
690
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
691
+ # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
692
+ # Post.find :all, :joins => {:categories => {:posts => :categories}}
693
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
694
+ # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
695
+ # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
696
+ #
697
+ # If you wish to specify your own custom joins using a <tt>:joins</tt> option, those table
698
+ # names will take precedence over the eager associations:
699
+ #
700
+ # Post.find :all, :joins => :comments, :joins => "inner join comments ..."
701
+ # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
702
+ # Post.find :all, :joins => [:comments, :special_comments], :joins => "inner join comments ..."
703
+ # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
704
+ # INNER JOIN comments special_comments_posts ...
705
+ # INNER JOIN comments ...
706
+ #
707
+ # Table aliases are automatically truncated according to the maximum length of table identifiers
708
+ # according to the specific database.
709
+ #
79
710
  # == Modules
80
711
  #
81
712
  # By default, associations will look for objects within the current module scope. Consider:
@@ -86,12 +717,14 @@ module ActiveRecord
86
717
  # has_many :clients
87
718
  # end
88
719
  #
89
- # class Company < ActiveRecord::Base; end
720
+ # class Client < ActiveRecord::Base; end
90
721
  # end
91
722
  # end
92
723
  #
93
- # When Firm#clients is called, it'll in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate
94
- # with a class in another module scope this can be done by specifying the complete class name, such as:
724
+ # When <tt>Firm#clients</tt> is called, it will in turn call
725
+ # <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>.
726
+ # If you want to associate with a class in another module scope, this can be done by
727
+ # specifying the complete class name.
95
728
  #
96
729
  # module MyApplication
97
730
  # module Business
@@ -105,325 +738,699 @@ module ActiveRecord
105
738
  # end
106
739
  # end
107
740
  #
108
- # == Type safety with ActiveRecord::AssociationTypeMismatch
741
+ # == Bi-directional associations
742
+ #
743
+ # When you specify an association there is usually an association on the associated model
744
+ # that specifies the same relationship in reverse. For example, with the following models:
745
+ #
746
+ # class Dungeon < ActiveRecord::Base
747
+ # has_many :traps
748
+ # has_one :evil_wizard
749
+ # end
750
+ #
751
+ # class Trap < ActiveRecord::Base
752
+ # belongs_to :dungeon
753
+ # end
754
+ #
755
+ # class EvilWizard < ActiveRecord::Base
756
+ # belongs_to :dungeon
757
+ # end
758
+ #
759
+ # The +traps+ association on +Dungeon+ and the the +dungeon+ association on +Trap+ are
760
+ # the inverse of each other and the inverse of the +dungeon+ association on +EvilWizard+
761
+ # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
762
+ # Active Record doesn't know anything about these inverse relationships and so no object
763
+ # loading optimisation is possible. For example:
764
+ #
765
+ # d = Dungeon.first
766
+ # t = d.traps.first
767
+ # d.level == t.dungeon.level # => true
768
+ # d.level = 10
769
+ # d.level == t.dungeon.level # => false
770
+ #
771
+ # The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to
772
+ # the same object data from the database, but are actually different in-memory copies
773
+ # of that data. Specifying the <tt>:inverse_of</tt> option on associations lets you tell
774
+ # Active Record about inverse relationships and it will optimise object loading. For
775
+ # example, if we changed our model definitions to:
776
+ #
777
+ # class Dungeon < ActiveRecord::Base
778
+ # has_many :traps, :inverse_of => :dungeon
779
+ # has_one :evil_wizard, :inverse_of => :dungeon
780
+ # end
109
781
  #
110
- # If you attempt to assign an object to an association that doesn't match the inferred or specified <tt>:class_name</tt>, you'll
111
- # get a ActiveRecord::AssociationTypeMismatch.
782
+ # class Trap < ActiveRecord::Base
783
+ # belongs_to :dungeon, :inverse_of => :traps
784
+ # end
785
+ #
786
+ # class EvilWizard < ActiveRecord::Base
787
+ # belongs_to :dungeon, :inverse_of => :evil_wizard
788
+ # end
789
+ #
790
+ # Then, from our code snippet above, +d+ and <tt>t.dungeon</tt> are actually the same
791
+ # in-memory instance and our final <tt>d.level == t.dungeon.level</tt> will return +true+.
792
+ #
793
+ # There are limitations to <tt>:inverse_of</tt> support:
794
+ #
795
+ # * does not work with <tt>:through</tt> associations.
796
+ # * does not work with <tt>:polymorphic</tt> associations.
797
+ # * for +belongs_to+ associations +has_many+ inverse associations are ignored.
798
+ #
799
+ # == Type safety with <tt>ActiveRecord::AssociationTypeMismatch</tt>
800
+ #
801
+ # If you attempt to assign an object to an association that doesn't match the inferred
802
+ # or specified <tt>:class_name</tt>, you'll get an <tt>ActiveRecord::AssociationTypeMismatch</tt>.
112
803
  #
113
804
  # == Options
114
805
  #
115
- # All of the association macros can be specialized through options which makes more complex cases than the simple and guessable ones
116
- # possible.
806
+ # All of the association macros can be specialized through options. This makes cases
807
+ # more complex than the simple and guessable ones possible.
117
808
  module ClassMethods
118
- # Adds the following methods for retrival and query of collections of associated objects.
119
- # +collection+ is replaced with the symbol passed as the first argument, so
120
- # <tt>has_many :clients</tt> would add among others <tt>has_clients?</tt>.
121
- # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
122
- # An empty array is returned if none is found.
123
- # * <tt>collection<<(object)</tt> - adds the object to the collection (by setting the foreign key on it) and saves it.
124
- # * <tt>collection.delete(object)</tt> - removes the association by setting the foreign key to null on the associated object.
125
- # * <tt>!collection.empty?</tt> - returns true if there's any associated objects.
126
- # * <tt>collection.size</tt> - returns the number of associated objects.
127
- # * <tt>collection.find(id)</tt> - finds an associated object responding to the +id+ and that
128
- # meets the condition that it has to be associated with this object.
129
- # * <tt>collection.find_all(conditions = nil, orderings = nil, limit = nil, joins = nil)</tt> - finds all associated objects responding
130
- # criterias mentioned (like in the standard find_all) and that meets the condition that it has to be associated with this object.
131
- # * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
132
- # with +attributes+ and linked to this object through a foreign key but has not yet been saved.
133
- # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
134
- # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
809
+ # Specifies a one-to-many association. The following methods for retrieval and query of
810
+ # collections of associated objects will be added:
811
+ #
812
+ # [collection(force_reload = false)]
813
+ # Returns an array of all the associated objects.
814
+ # An empty array is returned if none are found.
815
+ # [collection<<(object, ...)]
816
+ # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
817
+ # Note that this operation instantly fires update sql without waiting for the save or update call on the
818
+ # parent object.
819
+ # [collection.delete(object, ...)]
820
+ # Removes one or more objects from the collection by setting their foreign keys to +NULL+.
821
+ # Objects will be in addition destroyed if they're associated with <tt>:dependent => :destroy</tt>,
822
+ # and deleted if they're associated with <tt>:dependent => :delete_all</tt>.
823
+ # [collection=objects]
824
+ # Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt>
825
+ # option is true callbacks in the join models are triggered except destroy callbacks, since deletion is
826
+ # direct.
827
+ # [collection_singular_ids]
828
+ # Returns an array of the associated objects' ids
829
+ # [collection_singular_ids=ids]
830
+ # Replace the collection with the objects identified by the primary keys in +ids+. This
831
+ # method loads the models and calls <tt>collection=</tt>. See above.
832
+ # [collection.clear]
833
+ # Removes every object from the collection. This destroys the associated objects if they
834
+ # are associated with <tt>:dependent => :destroy</tt>, deletes them directly from the
835
+ # database if <tt>:dependent => :delete_all</tt>, otherwise sets their foreign keys to +NULL+.
836
+ # If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models.
837
+ # Join models are directly deleted.
838
+ # [collection.empty?]
839
+ # Returns +true+ if there are no associated objects.
840
+ # [collection.size]
841
+ # Returns the number of associated objects.
842
+ # [collection.find(...)]
843
+ # Finds an associated object according to the same rules as ActiveRecord::Base.find.
844
+ # [collection.exists?(...)]
845
+ # Checks whether an associated object with the given conditions exists.
846
+ # Uses the same rules as ActiveRecord::Base.exists?.
847
+ # [collection.build(attributes = {}, ...)]
848
+ # Returns one or more new objects of the collection type that have been instantiated
849
+ # with +attributes+ and linked to this object through a foreign key, but have not yet
850
+ # been saved.
851
+ # [collection.create(attributes = {})]
852
+ # Returns a new object of the collection type that has been instantiated
853
+ # with +attributes+, linked to this object through a foreign key, and that has already
854
+ # been saved (if it passed the validation). *Note*: This only works if the base model
855
+ # already exists in the DB, not if it is a new (unsaved) record!
856
+ #
857
+ # (*Note*: +collection+ is replaced with the symbol passed as the first argument, so
858
+ # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.)
859
+ #
860
+ # === Example
135
861
  #
136
862
  # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
137
- # * <tt>Firm#clients</tt> (similar to <tt>Clients.find_all "firm_id = #{id}"</tt>)
863
+ # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => ["firm_id = ?", id]</tt>)
138
864
  # * <tt>Firm#clients<<</tt>
139
865
  # * <tt>Firm#clients.delete</tt>
140
- # * <tt>!Firm#clients.empty?</tt> (similar to <tt>firm.clients.length > 0</tt>)
866
+ # * <tt>Firm#clients=</tt>
867
+ # * <tt>Firm#client_ids</tt>
868
+ # * <tt>Firm#client_ids=</tt>
869
+ # * <tt>Firm#clients.clear</tt>
870
+ # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
141
871
  # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
142
- # * <tt>Firm#clients.find</tt> (similar to <tt>Client.find_on_conditions(id, "firm_id = #{id}")</tt>)
143
- # * <tt>Firm#clients.find_all</tt> (similar to <tt>Client.find_all "firm_id = #{id}"</tt>)
872
+ # * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>)
873
+ # * <tt>Firm#clients.exists?(:name => 'ACME')</tt> (similar to <tt>Client.exists?(:name => 'ACME', :firm_id => firm.id)</tt>)
144
874
  # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
145
- # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("client_id" => id); c.save; c</tt>)
146
- # The declaration can also include an options hash to specialize the generated methods.
147
- #
148
- # Options are:
149
- # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be infered
150
- # from the association name. So <tt>has_many :products</tt> will by default be linked to the +Product+ class, but
151
- # if the real class name is +SpecialProduct+, you'll have to specify it with this option.
152
- # * <tt>:conditions</tt> - specify the conditions that the associated objects must meet in order to be included as a "WHERE"
153
- # sql fragment, such as "price > 5 AND name LIKE 'B%'".
154
- # * <tt>:order</tt> - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment,
155
- # such as "last_name, first_name DESC"
156
- # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
157
- # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_many association will use "person_id"
158
- # as the default foreign_key.
159
- # * <tt>:dependent</tt> - if set to true all the associated object are destroyed alongside this object
160
- # * <tt>:exclusively_dependent</tt> - if set to true all the associated object are deleted in one SQL statement without having their
161
- # before_destroy callback run. This should only be used on associations that depend solely on this class and don't need to do any
162
- # clean-up in before_destroy. The upside is that it's much faster, especially if there's a counter_cache involved.
163
- # * <tt>:finder_sql</tt> - specify a complete SQL statement to fetch the association. This is a good way to go for complex
164
- # associations that depends on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
875
+ # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
876
+ # The declaration can also include an options hash to specialize the behavior of the association.
877
+ #
878
+ # === Supported options
879
+ # [:class_name]
880
+ # Specify the class name of the association. Use it only if that name can't be inferred
881
+ # from the association name. So <tt>has_many :products</tt> will by default be linked
882
+ # to the Product class, but if the real class name is SpecialProduct, you'll have to
883
+ # specify it with this option.
884
+ # [:conditions]
885
+ # Specify the conditions that the associated objects must meet in order to be included as a +WHERE+
886
+ # SQL fragment, such as <tt>price > 5 AND name LIKE 'B%'</tt>. Record creations from
887
+ # the association are scoped if a hash is used.
888
+ # <tt>has_many :posts, :conditions => {:published => true}</tt> will create published
889
+ # posts with <tt>@blog.posts.create</tt> or <tt>@blog.posts.build</tt>.
890
+ # [:order]
891
+ # Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
892
+ # such as <tt>last_name, first_name DESC</tt>.
893
+ # [:foreign_key]
894
+ # Specify the foreign key used for the association. By default this is guessed to be the name
895
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+
896
+ # association will use "person_id" as the default <tt>:foreign_key</tt>.
897
+ # [:primary_key]
898
+ # Specify the method that returns the primary key used for the association. By default this is +id+.
899
+ # [:dependent]
900
+ # If set to <tt>:destroy</tt> all the associated objects are destroyed
901
+ # alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated
902
+ # objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated
903
+ # objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. If set to
904
+ # <tt>:restrict</tt> this object cannot be deleted if it has any associated object.
905
+ #
906
+ # *Warning:* This option is ignored when used with <tt>:through</tt> option.
907
+ #
908
+ # [:finder_sql]
909
+ # Specify a complete SQL statement to fetch the association. This is a good way to go for complex
910
+ # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+
911
+ # is _not_ added.
912
+ # [:counter_sql]
913
+ # Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
914
+ # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by
915
+ # replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
916
+ # [:extend]
917
+ # Specify a named module for extending the proxy. See "Association extensions".
918
+ # [:include]
919
+ # Specify second-order associations that should be eager loaded when the collection is loaded.
920
+ # [:group]
921
+ # An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
922
+ # [:having]
923
+ # Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt>
924
+ # returns. Uses the <tt>HAVING</tt> SQL-clause.
925
+ # [:limit]
926
+ # An integer determining the limit on the number of rows that should be returned.
927
+ # [:offset]
928
+ # An integer determining the offset from where the rows should be fetched. So at 5,
929
+ # it would skip the first 4 rows.
930
+ # [:select]
931
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if
932
+ # you, for example, want to do a join but not include the joined columns. Do not forget
933
+ # to include the primary and foreign keys, otherwise it will raise an error.
934
+ # [:as]
935
+ # Specifies a polymorphic interface (See <tt>belongs_to</tt>).
936
+ # [:through]
937
+ # Specifies a join model through which to perform the query. Options for <tt>:class_name</tt>
938
+ # and <tt>:foreign_key</tt> are ignored, as the association uses the source reflection. You
939
+ # can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>, <tt>has_one</tt>
940
+ # or <tt>has_many</tt> association on the join model. The collection of join models
941
+ # can be managed via the collection API. For example, new join models are created for
942
+ # newly associated objects, and if some are gone their rows are deleted (directly,
943
+ # no destroy callbacks are triggered).
944
+ # [:source]
945
+ # Specifies the source association name used by <tt>has_many :through</tt> queries.
946
+ # Only use it if the name cannot be inferred from the association.
947
+ # <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
948
+ # <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given.
949
+ # [:source_type]
950
+ # Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
951
+ # association is a polymorphic +belongs_to+.
952
+ # [:uniq]
953
+ # If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
954
+ # [:readonly]
955
+ # If true, all the associated objects are readonly through the association.
956
+ # [:validate]
957
+ # If +false+, don't validate the associated objects when saving the parent object. true by default.
958
+ # [:autosave]
959
+ # If true, always save the associated objects or destroy them if marked for destruction,
960
+ # when saving the parent object. If false, never save or destroy the associated objects.
961
+ # By default, only save associated objects that are new records.
962
+ # [:inverse_of]
963
+ # Specifies the name of the <tt>belongs_to</tt> association on the associated object
964
+ # that is the inverse of this <tt>has_many</tt> association. Does not work in combination
965
+ # with <tt>:through</tt> or <tt>:as</tt> options.
966
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
165
967
  #
166
968
  # Option examples:
167
969
  # has_many :comments, :order => "posted_on"
970
+ # has_many :comments, :include => :author
168
971
  # has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"
169
- # has_many :tracks, :order => "position", :dependent => true
972
+ # has_many :tracks, :order => "position", :dependent => :destroy
973
+ # has_many :comments, :dependent => :nullify
974
+ # has_many :tags, :as => :taggable
975
+ # has_many :reports, :readonly => true
976
+ # has_many :subscribers, :through => :subscriptions, :source => :user
170
977
  # has_many :subscribers, :class_name => "Person", :finder_sql =>
171
978
  # 'SELECT DISTINCT people.* ' +
172
979
  # 'FROM people p, post_subscriptions ps ' +
173
980
  # 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
174
981
  # 'ORDER BY p.first_name'
175
- def has_many(association_id, options = {})
176
- validate_options([ :foreign_key, :class_name, :exclusively_dependent, :dependent, :conditions, :order, :finder_sql ], options.keys)
177
- association_name, association_class_name, association_class_primary_key_name =
178
- associate_identification(association_id, options[:class_name], options[:foreign_key])
179
-
180
- if options[:dependent]
181
- module_eval "before_destroy '#{association_name}.each { |o| o.destroy }'"
182
- end
982
+ def has_many(association_id, options = {}, &extension)
983
+ reflection = create_has_many_reflection(association_id, options, &extension)
984
+ configure_dependency_for_has_many(reflection)
985
+ add_association_callbacks(reflection.name, reflection.options)
183
986
 
184
- if options[:exclusively_dependent]
185
- module_eval "before_destroy Proc.new{ |record| #{association_class_name}.delete_all(%(#{association_class_primary_key_name} = '\#{record.id}')) }"
987
+ if options[:through]
988
+ collection_accessor_methods(reflection, HasManyThroughAssociation)
989
+ else
990
+ collection_accessor_methods(reflection, HasManyAssociation)
186
991
  end
187
-
188
- module_eval <<-"end_eval", __FILE__, __LINE__
189
- def #{association_name}(force_reload = false)
190
- if @#{association_name}.nil?
191
- @#{association_name} = HasManyAssociation.new(self, "#{association_name}", "#{association_class_name}",
192
- "#{association_class_primary_key_name}", #{options.inspect})
193
- end
194
- @#{association_name}.reload if force_reload
195
-
196
- return @#{association_name}
197
- end
198
- end_eval
199
-
200
- # deprecated api
201
- deprecated_collection_count_method(association_name)
202
- deprecated_add_association_relation(association_name)
203
- deprecated_remove_association_relation(association_name)
204
- deprecated_has_collection_method(association_name)
205
- deprecated_find_in_collection_method(association_name)
206
- deprecated_find_all_in_collection_method(association_name)
207
- deprecated_create_method(association_name)
208
- deprecated_build_method(association_name)
209
992
  end
210
993
 
211
- # Adds the following methods for retrival and query of a single associated object.
212
- # +association+ is replaced with the symbol passed as the first argument, so
213
- # <tt>has_one :manager</tt> would add among others <tt>has_manager?</tt>.
214
- # * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
215
- # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key,
994
+ # Specifies a one-to-one association with another class. This method should only be used
995
+ # if the other class contains the foreign key. If the current class contains the foreign key,
996
+ # then you should use +belongs_to+ instead. See also ActiveRecord::Associations::ClassMethods's overview
997
+ # on when to use has_one and when to use belongs_to.
998
+ #
999
+ # The following methods for retrieval and query of a single associated object will be added:
1000
+ #
1001
+ # [association(force_reload = false)]
1002
+ # Returns the associated object. +nil+ is returned if none is found.
1003
+ # [association=(associate)]
1004
+ # Assigns the associate object, extracts the primary key, sets it as the foreign key,
216
1005
  # and saves the associate object.
217
- # * <tt>association?(object, force_reload = false)</tt> - returns true if the +object+ is of the same type and has the
218
- # same id as the associated object.
219
- # * <tt>!association.nil?</tt> - returns true if there's an associated object.
220
- # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
221
- # with +attributes+ and linked to this object through a foreign key but has not yet been saved.
222
- # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
223
- # with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
224
- #
225
- # Example: An Account class declares <tt>has_one :beneficiary</tt>, which will add:
226
- # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find_first "account_id = #{id}"</tt>)
1006
+ # [build_association(attributes = {})]
1007
+ # Returns a new object of the associated type that has been instantiated
1008
+ # with +attributes+ and linked to this object through a foreign key, but has not
1009
+ # yet been saved. <b>Note:</b> This ONLY works if an association already exists.
1010
+ # It will NOT work if the association is +nil+.
1011
+ # [create_association(attributes = {})]
1012
+ # Returns a new object of the associated type that has been instantiated
1013
+ # with +attributes+, linked to this object through a foreign key, and that
1014
+ # has already been saved (if it passed the validation).
1015
+ #
1016
+ # (+association+ is replaced with the symbol passed as the first argument, so
1017
+ # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.)
1018
+ #
1019
+ # === Example
1020
+ #
1021
+ # An Account class declares <tt>has_one :beneficiary</tt>, which will add:
1022
+ # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>)
227
1023
  # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
228
- # * <tt>Account#beneficiary?</tt> (similar to <tt>account.beneficiary == some_beneficiary</tt>)
229
- # * <tt>!Account#beneficiary.nil?</tt>
230
1024
  # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
231
1025
  # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
232
- # The declaration can also include an options hash to specialize the generated methods.
233
- #
1026
+ #
1027
+ # === Options
1028
+ #
1029
+ # The declaration can also include an options hash to specialize the behavior of the association.
1030
+ #
234
1031
  # Options are:
235
- # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be infered
236
- # from the association name. So <tt>has_one :manager</tt> will by default be linked to the +Manager+ class, but
237
- # if the real class name is +Person+, you'll have to specify it with this option.
238
- # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE"
239
- # sql fragment, such as "rank = 5".
240
- # * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as
241
- # an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
242
- # * <tt>:dependent</tt> - if set to true the associated object is destroyed alongside this object
243
- # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
244
- # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_one association will use "person_id"
245
- # as the default foreign_key.
1032
+ # [:class_name]
1033
+ # Specify the class name of the association. Use it only if that name can't be inferred
1034
+ # from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but
1035
+ # if the real class name is Person, you'll have to specify it with this option.
1036
+ # [:conditions]
1037
+ # Specify the conditions that the associated object must meet in order to be included as a +WHERE+
1038
+ # SQL fragment, such as <tt>rank = 5</tt>. Record creation from the association is scoped if a hash
1039
+ # is used. <tt>has_one :account, :conditions => {:enabled => true}</tt> will create
1040
+ # an enabled account with <tt>@company.create_account</tt> or <tt>@company.build_account</tt>.
1041
+ # [:order]
1042
+ # Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
1043
+ # such as <tt>last_name, first_name DESC</tt>.
1044
+ # [:dependent]
1045
+ # If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
1046
+ # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
1047
+ # If set to <tt>:nullify</tt>, the associated object's foreign key is set to +NULL+.
1048
+ # Also, association is assigned.
1049
+ # [:foreign_key]
1050
+ # Specify the foreign key used for the association. By default this is guessed to be the name
1051
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
1052
+ # will use "person_id" as the default <tt>:foreign_key</tt>.
1053
+ # [:primary_key]
1054
+ # Specify the method that returns the primary key used for the association. By default this is +id+.
1055
+ # [:include]
1056
+ # Specify second-order associations that should be eager loaded when this object is loaded.
1057
+ # [:as]
1058
+ # Specifies a polymorphic interface (See <tt>belongs_to</tt>).
1059
+ # [:select]
1060
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example,
1061
+ # you want to do a join but not include the joined columns. Do not forget to include the
1062
+ # primary and foreign keys, otherwise it will raise an error.
1063
+ # [:through]
1064
+ # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>
1065
+ # and <tt>:foreign_key</tt> are ignored, as the association uses the source reflection. You
1066
+ # can only use a <tt>:through</tt> query through a <tt>has_one</tt> or <tt>belongs_to</tt>
1067
+ # association on the join model.
1068
+ # [:source]
1069
+ # Specifies the source association name used by <tt>has_one :through</tt> queries.
1070
+ # Only use it if the name cannot be inferred from the association.
1071
+ # <tt>has_one :favorite, :through => :favorites</tt> will look for a
1072
+ # <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
1073
+ # [:source_type]
1074
+ # Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
1075
+ # association is a polymorphic +belongs_to+.
1076
+ # [:readonly]
1077
+ # If true, the associated object is readonly through the association.
1078
+ # [:validate]
1079
+ # If +false+, don't validate the associated object when saving the parent object. +false+ by default.
1080
+ # [:autosave]
1081
+ # If true, always save the associated object or destroy it if marked for destruction,
1082
+ # when saving the parent object. If false, never save or destroy the associated object.
1083
+ # By default, only save the associated object if it's a new record.
1084
+ # [:inverse_of]
1085
+ # Specifies the name of the <tt>belongs_to</tt> association on the associated object
1086
+ # that is the inverse of this <tt>has_one</tt> association. Does not work in combination
1087
+ # with <tt>:through</tt> or <tt>:as</tt> options.
1088
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
246
1089
  #
247
1090
  # Option examples:
248
- # has_one :credit_card, :dependent => true
1091
+ # has_one :credit_card, :dependent => :destroy # destroys the associated credit card
1092
+ # has_one :credit_card, :dependent => :nullify # updates the associated records foreign
1093
+ # # key value to NULL rather than destroying it
249
1094
  # has_one :last_comment, :class_name => "Comment", :order => "posted_on"
250
1095
  # has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
1096
+ # has_one :attachment, :as => :attachable
1097
+ # has_one :boss, :readonly => :true
1098
+ # has_one :club, :through => :membership
1099
+ # has_one :primary_address, :through => :addressables, :conditions => ["addressable.primary = ?", true], :source => :addressable
251
1100
  def has_one(association_id, options = {})
252
- options.merge!({ :remote => true })
253
- belongs_to(association_id, options)
254
-
255
- association_name, association_class_name, class_primary_key_name =
256
- associate_identification(association_id, options[:class_name], options[:foreign_key], false)
257
-
258
- has_one_writer_method(association_name, association_class_name, class_primary_key_name)
259
- build_method("build_", association_name, association_class_name, class_primary_key_name)
260
- create_method("create_", association_name, association_class_name, class_primary_key_name)
261
-
262
- module_eval "before_destroy '#{association_name}.destroy if has_#{association_name}?'" if options[:dependent]
1101
+ if options[:through]
1102
+ reflection = create_has_one_through_reflection(association_id, options)
1103
+ association_accessor_methods(reflection, ActiveRecord::Associations::HasOneThroughAssociation)
1104
+ else
1105
+ reflection = create_has_one_reflection(association_id, options)
1106
+ association_accessor_methods(reflection, HasOneAssociation)
1107
+ association_constructor_method(:build, reflection, HasOneAssociation)
1108
+ association_constructor_method(:create, reflection, HasOneAssociation)
1109
+ configure_dependency_for_has_one(reflection)
1110
+ end
263
1111
  end
264
1112
 
265
- # Adds the following methods for retrival and query for a single associated object that this object holds an id to.
266
- # +association+ is replaced with the symbol passed as the first argument, so
267
- # <tt>belongs_to :author</tt> would add among others <tt>has_author?</tt>.
268
- # * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
269
- # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, and sets it as the foreign key.
270
- # * <tt>association?(object, force_reload = false)</tt> - returns true if the +object+ is of the same type and has the
271
- # same id as the associated object.
272
- # * <tt>association.nil?</tt> - returns true if there's an associated object.
1113
+ # Specifies a one-to-one association with another class. This method should only be used
1114
+ # if this class contains the foreign key. If the other class contains the foreign key,
1115
+ # then you should use +has_one+ instead. See also ActiveRecord::Associations::ClassMethods's overview
1116
+ # on when to use +has_one+ and when to use +belongs_to+.
1117
+ #
1118
+ # Methods will be added for retrieval and query for a single associated object, for which
1119
+ # this object holds an id:
1120
+ #
1121
+ # [association(force_reload = false)]
1122
+ # Returns the associated object. +nil+ is returned if none is found.
1123
+ # [association=(associate)]
1124
+ # Assigns the associate object, extracts the primary key, and sets it as the foreign key.
1125
+ # [build_association(attributes = {})]
1126
+ # Returns a new object of the associated type that has been instantiated
1127
+ # with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
1128
+ # [create_association(attributes = {})]
1129
+ # Returns a new object of the associated type that has been instantiated
1130
+ # with +attributes+, linked to this object through a foreign key, and that
1131
+ # has already been saved (if it passed the validation).
273
1132
  #
274
- # Example: An Post class declares <tt>has_one :author</tt>, which will add:
1133
+ # (+association+ is replaced with the symbol passed as the first argument, so
1134
+ # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.)
1135
+ #
1136
+ # === Example
1137
+ #
1138
+ # A Post class declares <tt>belongs_to :author</tt>, which will add:
275
1139
  # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
276
1140
  # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
277
- # * <tt>Post#author?</tt> (similar to <tt>post.author == some_author</tt>)
278
- # * <tt>!Post#author.nil?</tt>
279
- # The declaration can also include an options hash to specialize the generated methods.
280
- #
281
- # Options are:
282
- # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be infered
283
- # from the association name. So <tt>has_one :author</tt> will by default be linked to the +Author+ class, but
284
- # if the real class name is +Person+, you'll have to specify it with this option.
285
- # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a "WHERE"
286
- # sql fragment, such as "authorized = 1".
287
- # * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as
288
- # an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
289
- # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
290
- # of the associated class in lower-case and "_id" suffixed. So a +Person+ class that makes a belongs_to association to a
291
- # +Boss+ class will use "boss_id" as the default foreign_key.
292
- # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through use of increment_counter
293
- # and decrement_counter. The counter cache is incremented when an object of this class is created and decremented when it's
294
- # destroyed. This requires that a column named "#{table_name}_count" (such as comments_count for a belonging Comment class)
295
- # is used on the associate class (such as a Post class).
1141
+ # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
1142
+ # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
1143
+ # The declaration can also include an options hash to specialize the behavior of the association.
1144
+ #
1145
+ # === Options
1146
+ #
1147
+ # [:class_name]
1148
+ # Specify the class name of the association. Use it only if that name can't be inferred
1149
+ # from the association name. So <tt>has_one :author</tt> will by default be linked to the Author class, but
1150
+ # if the real class name is Person, you'll have to specify it with this option.
1151
+ # [:conditions]
1152
+ # Specify the conditions that the associated object must meet in order to be included as a +WHERE+
1153
+ # SQL fragment, such as <tt>authorized = 1</tt>.
1154
+ # [:select]
1155
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed
1156
+ # if, for example, you want to do a join but not include the joined columns. Do not
1157
+ # forget to include the primary and foreign keys, otherwise it will raise an error.
1158
+ # [:foreign_key]
1159
+ # Specify the foreign key used for the association. By default this is guessed to be the name
1160
+ # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt>
1161
+ # association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly,
1162
+ # <tt>belongs_to :favorite_person, :class_name => "Person"</tt> will use a foreign key
1163
+ # of "favorite_person_id".
1164
+ # [:primary_key]
1165
+ # Specify the method that returns the primary key of associated object used for the association.
1166
+ # By default this is id.
1167
+ # [:dependent]
1168
+ # If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
1169
+ # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
1170
+ # This option should not be specified when <tt>belongs_to</tt> is used in conjunction with
1171
+ # a <tt>has_many</tt> relationship on another class because of the potential to leave
1172
+ # orphaned records behind.
1173
+ # [:counter_cache]
1174
+ # Caches the number of belonging objects on the associate class through the use of +increment_counter+
1175
+ # and +decrement_counter+. The counter cache is incremented when an object of this
1176
+ # class is created and decremented when it's destroyed. This requires that a column
1177
+ # named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
1178
+ # is used on the associate class (such as a Post class). You can also specify a custom counter
1179
+ # cache column by providing a column name instead of a +true+/+false+ value to this
1180
+ # option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
1181
+ # Note: Specifying a counter cache will add it to that model's list of readonly attributes
1182
+ # using +attr_readonly+.
1183
+ # [:include]
1184
+ # Specify second-order associations that should be eager loaded when this object is loaded.
1185
+ # [:polymorphic]
1186
+ # Specify this association is a polymorphic association by passing +true+.
1187
+ # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
1188
+ # to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
1189
+ # [:readonly]
1190
+ # If true, the associated object is readonly through the association.
1191
+ # [:validate]
1192
+ # If +false+, don't validate the associated objects when saving the parent object. +false+ by default.
1193
+ # [:autosave]
1194
+ # If true, always save the associated object or destroy it if marked for destruction, when
1195
+ # saving the parent object.
1196
+ # If false, never save or destroy the associated object.
1197
+ # By default, only save the associated object if it's a new record.
1198
+ # [:touch]
1199
+ # If true, the associated object will be touched (the updated_at/on attributes set to now)
1200
+ # when this record is either saved or destroyed. If you specify a symbol, that attribute
1201
+ # will be updated with the current time instead of the updated_at/on attribute.
1202
+ # [:inverse_of]
1203
+ # Specifies the name of the <tt>has_one</tt> or <tt>has_many</tt> association on the associated
1204
+ # object that is the inverse of this <tt>belongs_to</tt> association. Does not work in
1205
+ # combination with the <tt>:polymorphic</tt> options.
1206
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
296
1207
  #
297
1208
  # Option examples:
298
1209
  # belongs_to :firm, :foreign_key => "client_of"
1210
+ # belongs_to :person, :primary_key => "name", :foreign_key => "person_name"
299
1211
  # belongs_to :author, :class_name => "Person", :foreign_key => "author_id"
300
- # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
1212
+ # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
301
1213
  # :conditions => 'discounts > #{payments_count}'
302
- def belongs_to(association_id, options = {})
303
- validate_options([ :class_name, :foreign_key, :remote, :conditions, :order, :dependent, :counter_cache ], options.keys)
304
-
305
- association_name, association_class_name, class_primary_key_name =
306
- associate_identification(association_id, options[:class_name], options[:foreign_key], false)
1214
+ # belongs_to :attachable, :polymorphic => true
1215
+ # belongs_to :project, :readonly => true
1216
+ # belongs_to :post, :counter_cache => true
1217
+ # belongs_to :company, :touch => true
1218
+ # belongs_to :company, :touch => :employees_last_updated_at
1219
+ def belongs_to(association_id, options = {})
1220
+ reflection = create_belongs_to_reflection(association_id, options)
307
1221
 
308
- association_class_primary_key_name = options[:foreign_key] || Inflector.underscore(Inflector.demodulize(association_class_name)) + "_id"
1222
+ if reflection.options[:polymorphic]
1223
+ association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
1224
+ else
1225
+ association_accessor_methods(reflection, BelongsToAssociation)
1226
+ association_constructor_method(:build, reflection, BelongsToAssociation)
1227
+ association_constructor_method(:create, reflection, BelongsToAssociation)
1228
+ end
309
1229
 
310
- if options[:remote]
311
- association_finder = <<-"end_eval"
312
- #{association_class_name}.find_first(
313
- "#{class_primary_key_name} = '\#{id}'#{options[:conditions] ? " AND " + options[:conditions] : ""}",
314
- #{options[:order] ? "\"" + options[:order] + "\"" : "nil" }
315
- )
316
- end_eval
317
- else
318
- association_finder = options[:conditions] ?
319
- "#{association_class_name}.find_on_conditions(#{association_class_primary_key_name}, \"#{options[:conditions]}\")" :
320
- "#{association_class_name}.find(#{association_class_primary_key_name})"
321
- end
1230
+ add_counter_cache_callbacks(reflection) if options[:counter_cache]
1231
+ add_touch_callbacks(reflection, options[:touch]) if options[:touch]
322
1232
 
323
- has_association_method(association_name)
324
- association_reader_method(association_name, association_finder)
325
- belongs_to_writer_method(association_name, association_class_name, association_class_primary_key_name)
326
- association_comparison_method(association_name, association_class_name)
327
-
328
- if options[:counter_cache]
329
- module_eval(
330
- "after_create '#{association_class_name}.increment_counter(\"#{Inflector.pluralize(self.to_s.downcase). + "_count"}\", #{association_class_primary_key_name})" +
331
- " if has_#{association_name}?'"
332
- )
333
-
334
- module_eval(
335
- "before_destroy '#{association_class_name}.decrement_counter(\"#{Inflector.pluralize(self.to_s.downcase) + "_count"}\", #{association_class_primary_key_name})" +
336
- " if has_#{association_name}?'"
337
- )
338
- end
339
- end
1233
+ configure_dependency_for_belongs_to(reflection)
1234
+ end
340
1235
 
341
- # Associates two classes via an intermediate join table. Unless the join table is explicitly specified as
342
- # an option, it is guessed using the lexical order of the class names. So a join between Developer and Project
1236
+ # Specifies a many-to-many relationship with another class. This associates two classes via an
1237
+ # intermediate join table. Unless the join table is explicitly specified as an option, it is
1238
+ # guessed using the lexical order of the class names. So a join between Developer and Project
343
1239
  # will give the default join table name of "developers_projects" because "D" outranks "P".
344
- # Adds the following methods for retrival and query.
345
- # +collection+ is replaced with the symbol passed as the first argument, so
346
- # <tt>has_and_belongs_to_many :categories</tt> would add among others +add_categories+.
347
- # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
348
- # An empty array is returned if none is found.
349
- # * <tt>!collection.empty?</tt> - returns true if there's any associated objects.
350
- # * <tt>collection.size</tt> - returns the number of associated objects.
351
- # * <tt>collection<<(object)</tt> - adds an association between this object and the object given as argument. Multiple associations
352
- # can be created by passing an array of objects instead.
353
- # * <tt>collection.delete(object)</tt> - removes the association between this object and the object given as
354
- # argument. Multiple associations can be removed by passing an array of objects instead.
355
- #
356
- # Example: An Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
1240
+ # Note that this precedence is calculated using the <tt><</tt> operator for String. This
1241
+ # means that if the strings are of different lengths, and the strings are equal when compared
1242
+ # up to the shortest length, then the longer string is considered of higher
1243
+ # lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers"
1244
+ # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes",
1245
+ # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
1246
+ # custom <tt>:join_table</tt> option if you need to.
1247
+ #
1248
+ # The join table should not have a primary key or a model associated with it. You must manually generate the
1249
+ # join table with a migration such as this:
1250
+ #
1251
+ # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration
1252
+ # def self.up
1253
+ # create_table :developers_projects, :id => false do |t|
1254
+ # t.integer :developer_id
1255
+ # t.integer :project_id
1256
+ # end
1257
+ # end
1258
+ #
1259
+ # def self.down
1260
+ # drop_table :developers_projects
1261
+ # end
1262
+ # end
1263
+ #
1264
+ # Deprecated: Any additional fields added to the join table will be placed as attributes when
1265
+ # pulling records out through +has_and_belongs_to_many+ associations. Records returned from join
1266
+ # tables with additional attributes will be marked as readonly (because we can't save changes
1267
+ # to the additional attributes). It's strongly recommended that you upgrade any
1268
+ # associations with attributes to a real join model (see introduction).
1269
+ #
1270
+ # Adds the following methods for retrieval and query:
1271
+ #
1272
+ # [collection(force_reload = false)]
1273
+ # Returns an array of all the associated objects.
1274
+ # An empty array is returned if none are found.
1275
+ # [collection<<(object, ...)]
1276
+ # Adds one or more objects to the collection by creating associations in the join table
1277
+ # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
1278
+ # Note that this operation instantly fires update sql without waiting for the save or update call on the
1279
+ # parent object.
1280
+ # [collection.delete(object, ...)]
1281
+ # Removes one or more objects from the collection by removing their associations from the join table.
1282
+ # This does not destroy the objects.
1283
+ # [collection=objects]
1284
+ # Replaces the collection's content by deleting and adding objects as appropriate.
1285
+ # [collection_singular_ids]
1286
+ # Returns an array of the associated objects' ids.
1287
+ # [collection_singular_ids=ids]
1288
+ # Replace the collection by the objects identified by the primary keys in +ids+.
1289
+ # [collection.clear]
1290
+ # Removes every object from the collection. This does not destroy the objects.
1291
+ # [collection.empty?]
1292
+ # Returns +true+ if there are no associated objects.
1293
+ # [collection.size]
1294
+ # Returns the number of associated objects.
1295
+ # [collection.find(id)]
1296
+ # Finds an associated object responding to the +id+ and that
1297
+ # meets the condition that it has to be associated with this object.
1298
+ # Uses the same rules as ActiveRecord::Base.find.
1299
+ # [collection.exists?(...)]
1300
+ # Checks whether an associated object with the given conditions exists.
1301
+ # Uses the same rules as ActiveRecord::Base.exists?.
1302
+ # [collection.build(attributes = {})]
1303
+ # Returns a new object of the collection type that has been instantiated
1304
+ # with +attributes+ and linked to this object through the join table, but has not yet been saved.
1305
+ # [collection.create(attributes = {})]
1306
+ # Returns a new object of the collection type that has been instantiated
1307
+ # with +attributes+, linked to this object through the join table, and that has already been
1308
+ # saved (if it passed the validation).
1309
+ #
1310
+ # (+collection+ is replaced with the symbol passed as the first argument, so
1311
+ # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.)
1312
+ #
1313
+ # === Example
1314
+ #
1315
+ # A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
357
1316
  # * <tt>Developer#projects</tt>
358
- # * <tt>!Developer#projects.empty?</tt>
359
- # * <tt>Developer#projects.size</tt>
360
1317
  # * <tt>Developer#projects<<</tt>
361
1318
  # * <tt>Developer#projects.delete</tt>
362
- # The declaration can also include an options hash to specialize the generated methods.
363
- #
364
- # Options are:
365
- # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be infered
366
- # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
367
- # +Project+ class, but if the real class name is +SuperProject+, you'll have to specify it with this option.
368
- # * <tt>:join_table</tt> - specify the name of the join table if the default based on lexical order isn't what you want.
369
- # WARNING: If you're overwriting the table name of either class, the table_name method MUST be declared underneath any
370
- # has_and_belongs_to_many declaration in order to work.
371
- # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
372
- # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_and_belongs_to_many association
373
- # will use "person_id" as the default foreign_key.
374
- # * <tt>:association_foreign_key</tt> - specify the association foreign key used for the association. By default this is
375
- # guessed to be the name of the associated class in lower-case and "_id" suffixed. So the associated class is +Project+
376
- # that makes a has_and_belongs_to_many association will use "project_id" as the default association foreign_key.
377
- # * <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".
378
- # * <tt>:finder_sql</tt> - overwrite the default generated SQL used to fetch the association with a manual one
379
- # * <tt>:delete_sql</tt> - overwrite the default generated SQL used to remove links between the associated
380
- # classes with a manual one
381
- # * <tt>:insert_sql</tt> - overwrite the default generated SQL used to add links between the associated classes
382
- # with a manual one
1319
+ # * <tt>Developer#projects=</tt>
1320
+ # * <tt>Developer#project_ids</tt>
1321
+ # * <tt>Developer#project_ids=</tt>
1322
+ # * <tt>Developer#projects.clear</tt>
1323
+ # * <tt>Developer#projects.empty?</tt>
1324
+ # * <tt>Developer#projects.size</tt>
1325
+ # * <tt>Developer#projects.find(id)</tt>
1326
+ # * <tt>Developer#projects.exists?(...)</tt>
1327
+ # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("project_id" => id)</tt>)
1328
+ # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("project_id" => id); c.save; c</tt>)
1329
+ # The declaration may include an options hash to specialize the behavior of the association.
1330
+ #
1331
+ # === Options
1332
+ #
1333
+ # [:class_name]
1334
+ # Specify the class name of the association. Use it only if that name can't be inferred
1335
+ # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
1336
+ # Project class, but if the real class name is SuperProject, you'll have to specify it with this option.
1337
+ # [:join_table]
1338
+ # Specify the name of the join table if the default based on lexical order isn't what you want.
1339
+ # <b>WARNING:</b> If you're overwriting the table name of either class, the +table_name+ method
1340
+ # MUST be declared underneath any +has_and_belongs_to_many+ declaration in order to work.
1341
+ # [:foreign_key]
1342
+ # Specify the foreign key used for the association. By default this is guessed to be the name
1343
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes
1344
+ # a +has_and_belongs_to_many+ association to Project will use "person_id" as the
1345
+ # default <tt>:foreign_key</tt>.
1346
+ # [:association_foreign_key]
1347
+ # Specify the foreign key used for the association on the receiving side of the association.
1348
+ # By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed.
1349
+ # So if a Person class makes a +has_and_belongs_to_many+ association to Project,
1350
+ # the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
1351
+ # [:conditions]
1352
+ # Specify the conditions that the associated object must meet in order to be included as a +WHERE+
1353
+ # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are
1354
+ # scoped if a hash is used.
1355
+ # <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
1356
+ # or <tt>@blog.posts.build</tt>.
1357
+ # [:order]
1358
+ # Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
1359
+ # such as <tt>last_name, first_name DESC</tt>
1360
+ # [:uniq]
1361
+ # If true, duplicate associated objects will be ignored by accessors and query methods.
1362
+ # [:finder_sql]
1363
+ # Overwrite the default generated SQL statement used to fetch the association with a manual statement
1364
+ # [:counter_sql]
1365
+ # Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
1366
+ # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by
1367
+ # replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
1368
+ # [:delete_sql]
1369
+ # Overwrite the default generated SQL statement used to remove links between the associated
1370
+ # classes with a manual statement.
1371
+ # [:insert_sql]
1372
+ # Overwrite the default generated SQL statement used to add links between the associated classes
1373
+ # with a manual statement.
1374
+ # [:extend]
1375
+ # Anonymous module for extending the proxy, see "Association extensions".
1376
+ # [:include]
1377
+ # Specify second-order associations that should be eager loaded when the collection is loaded.
1378
+ # [:group]
1379
+ # An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
1380
+ # [:having]
1381
+ # Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns.
1382
+ # Uses the <tt>HAVING</tt> SQL-clause.
1383
+ # [:limit]
1384
+ # An integer determining the limit on the number of rows that should be returned.
1385
+ # [:offset]
1386
+ # An integer determining the offset from where the rows should be fetched. So at 5,
1387
+ # it would skip the first 4 rows.
1388
+ # [:select]
1389
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example,
1390
+ # you want to do a join but not include the joined columns. Do not forget to include the primary
1391
+ # and foreign keys, otherwise it will raise an error.
1392
+ # [:readonly]
1393
+ # If true, all the associated objects are readonly through the association.
1394
+ # [:validate]
1395
+ # If +false+, don't validate the associated objects when saving the parent object. +true+ by default.
1396
+ # [:autosave]
1397
+ # If true, always save the associated objects or destroy them if marked for destruction, when
1398
+ # saving the parent object.
1399
+ # If false, never save or destroy the associated objects.
1400
+ # By default, only save associated objects that are new records.
383
1401
  #
384
1402
  # Option examples:
385
1403
  # has_and_belongs_to_many :projects
1404
+ # has_and_belongs_to_many :projects, :include => [ :milestones, :manager ]
386
1405
  # has_and_belongs_to_many :nations, :class_name => "Country"
387
1406
  # has_and_belongs_to_many :categories, :join_table => "prods_cats"
388
- def has_and_belongs_to_many(association_id, options = {})
389
- validate_options([ :class_name, :table_name, :foreign_key, :association_foreign_key,
390
- :join_table, :finder_sql, :delete_sql, :insert_sql, :order ], options.keys)
391
- association_name, association_class_name, association_class_primary_key_name =
392
- associate_identification(association_id, options[:class_name], options[:foreign_key])
393
-
394
- join_table = options[:join_table] ||
395
- join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(association_class_name))
396
-
397
-
398
- module_eval <<-"end_eval", __FILE__, __LINE__
399
- def #{association_name}(force_reload = false)
400
- if @#{association_name}.nil?
401
- @#{association_name} = HasAndBelongsToManyCollection.new(self, "#{association_name}", "#{association_class_name}",
402
- "#{association_class_primary_key_name}", '#{join_table}', #{options.inspect})
403
- end
404
- @#{association_name}.reload if force_reload
405
-
406
- return @#{association_name}
407
- end
408
- end_eval
409
-
410
- before_destroy_sql = "DELETE FROM #{join_table} WHERE #{Inflector.foreign_key(self.class_name)} = '\\\#{self.id}'"
411
- module_eval(%{before_destroy "self.connection.delete(%{#{before_destroy_sql}})"}) # "
412
-
413
- # deprecated api
414
- deprecated_collection_count_method(association_name)
415
- deprecated_add_association_relation(association_name)
416
- deprecated_remove_association_relation(association_name)
417
- deprecated_has_collection_method(association_name)
1407
+ # has_and_belongs_to_many :categories, :readonly => true
1408
+ # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
1409
+ # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
1410
+ def has_and_belongs_to_many(association_id, options = {}, &extension)
1411
+ reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
1412
+ collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
1413
+
1414
+ # Don't use a before_destroy callback since users' before_destroy
1415
+ # callbacks will be executed after the association is wiped out.
1416
+ include Module.new {
1417
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
1418
+ def destroy # def destroy
1419
+ super # super
1420
+ #{reflection.name}.clear # posts.clear
1421
+ end # end
1422
+ RUBY
1423
+ }
1424
+
1425
+ add_association_callbacks(reflection.name, options)
418
1426
  end
419
1427
 
420
1428
  private
421
- # Raises an exception if an invalid option has been specified to prevent misspellings from slipping through
422
- def validate_options(valid_option_keys, supplied_option_keys)
423
- unknown_option_keys = supplied_option_keys - valid_option_keys
424
- raise(ActiveRecord::ActiveRecordError, "Unknown options: #{unknown_option_keys}") unless unknown_option_keys.empty?
425
- end
426
-
1429
+ # Generates a join table name from two provided table names.
1430
+ # The names in the join table names end up in lexicographic order.
1431
+ #
1432
+ # join_table_name("members", "clubs") # => "clubs_members"
1433
+ # join_table_name("members", "special_clubs") # => "members_special_clubs"
427
1434
  def join_table_name(first_table_name, second_table_name)
428
1435
  if first_table_name < second_table_name
429
1436
  join_table = "#{first_table_name}_#{second_table_name}"
@@ -433,104 +1440,815 @@ module ActiveRecord
433
1440
 
434
1441
  table_name_prefix + join_table + table_name_suffix
435
1442
  end
436
-
437
- def associate_identification(association_id, association_class_name, foreign_key, plural = true)
438
- if association_class_name !~ /::/
439
- association_class_name = type_name_with_module(
440
- association_class_name ||
441
- Inflector.camelize(plural ? Inflector.singularize(association_id.id2name) : association_id.id2name)
442
- )
1443
+
1444
+ def association_accessor_methods(reflection, association_proxy_class)
1445
+ redefine_method(reflection.name) do |*params|
1446
+ force_reload = params.first unless params.empty?
1447
+ association = association_instance_get(reflection.name)
1448
+
1449
+ if association.nil? || force_reload
1450
+ association = association_proxy_class.new(self, reflection)
1451
+ retval = force_reload ? reflection.klass.uncached { association.reload } : association.reload
1452
+ if retval.nil? and association_proxy_class == BelongsToAssociation
1453
+ association_instance_set(reflection.name, nil)
1454
+ return nil
1455
+ end
1456
+ association_instance_set(reflection.name, association)
1457
+ end
1458
+
1459
+ association.target.nil? ? nil : association
1460
+ end
1461
+
1462
+ redefine_method("loaded_#{reflection.name}?") do
1463
+ association = association_instance_get(reflection.name)
1464
+ association && association.loaded?
1465
+ end
1466
+
1467
+ redefine_method("#{reflection.name}=") do |new_value|
1468
+ association = association_instance_get(reflection.name)
1469
+
1470
+ if association.nil? || association.target != new_value
1471
+ association = association_proxy_class.new(self, reflection)
1472
+ end
1473
+
1474
+ association.replace(new_value)
1475
+ association_instance_set(reflection.name, new_value.nil? ? nil : association)
443
1476
  end
444
1477
 
445
- primary_key_name = foreign_key || Inflector.underscore(Inflector.demodulize(name)) + "_id"
446
-
447
- return association_id.id2name, association_class_name, primary_key_name
1478
+ redefine_method("set_#{reflection.name}_target") do |target|
1479
+ return if target.nil? and association_proxy_class == BelongsToAssociation
1480
+ association = association_proxy_class.new(self, reflection)
1481
+ association.target = target
1482
+ association_instance_set(reflection.name, association)
1483
+ end
448
1484
  end
449
-
450
- def association_comparison_method(association_name, association_class_name)
451
- module_eval <<-"end_eval", __FILE__, __LINE__
452
- def #{association_name}?(comparison_object, force_reload = false)
453
- if comparison_object.kind_of?(#{association_class_name})
454
- #{association_name}(force_reload) == comparison_object
1485
+
1486
+ def collection_reader_method(reflection, association_proxy_class)
1487
+ redefine_method(reflection.name) do |*params|
1488
+ force_reload = params.first unless params.empty?
1489
+ association = association_instance_get(reflection.name)
1490
+
1491
+ unless association
1492
+ association = association_proxy_class.new(self, reflection)
1493
+ association_instance_set(reflection.name, association)
1494
+ end
1495
+
1496
+ reflection.klass.uncached { association.reload } if force_reload
1497
+
1498
+ association
1499
+ end
1500
+
1501
+ redefine_method("#{reflection.name.to_s.singularize}_ids") do
1502
+ if send(reflection.name).loaded? || reflection.options[:finder_sql]
1503
+ send(reflection.name).map { |r| r.id }
1504
+ else
1505
+ if reflection.through_reflection && reflection.source_reflection.belongs_to?
1506
+ through = reflection.through_reflection
1507
+ primary_key = reflection.source_reflection.primary_key_name
1508
+ send(through.name).select("DISTINCT #{through.quoted_table_name}.#{primary_key}").map! { |r| r.send(primary_key) }
455
1509
  else
456
- raise "Comparison object is a #{association_class_name}, should have been \#{comparison_object.class.name}"
1510
+ send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").except(:includes).map! { |r| r.id }
457
1511
  end
458
1512
  end
459
- end_eval
1513
+ end
1514
+
460
1515
  end
461
1516
 
462
- def association_reader_method(association_name, association_finder)
463
- module_eval <<-"end_eval", __FILE__, __LINE__
464
- def #{association_name}(force_reload = false)
465
- if @#{association_name}.nil? || force_reload
466
- begin
467
- @#{association_name} = #{association_finder}
468
- rescue ActiveRecord::StatementInvalid, ActiveRecord::RecordNotFound
469
- nil
470
- end
471
- end
472
-
473
- return @#{association_name}
1517
+ def collection_accessor_methods(reflection, association_proxy_class, writer = true)
1518
+ collection_reader_method(reflection, association_proxy_class)
1519
+
1520
+ if writer
1521
+ redefine_method("#{reflection.name}=") do |new_value|
1522
+ # Loads proxy class instance (defined in collection_reader_method) if not already loaded
1523
+ association = send(reflection.name)
1524
+ association.replace(new_value)
1525
+ association
1526
+ end
1527
+
1528
+ redefine_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
1529
+ pk_column = reflection.primary_key_column
1530
+ ids = (new_value || []).reject { |nid| nid.blank? }
1531
+ ids.map!{ |i| pk_column.type_cast(i) }
1532
+ send("#{reflection.name}=", reflection.klass.find(ids).index_by{ |r| r.id }.values_at(*ids))
474
1533
  end
475
- end_eval
1534
+ end
476
1535
  end
477
1536
 
478
- def has_one_writer_method(association_name, association_class_name, class_primary_key_name)
479
- module_eval <<-"end_eval", __FILE__, __LINE__
480
- def #{association_name}=(association)
481
- if association.nil?
482
- @#{association_name}.#{class_primary_key_name} = nil
483
- @#{association_name}.save(false)
484
- @#{association_name} = nil
1537
+ def association_constructor_method(constructor, reflection, association_proxy_class)
1538
+ redefine_method("#{constructor}_#{reflection.name}") do |*params|
1539
+ attributees = params.first unless params.empty?
1540
+ replace_existing = params[1].nil? ? true : params[1]
1541
+ association = association_instance_get(reflection.name)
1542
+
1543
+ unless association
1544
+ association = association_proxy_class.new(self, reflection)
1545
+ association_instance_set(reflection.name, association)
1546
+ end
1547
+
1548
+ if association_proxy_class == HasOneAssociation
1549
+ association.send(constructor, attributees, replace_existing)
1550
+ else
1551
+ association.send(constructor, attributees)
1552
+ end
1553
+ end
1554
+ end
1555
+
1556
+ def add_counter_cache_callbacks(reflection)
1557
+ cache_column = reflection.counter_cache_column
1558
+
1559
+ method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
1560
+ define_method(method_name) do
1561
+ association = send(reflection.name)
1562
+ association.class.increment_counter(cache_column, association.id) unless association.nil?
1563
+ end
1564
+ after_create(method_name)
1565
+
1566
+ method_name = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".to_sym
1567
+ define_method(method_name) do
1568
+ association = send(reflection.name)
1569
+ association.class.decrement_counter(cache_column, association.id) unless association.nil?
1570
+ end
1571
+ before_destroy(method_name)
1572
+
1573
+ module_eval(
1574
+ "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)", __FILE__, __LINE__
1575
+ )
1576
+ end
1577
+
1578
+ def add_touch_callbacks(reflection, touch_attribute)
1579
+ method_name = :"belongs_to_touch_after_save_or_destroy_for_#{reflection.name}"
1580
+ redefine_method(method_name) do
1581
+ association = send(reflection.name)
1582
+
1583
+ if touch_attribute == true
1584
+ association.touch unless association.nil?
1585
+ else
1586
+ association.touch(touch_attribute) unless association.nil?
1587
+ end
1588
+ end
1589
+ after_save(method_name)
1590
+ after_touch(method_name)
1591
+ after_destroy(method_name)
1592
+ end
1593
+
1594
+ # Creates before_destroy callback methods that nullify, delete or destroy
1595
+ # has_many associated objects, according to the defined :dependent rule.
1596
+ # If the association is marked as :dependent => :restrict, create a callback
1597
+ # that prevents deleting entirely.
1598
+ #
1599
+ # See HasManyAssociation#delete_records. Dependent associations
1600
+ # delete children, otherwise foreign key is set to NULL.
1601
+ # See HasManyAssociation#delete_records. Dependent associations
1602
+ # delete children if the option is set to :destroy or :delete_all, set the
1603
+ # foreign key to NULL if the option is set to :nullify, and do not touch the
1604
+ # child records if the option is set to :restrict.
1605
+ #
1606
+ # The +extra_conditions+ parameter, which is not used within the main
1607
+ # Active Record codebase, is meant to allow plugins to define extra
1608
+ # finder conditions.
1609
+ def configure_dependency_for_has_many(reflection, extra_conditions = nil)
1610
+ if reflection.options.include?(:dependent)
1611
+ case reflection.options[:dependent]
1612
+ when :destroy
1613
+ method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
1614
+ define_method(method_name) do
1615
+ send(reflection.name).each do |o|
1616
+ # No point in executing the counter update since we're going to destroy the parent anyway
1617
+ counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym
1618
+ if(o.respond_to? counter_method) then
1619
+ class << o
1620
+ self
1621
+ end.send(:define_method, counter_method, Proc.new {})
1622
+ end
1623
+ o.destroy
1624
+ end
1625
+ end
1626
+ before_destroy method_name
1627
+ when :delete_all
1628
+ before_destroy do |record|
1629
+ self.class.send(:delete_all_has_many_dependencies,
1630
+ record,
1631
+ reflection.name,
1632
+ reflection.klass,
1633
+ reflection.dependent_conditions(record, self.class, extra_conditions))
1634
+ end
1635
+ when :nullify
1636
+ before_destroy do |record|
1637
+ self.class.send(:nullify_has_many_dependencies,
1638
+ record,
1639
+ reflection.name,
1640
+ reflection.klass,
1641
+ reflection.primary_key_name,
1642
+ reflection.dependent_conditions(record, self.class, extra_conditions))
1643
+ end
1644
+ when :restrict
1645
+ method_name = "has_many_dependent_restrict_for_#{reflection.name}".to_sym
1646
+ define_method(method_name) do
1647
+ unless send(reflection.name).empty?
1648
+ raise DeleteRestrictionError.new(reflection)
1649
+ end
1650
+ end
1651
+ before_destroy method_name
485
1652
  else
486
- raise ActiveRecord::AssociationTypeMismatch unless #{association_class_name} === association
487
- association.#{class_primary_key_name} = id
488
- association.save(false)
489
- @#{association_name} = association
490
- end
1653
+ raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, :nullify or :restrict (#{reflection.options[:dependent].inspect})"
491
1654
  end
492
- end_eval
1655
+ end
493
1656
  end
494
1657
 
495
- def belongs_to_writer_method(association_name, association_class_name, association_class_primary_key_name)
496
- module_eval <<-"end_eval", __FILE__, __LINE__
497
- def #{association_name}=(association)
498
- if association.nil?
499
- @#{association_name} = self.#{association_class_primary_key_name} = nil
1658
+ # Creates before_destroy callback methods that nullify, delete or destroy
1659
+ # has_one associated objects, according to the defined :dependent rule.
1660
+ # If the association is marked as :dependent => :restrict, create a callback
1661
+ # that prevents deleting entirely.
1662
+ def configure_dependency_for_has_one(reflection)
1663
+ if reflection.options.include?(:dependent)
1664
+ name = reflection.options[:dependent]
1665
+ method_name = :"has_one_dependent_#{name}_for_#{reflection.name}"
1666
+
1667
+ case name
1668
+ when :destroy, :delete
1669
+ class_eval <<-eoruby, __FILE__, __LINE__ + 1
1670
+ def #{method_name}
1671
+ association = #{reflection.name}
1672
+ association.#{name} if association
1673
+ end
1674
+ eoruby
1675
+ when :nullify
1676
+ class_eval <<-eoruby, __FILE__, __LINE__ + 1
1677
+ def #{method_name}
1678
+ association = #{reflection.name}
1679
+ association.update_attribute(#{reflection.primary_key_name.inspect}, nil) if association
1680
+ end
1681
+ eoruby
1682
+ when :restrict
1683
+ method_name = "has_one_dependent_restrict_for_#{reflection.name}".to_sym
1684
+ define_method(method_name) do
1685
+ unless send(reflection.name).nil?
1686
+ raise DeleteRestrictionError.new(reflection)
1687
+ end
1688
+ end
1689
+ before_destroy method_name
500
1690
  else
501
- raise ActiveRecord::AssociationTypeMismatch unless #{association_class_name} === association
502
- @#{association_name} = association
503
- self.#{association_class_primary_key_name} = association.id
504
- end
1691
+ raise ArgumentError, "The :dependent option expects either :destroy, :delete, :nullify or :restrict (#{reflection.options[:dependent].inspect})"
505
1692
  end
506
- end_eval
1693
+
1694
+ before_destroy method_name
1695
+ end
507
1696
  end
508
1697
 
509
- def has_association_method(association_name)
510
- module_eval <<-"end_eval", __FILE__, __LINE__
511
- def has_#{association_name}?(force_reload = false)
512
- !#{association_name}(force_reload).nil?
1698
+ def configure_dependency_for_belongs_to(reflection)
1699
+ if reflection.options.include?(:dependent)
1700
+ name = reflection.options[:dependent]
1701
+
1702
+ unless [:destroy, :delete].include?(name)
1703
+ raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{reflection.options[:dependent].inspect})"
513
1704
  end
514
- end_eval
1705
+
1706
+ method_name = :"belongs_to_dependent_#{name}_for_#{reflection.name}"
1707
+ class_eval <<-eoruby, __FILE__, __LINE__ + 1
1708
+ def #{method_name}
1709
+ association = #{reflection.name}
1710
+ association.#{name} if association
1711
+ end
1712
+ eoruby
1713
+ after_destroy method_name
1714
+ end
515
1715
  end
516
-
517
- def build_method(method_prefix, collection_name, collection_class_name, class_primary_key_name)
518
- module_eval <<-"end_eval", __FILE__, __LINE__
519
- def #{method_prefix + collection_name}(attributes = {})
520
- association = #{collection_class_name}.new
521
- association.attributes = attributes.merge({ "#{class_primary_key_name}" => id})
522
- association
1716
+
1717
+ def delete_all_has_many_dependencies(record, reflection_name, association_class, dependent_conditions)
1718
+ association_class.delete_all(dependent_conditions)
1719
+ end
1720
+
1721
+ def nullify_has_many_dependencies(record, reflection_name, association_class, primary_key_name, dependent_conditions)
1722
+ association_class.update_all("#{primary_key_name} = NULL", dependent_conditions)
1723
+ end
1724
+
1725
+ mattr_accessor :valid_keys_for_has_many_association
1726
+ @@valid_keys_for_has_many_association = [
1727
+ :class_name, :table_name, :foreign_key, :primary_key,
1728
+ :dependent,
1729
+ :select, :conditions, :include, :order, :group, :having, :limit, :offset,
1730
+ :as, :through, :source, :source_type,
1731
+ :uniq,
1732
+ :finder_sql, :counter_sql,
1733
+ :before_add, :after_add, :before_remove, :after_remove,
1734
+ :extend, :readonly,
1735
+ :validate, :inverse_of
1736
+ ]
1737
+
1738
+ def create_has_many_reflection(association_id, options, &extension)
1739
+ options.assert_valid_keys(valid_keys_for_has_many_association)
1740
+ options[:extend] = create_extension_modules(association_id, extension, options[:extend])
1741
+
1742
+ create_reflection(:has_many, association_id, options, self)
1743
+ end
1744
+
1745
+ mattr_accessor :valid_keys_for_has_one_association
1746
+ @@valid_keys_for_has_one_association = [
1747
+ :class_name, :foreign_key, :remote, :select, :conditions, :order,
1748
+ :include, :dependent, :counter_cache, :extend, :as, :readonly,
1749
+ :validate, :primary_key, :inverse_of
1750
+ ]
1751
+
1752
+ def create_has_one_reflection(association_id, options)
1753
+ options.assert_valid_keys(valid_keys_for_has_one_association)
1754
+ create_reflection(:has_one, association_id, options, self)
1755
+ end
1756
+
1757
+ def create_has_one_through_reflection(association_id, options)
1758
+ options.assert_valid_keys(
1759
+ :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate
1760
+ )
1761
+ create_reflection(:has_one, association_id, options, self)
1762
+ end
1763
+
1764
+ mattr_accessor :valid_keys_for_belongs_to_association
1765
+ @@valid_keys_for_belongs_to_association = [
1766
+ :class_name, :primary_key, :foreign_key, :foreign_type, :remote, :select, :conditions,
1767
+ :include, :dependent, :counter_cache, :extend, :polymorphic, :readonly,
1768
+ :validate, :touch, :inverse_of
1769
+ ]
1770
+
1771
+ def create_belongs_to_reflection(association_id, options)
1772
+ options.assert_valid_keys(valid_keys_for_belongs_to_association)
1773
+ reflection = create_reflection(:belongs_to, association_id, options, self)
1774
+
1775
+ if options[:polymorphic]
1776
+ reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
1777
+ end
1778
+
1779
+ reflection
1780
+ end
1781
+
1782
+ mattr_accessor :valid_keys_for_has_and_belongs_to_many_association
1783
+ @@valid_keys_for_has_and_belongs_to_many_association = [
1784
+ :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
1785
+ :select, :conditions, :include, :order, :group, :having, :limit, :offset,
1786
+ :uniq,
1787
+ :finder_sql, :counter_sql, :delete_sql, :insert_sql,
1788
+ :before_add, :after_add, :before_remove, :after_remove,
1789
+ :extend, :readonly,
1790
+ :validate
1791
+ ]
1792
+
1793
+ def create_has_and_belongs_to_many_reflection(association_id, options, &extension)
1794
+ options.assert_valid_keys(valid_keys_for_has_and_belongs_to_many_association)
1795
+ options[:extend] = create_extension_modules(association_id, extension, options[:extend])
1796
+
1797
+ reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
1798
+
1799
+ if reflection.association_foreign_key == reflection.primary_key_name
1800
+ raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
1801
+ end
1802
+
1803
+ reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
1804
+ if connection.supports_primary_key? && (connection.primary_key(reflection.options[:join_table]) rescue false)
1805
+ raise HasAndBelongsToManyAssociationWithPrimaryKeyError.new(reflection)
1806
+ end
1807
+
1808
+ reflection
1809
+ end
1810
+
1811
+ def add_association_callbacks(association_name, options)
1812
+ callbacks = %w(before_add after_add before_remove after_remove)
1813
+ callbacks.each do |callback_name|
1814
+ full_callback_name = "#{callback_name}_for_#{association_name}"
1815
+ defined_callbacks = options[callback_name.to_sym]
1816
+ if options.has_key?(callback_name.to_sym)
1817
+ class_inheritable_reader full_callback_name.to_sym
1818
+ write_inheritable_attribute(full_callback_name.to_sym, [defined_callbacks].flatten)
1819
+ else
1820
+ write_inheritable_attribute(full_callback_name.to_sym, [])
523
1821
  end
524
- end_eval
1822
+ end
525
1823
  end
526
1824
 
527
- def create_method(method_prefix, collection_name, collection_class_name, class_primary_key_name)
528
- module_eval <<-"end_eval", __FILE__, __LINE__
529
- def #{method_prefix + collection_name}(attributes = nil)
530
- #{collection_class_name}.create((attributes || {}).merge({ "#{class_primary_key_name}" => id}))
1825
+ def create_extension_modules(association_id, block_extension, extensions)
1826
+ if block_extension
1827
+ extension_module_name = "#{self.to_s.demodulize}#{association_id.to_s.camelize}AssociationExtension"
1828
+
1829
+ silence_warnings do
1830
+ self.parent.const_set(extension_module_name, Module.new(&block_extension))
531
1831
  end
532
- end_eval
1832
+ Array.wrap(extensions).push("#{self.parent}::#{extension_module_name}".constantize)
1833
+ else
1834
+ Array.wrap(extensions)
1835
+ end
1836
+ end
1837
+
1838
+ class JoinDependency # :nodoc:
1839
+ attr_reader :joins, :reflections, :table_aliases
1840
+
1841
+ def initialize(base, associations, joins)
1842
+ @joins = [JoinBase.new(base, joins)]
1843
+ @associations = associations
1844
+ @reflections = []
1845
+ @base_records_hash = {}
1846
+ @base_records_in_order = []
1847
+ @table_aliases = Hash.new { |aliases, table| aliases[table] = 0 }
1848
+ @table_aliases[base.table_name] = 1
1849
+ build(associations)
1850
+ end
1851
+
1852
+ def graft(*associations)
1853
+ associations.each do |association|
1854
+ join_associations.detect {|a| association == a} ||
1855
+ build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_class)
1856
+ end
1857
+ self
1858
+ end
1859
+
1860
+ def join_associations
1861
+ @joins[1..-1].to_a
1862
+ end
1863
+
1864
+ def join_base
1865
+ @joins[0]
1866
+ end
1867
+
1868
+ def count_aliases_from_table_joins(name)
1869
+ # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
1870
+ quoted_name = join_base.active_record.connection.quote_table_name(name.downcase).downcase
1871
+ join_sql = join_base.table_joins.to_s.downcase
1872
+ join_sql.blank? ? 0 :
1873
+ # Table names
1874
+ join_sql.scan(/join(?:\s+\w+)?\s+#{quoted_name}\son/).size +
1875
+ # Table aliases
1876
+ join_sql.scan(/join(?:\s+\w+)?\s+\S+\s+#{quoted_name}\son/).size
1877
+ end
1878
+
1879
+ def instantiate(rows)
1880
+ rows.each_with_index do |row, i|
1881
+ primary_id = join_base.record_id(row)
1882
+ unless @base_records_hash[primary_id]
1883
+ @base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row))
1884
+ end
1885
+ construct(@base_records_hash[primary_id], @associations, join_associations.dup, row)
1886
+ end
1887
+ remove_duplicate_results!(join_base.active_record, @base_records_in_order, @associations)
1888
+ return @base_records_in_order
1889
+ end
1890
+
1891
+ def remove_duplicate_results!(base, records, associations)
1892
+ case associations
1893
+ when Symbol, String
1894
+ reflection = base.reflections[associations]
1895
+ remove_uniq_by_reflection(reflection, records)
1896
+ when Array
1897
+ associations.each do |association|
1898
+ remove_duplicate_results!(base, records, association)
1899
+ end
1900
+ when Hash
1901
+ associations.keys.each do |name|
1902
+ reflection = base.reflections[name]
1903
+ remove_uniq_by_reflection(reflection, records)
1904
+
1905
+ parent_records = []
1906
+ records.each do |record|
1907
+ if descendant = record.send(reflection.name)
1908
+ if reflection.collection?
1909
+ parent_records.concat descendant.target.uniq
1910
+ else
1911
+ parent_records << descendant
1912
+ end
1913
+ end
1914
+ end
1915
+
1916
+ remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
1917
+ end
1918
+ end
1919
+ end
1920
+
1921
+ protected
1922
+
1923
+ def build(associations, parent = nil, join_class = Arel::InnerJoin)
1924
+ parent ||= @joins.last
1925
+ case associations
1926
+ when Symbol, String
1927
+ reflection = parent.reflections[associations.to_s.intern] or
1928
+ raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
1929
+ @reflections << reflection
1930
+ @joins << build_join_association(reflection, parent).with_join_class(join_class)
1931
+ when Array
1932
+ associations.each do |association|
1933
+ build(association, parent, join_class)
1934
+ end
1935
+ when Hash
1936
+ associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
1937
+ build(name, parent, join_class)
1938
+ build(associations[name], nil, join_class)
1939
+ end
1940
+ else
1941
+ raise ConfigurationError, associations.inspect
1942
+ end
1943
+ end
1944
+
1945
+ def remove_uniq_by_reflection(reflection, records)
1946
+ if reflection && reflection.collection?
1947
+ records.each { |record| record.send(reflection.name).target.uniq! }
1948
+ end
1949
+ end
1950
+
1951
+ def build_join_association(reflection, parent)
1952
+ JoinAssociation.new(reflection, self, parent)
1953
+ end
1954
+
1955
+ def construct(parent, associations, joins, row)
1956
+ case associations
1957
+ when Symbol, String
1958
+ join = joins.detect{|j| j.reflection.name.to_s == associations.to_s && j.parent_table_name == parent.class.table_name }
1959
+ raise(ConfigurationError, "No such association") if join.nil?
1960
+
1961
+ joins.delete(join)
1962
+ construct_association(parent, join, row)
1963
+ when Array
1964
+ associations.each do |association|
1965
+ construct(parent, association, joins, row)
1966
+ end
1967
+ when Hash
1968
+ associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
1969
+ join = joins.detect{|j| j.reflection.name.to_s == name.to_s && j.parent_table_name == parent.class.table_name }
1970
+ raise(ConfigurationError, "No such association") if join.nil?
1971
+
1972
+ association = construct_association(parent, join, row)
1973
+ joins.delete(join)
1974
+ construct(association, associations[name], joins, row) if association
1975
+ end
1976
+ else
1977
+ raise ConfigurationError, associations.inspect
1978
+ end
1979
+ end
1980
+
1981
+ def construct_association(record, join, row)
1982
+ case join.reflection.macro
1983
+ when :has_many, :has_and_belongs_to_many
1984
+ collection = record.send(join.reflection.name)
1985
+ collection.loaded
1986
+
1987
+ return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
1988
+ association = join.instantiate(row)
1989
+ collection.target.push(association)
1990
+ collection.__send__(:set_inverse_instance, association, record)
1991
+ when :has_one
1992
+ return if record.id.to_s != join.parent.record_id(row).to_s
1993
+ return if record.instance_variable_defined?("@#{join.reflection.name}")
1994
+ association = join.instantiate(row) unless row[join.aliased_primary_key].nil?
1995
+ set_target_and_inverse(join, association, record)
1996
+ when :belongs_to
1997
+ return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
1998
+ association = join.instantiate(row)
1999
+ set_target_and_inverse(join, association, record)
2000
+ else
2001
+ raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
2002
+ end
2003
+ return association
2004
+ end
2005
+
2006
+ def set_target_and_inverse(join, association, record)
2007
+ association_proxy = record.send("set_#{join.reflection.name}_target", association)
2008
+ association_proxy.__send__(:set_inverse_instance, association, record)
2009
+ end
2010
+
2011
+ class JoinBase # :nodoc:
2012
+ attr_reader :active_record, :table_joins
2013
+ delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :arel_engine, :to => :active_record
2014
+
2015
+ def initialize(active_record, joins = nil)
2016
+ @active_record = active_record
2017
+ @cached_record = {}
2018
+ @table_joins = joins
2019
+ end
2020
+
2021
+ def ==(other)
2022
+ other.class == self.class &&
2023
+ other.active_record == active_record &&
2024
+ other.table_joins == table_joins
2025
+ end
2026
+
2027
+ def aliased_prefix
2028
+ "t0"
2029
+ end
2030
+
2031
+ def aliased_primary_key
2032
+ "#{aliased_prefix}_r0"
2033
+ end
2034
+
2035
+ def aliased_table_name
2036
+ active_record.table_name
2037
+ end
2038
+
2039
+ def column_names_with_alias
2040
+ unless defined?(@column_names_with_alias)
2041
+ @column_names_with_alias = []
2042
+
2043
+ ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
2044
+ @column_names_with_alias << [column_name, "#{aliased_prefix}_r#{i}"]
2045
+ end
2046
+ end
2047
+
2048
+ @column_names_with_alias
2049
+ end
2050
+
2051
+ def extract_record(row)
2052
+ column_names_with_alias.inject({}){|record, (cn, an)| record[cn] = row[an]; record}
2053
+ end
2054
+
2055
+ def record_id(row)
2056
+ row[aliased_primary_key]
2057
+ end
2058
+
2059
+ def instantiate(row)
2060
+ @cached_record[record_id(row)] ||= active_record.send(:instantiate, extract_record(row))
2061
+ end
2062
+ end
2063
+
2064
+ class JoinAssociation < JoinBase # :nodoc:
2065
+ attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name, :join_class
2066
+ delegate :options, :klass, :through_reflection, :source_reflection, :to => :reflection
2067
+
2068
+ def initialize(reflection, join_dependency, parent = nil)
2069
+ reflection.check_validity!
2070
+ if reflection.options[:polymorphic]
2071
+ raise EagerLoadPolymorphicError.new(reflection)
2072
+ end
2073
+
2074
+ super(reflection.klass)
2075
+ @join_dependency = join_dependency
2076
+ @parent = parent
2077
+ @reflection = reflection
2078
+ @aliased_prefix = "t#{ join_dependency.joins.size }"
2079
+ @parent_table_name = parent.active_record.table_name
2080
+ @aliased_table_name = aliased_table_name_for(table_name)
2081
+ @join = nil
2082
+ @join_class = Arel::InnerJoin
2083
+
2084
+ if reflection.macro == :has_and_belongs_to_many
2085
+ @aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
2086
+ end
2087
+
2088
+ if [:has_many, :has_one].include?(reflection.macro) && reflection.options[:through]
2089
+ @aliased_join_table_name = aliased_table_name_for(reflection.through_reflection.klass.table_name, "_join")
2090
+ end
2091
+ end
2092
+
2093
+ def ==(other)
2094
+ other.class == self.class &&
2095
+ other.reflection == reflection &&
2096
+ other.parent == parent
2097
+ end
2098
+
2099
+ def find_parent_in(other_join_dependency)
2100
+ other_join_dependency.joins.detect do |join|
2101
+ self.parent == join
2102
+ end
2103
+ end
2104
+
2105
+ def with_join_class(join_class)
2106
+ @join_class = join_class
2107
+ self
2108
+ end
2109
+
2110
+ def association_join
2111
+ return @join if @join
2112
+
2113
+ aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine)
2114
+ parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name, :engine => arel_engine)
2115
+
2116
+ @join = case reflection.macro
2117
+ when :has_and_belongs_to_many
2118
+ join_table = Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine)
2119
+ fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key
2120
+ klass_fk = options[:association_foreign_key] || klass.to_s.foreign_key
2121
+
2122
+ [
2123
+ join_table[fk].eq(parent_table[reflection.active_record.primary_key]),
2124
+ aliased_table[klass.primary_key].eq(join_table[klass_fk])
2125
+ ]
2126
+ when :has_many, :has_one
2127
+ if reflection.options[:through]
2128
+ join_table = Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine)
2129
+ jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
2130
+ first_key = second_key = as_extra = nil
2131
+
2132
+ if through_reflection.options[:as] # has_many :through against a polymorphic join
2133
+ jt_foreign_key = through_reflection.options[:as].to_s + '_id'
2134
+ jt_as_extra = join_table[through_reflection.options[:as].to_s + '_type'].eq(parent.active_record.base_class.name)
2135
+ else
2136
+ jt_foreign_key = through_reflection.primary_key_name
2137
+ end
2138
+
2139
+ case source_reflection.macro
2140
+ when :has_many
2141
+ if source_reflection.options[:as]
2142
+ first_key = "#{source_reflection.options[:as]}_id"
2143
+ second_key = options[:foreign_key] || primary_key
2144
+ as_extra = aliased_table["#{source_reflection.options[:as]}_type"].eq(source_reflection.active_record.base_class.name)
2145
+ else
2146
+ first_key = through_reflection.klass.base_class.to_s.foreign_key
2147
+ second_key = options[:foreign_key] || primary_key
2148
+ end
2149
+
2150
+ unless through_reflection.klass.descends_from_active_record?
2151
+ jt_sti_extra = join_table[through_reflection.active_record.inheritance_column].eq(through_reflection.klass.sti_name)
2152
+ end
2153
+ when :belongs_to
2154
+ first_key = primary_key
2155
+ if reflection.options[:source_type]
2156
+ second_key = source_reflection.association_foreign_key
2157
+ jt_source_extra = join_table[reflection.source_reflection.options[:foreign_type]].eq(reflection.options[:source_type])
2158
+ else
2159
+ second_key = source_reflection.primary_key_name
2160
+ end
2161
+ end
2162
+
2163
+ [
2164
+ [parent_table[parent.primary_key].eq(join_table[jt_foreign_key]), jt_as_extra, jt_source_extra, jt_sti_extra].reject{|x| x.blank? },
2165
+ aliased_table[first_key].eq(join_table[second_key])
2166
+ ]
2167
+ elsif reflection.options[:as]
2168
+ id_rel = aliased_table["#{reflection.options[:as]}_id"].eq(parent_table[parent.primary_key])
2169
+ type_rel = aliased_table["#{reflection.options[:as]}_type"].eq(parent.active_record.base_class.name)
2170
+ [id_rel, type_rel]
2171
+ else
2172
+ foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
2173
+ [aliased_table[foreign_key].eq(parent_table[reflection.options[:primary_key] || parent.primary_key])]
2174
+ end
2175
+ when :belongs_to
2176
+ [aliased_table[options[:primary_key] || reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name])]
2177
+ end
2178
+
2179
+ unless klass.descends_from_active_record?
2180
+ sti_column = aliased_table[klass.inheritance_column]
2181
+ sti_condition = sti_column.eq(klass.sti_name)
2182
+ klass.descendants.each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
2183
+
2184
+ @join << sti_condition
2185
+ end
2186
+
2187
+ [through_reflection, reflection].each do |ref|
2188
+ if ref && ref.options[:conditions]
2189
+ @join << interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))
2190
+ end
2191
+ end
2192
+
2193
+ @join
2194
+ end
2195
+
2196
+ def relation
2197
+ aliased = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine)
2198
+
2199
+ if reflection.macro == :has_and_belongs_to_many
2200
+ [Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine), aliased]
2201
+ elsif reflection.options[:through]
2202
+ [Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine), aliased]
2203
+ else
2204
+ aliased
2205
+ end
2206
+ end
2207
+
2208
+ def join_relation(joining_relation, join = nil)
2209
+ joining_relation.joins(self.with_join_class(Arel::OuterJoin))
2210
+ end
2211
+
2212
+ protected
2213
+
2214
+ def aliased_table_name_for(name, suffix = nil)
2215
+ if @join_dependency.table_aliases[name].zero?
2216
+ @join_dependency.table_aliases[name] = @join_dependency.count_aliases_from_table_joins(name)
2217
+ end
2218
+
2219
+ if !@join_dependency.table_aliases[name].zero? # We need an alias
2220
+ name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
2221
+ @join_dependency.table_aliases[name] += 1
2222
+ if @join_dependency.table_aliases[name] == 1 # First time we've seen this name
2223
+ # Also need to count the aliases from the table_aliases to avoid incorrect count
2224
+ @join_dependency.table_aliases[name] += @join_dependency.count_aliases_from_table_joins(name)
2225
+ end
2226
+ table_index = @join_dependency.table_aliases[name]
2227
+ name = name[0..active_record.connection.table_alias_length-3] + "_#{table_index}" if table_index > 1
2228
+ else
2229
+ @join_dependency.table_aliases[name] += 1
2230
+ end
2231
+
2232
+ name
2233
+ end
2234
+
2235
+ def pluralize(table_name)
2236
+ ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
2237
+ end
2238
+
2239
+ def table_alias_for(table_name, table_alias)
2240
+ "#{table_name} #{table_alias if table_name != table_alias}".strip
2241
+ end
2242
+
2243
+ def table_name_and_alias
2244
+ table_alias_for table_name, @aliased_table_name
2245
+ end
2246
+
2247
+ def interpolate_sql(sql)
2248
+ instance_eval("%@#{sql.gsub('@', '\@')}@", __FILE__, __LINE__)
2249
+ end
2250
+ end
533
2251
  end
534
2252
  end
535
2253
  end
536
- end
2254
+ end