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