Module: ActiveRecord::Associations::ClassMethods
- Defined in:
- lib/activerecord/lib/active_record/associations.rb
Instance Method Summary collapse
- #add_cache_callbacks ⇒ Object
-
#belongs_to(association_id, options = {}) ⇒ Object
Adds the following methods for retrieval and query for a single associated object for which this object holds an id:
association
is replaced with the symbol passed as the first argument, sobelongs_to :author
would add among othersauthor.nil?
. - #collection_accessor_methods(reflection, association_proxy_class, options, writer = true) ⇒ Object
- #collection_reader_method(reflection, association_proxy_class, options) ⇒ Object
- #create_belongs_to_reflection(association_id, options) ⇒ Object
- #create_has_many_reflection(association_id, options, &extension) ⇒ Object
- #has_and_belongs_to_many(association_id, options = {}, &extension) ⇒ Object
-
#has_many(association_id, options = {}, &extension) ⇒ Object
Adds the following methods for retrieval and query of collections of associated objects:
collection
is replaced with the symbol passed as the first argument, sohas_many :clients
would add among othersclients.empty?
.
Instance Method Details
#add_cache_callbacks ⇒ Object
389 390 391 392 393 394 395 396 397 398 399 400 401 |
# File 'lib/activerecord/lib/active_record/associations.rb', line 389 def add_cache_callbacks method_name = :after_save_cache_expire return if respond_to? method_name define_method(method_name) do return unless self[:updated_at] self.class.reflections.each do |name, reflection| cache_delete(reflection) if reflection.[:cached] end end after_save method_name end |
#belongs_to(association_id, options = {}) ⇒ Object
Adds the following methods for retrieval and query for a single associated object for which this object holds an id: association
is replaced with the symbol passed as the first argument, so belongs_to :author
would add among others author.nil?
.
-
association(force_reload = false)
- Returns the associated object.nil
is returned if none is found. -
association=(associate)
- Assigns the associate object, extracts the primary key, and sets it as the foreign key. -
association.nil?
- Returnstrue
if there is no associated object. -
build_association(attributes = {})
- Returns a new object of the associated type that has been instantiated withattributes
and linked to this object through a foreign key, but has not yet been saved. -
create_association(attributes = {})
- Returns a new object of the associated type that has been instantiated withattributes
, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
Example: A Post class declares belongs_to :author
, which will add:
-
Post#author
(similar toAuthor.find(author_id)
) -
Post#author=(author)
(similar topost.author_id = author.id
) -
Post#author?
(similar topost.author == some_author
) -
Post#author.nil?
-
Post#build_author
(similar topost.author = Author.new
) -
Post#create_author
(similar topost.author = Author.new; post.author.save; post.author
)
The declaration can also include an options hash to specialize the behavior of the association.
Options are:
-
:class_name
- Specify the class name of the association. Use it only if that name can’t be inferred from the association name. Sohas_one :author
will by default be linked to the Author class, but if the real class name is Person, you’ll have to specify it with this option. -
:conditions
- Specify the conditions that the associated object must meet in order to be included as aWHERE
SQL fragment, such asauthorized = 1
. -
:select
- By default, this is*
as inSELECT * FROM
, but can be changed if, for example, you want to do a join but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error. -
:foreign_key
- Specify the foreign key used for the association. By default this is guessed to be the name of the association with an “_id” suffix. So a class that defines abelongs_to :person
association will use “person_id” as the default:foreign_key
. Similarly,belongs_to :favorite_person, :class_name => "Person"
will use a foreign key of “favorite_person_id”. -
:dependent
- If set to:destroy
, the associated object is destroyed when this object is. If set to:delete
, the associated object is deleted without calling its destroy method. This option should not be specified whenbelongs_to
is used in conjunction with ahas_many
relationship on another class because of the potential to leave orphaned records behind. -
:counter_cache
- Caches the number of belonging objects on the associate class through the use ofincrement_counter
anddecrement_counter
. The counter cache is incremented when an object of this class is created and decremented when it’s destroyed. This requires that a column named#{table_name}_count
(such ascomments_count
for a belonging Comment class) is used on the associate class (such as a Post class). You can also specify a custom counter cache column by providing a column name instead of atrue
/false
value to this option (e.g.,:counter_cache => :my_custom_counter
.) When creating a counter cache column, the database statement or migration must specify a default value of0
, failing to do this results in a counter withNULL
value, which will never increment. Note: Specifying a counter cache will add it to that model’s list of readonly attributes usingattr_readonly
. -
:include
- Specify second-order associations that should be eager loaded when this object is loaded. -
:polymorphic
- Specify this association is a polymorphic association by passingtrue
. Note: If you’ve enabled the counter cache, then you may want to add the counter cache attribute to theattr_readonly
list in the associated classes (e.g.class Post; attr_readonly :comments_count; end
). -
:readonly
- If true, the associated object is readonly through the association. -
:validate
- If false, don’t validate the associated objects when saving the parent object.false
by default.
Option examples:
belongs_to :firm, :foreign_key => "client_of"
belongs_to :author, :class_name => "Person", :foreign_key => "author_id"
belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
:conditions => 'discounts > #{payments_count}'
belongs_to :attachable, :polymorphic => true
belongs_to :project, :readonly => true
belongs_to :post, :counter_cache => true
belongs_to :blog, :cached => true
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 |
# File 'lib/activerecord/lib/active_record/associations.rb', line 182 def belongs_to(association_id, = {}) reflection = create_belongs_to_reflection(association_id, ) ivar = "@#{reflection.name}" if reflection.[:polymorphic] association_accessor_methods(reflection, BelongsToPolymorphicAssociation) method_name = "polymorphic_belongs_to_before_save_for_#{reflection.name}".to_sym define_method(method_name) do association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}") if association && association.target if association.new_record? association.save(true) end if association.updated? self["#{reflection.primary_key_name}"] = association.id self["#{reflection.[:foreign_type]}"] = association.class.base_class.name.to_s end end end before_save method_name else association_accessor_methods(reflection, BelongsToAssociation) association_constructor_method(:build, reflection, BelongsToAssociation) association_constructor_method(:create, reflection, BelongsToAssociation) method_name = "belongs_to_before_save_for_#{reflection.name}".to_sym define_method(method_name) do association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}") if !association.nil? if association.new_record? association.save(true) end if association.updated? self["#{reflection.primary_key_name}"] = association.id end end end before_save method_name end # Create the callbacks to update counter cache if [:counter_cache] cache_column = [:counter_cache] == true ? "#{self.to_s.underscore.pluralize}_count" : [:counter_cache] method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym define_method(method_name) do association = send("#{reflection.name}") association.class.increment_counter("#{cache_column}", send("#{reflection.primary_key_name}")) unless association.nil? end after_create method_name method_name = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".to_sym define_method(method_name) do association = send("#{reflection.name}") association.class.decrement_counter("#{cache_column}", send("#{reflection.primary_key_name}")) unless association.nil? end before_destroy method_name module_eval( "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)" ) end if [:cached] after_save_method_name = "belongs_to_after_save_for_#{reflection.name}".to_sym after_destroy_method_name = "belongs_to_after_destroy_for_#{reflection.name}".to_sym define_method(after_save_method_name) do send(reflection.name).expire_cache_for(self.class.name) end alias_method after_destroy_method_name, after_save_method_name after_save after_save_method_name after_destroy after_destroy_method_name end add_single_associated_validation_callbacks(reflection.name) if [:validate] == true configure_dependency_for_belongs_to(reflection) end |
#collection_accessor_methods(reflection, association_proxy_class, options, writer = true) ⇒ Object
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/activerecord/lib/active_record/associations.rb', line 334 def collection_accessor_methods(reflection, association_proxy_class, , writer = true) collection_reader_method(reflection, association_proxy_class, ) if writer define_method("#{reflection.name}=") do |new_value| # Loads proxy class instance (defined in collection_reader_method) if not already loaded association = send(reflection.name) association.replace(new_value) cache_write(reflection, association) if [:cached] association end define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| ids = (new_value || []).reject { |nid| nid.blank? } send("#{reflection.name}=", reflection.class_name.constantize.find(ids)) end end end |
#collection_reader_method(reflection, association_proxy_class, options) ⇒ Object
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/activerecord/lib/active_record/associations.rb', line 270 def collection_reader_method(reflection, association_proxy_class, ) define_method(reflection.name) do |*params| ivar = "@#{reflection.name}" force_reload = params.first unless params.empty? association = if [:cached] cache_read(reflection) else instance_variable_get(ivar) if instance_variable_defined?(ivar) end unless association.respond_to?(:loaded?) association = association_proxy_class.new(self, reflection) if [:cached] cache_write(reflection, association) else instance_variable_set(ivar, association) end end if force_reload association.reload cache_write(reflection, association) if [:cached] end association end method_name = "#{reflection.name.to_s.singularize}_ids" define_method(method_name) do if [:cached] cache_fetch("#{cache_key}/#{method_name}", send("calculate_#{method_name}")) else send("calculate_#{method_name}") end end define_method("calculate_#{method_name}") do send(reflection.name).map { |record| record.id } end end |
#create_belongs_to_reflection(association_id, options) ⇒ Object
374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
# File 'lib/activerecord/lib/active_record/associations.rb', line 374 def create_belongs_to_reflection(association_id, ) .assert_valid_keys( :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent, :counter_cache, :extend, :polymorphic, :readonly, :validate, :cached ) reflection = create_reflection(:belongs_to, association_id, , self) if [:polymorphic] reflection.[:foreign_type] ||= reflection.class_name.underscore + "_type" end reflection end |
#create_has_many_reflection(association_id, options, &extension) ⇒ Object
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 |
# File 'lib/activerecord/lib/active_record/associations.rb', line 355 def create_has_many_reflection(association_id, , &extension) .assert_valid_keys( :class_name, :table_name, :foreign_key, :primary_key, :dependent, :select, :conditions, :include, :order, :group, :limit, :offset, :as, :through, :source, :source_type, :uniq, :finder_sql, :counter_sql, :before_add, :after_add, :before_remove, :after_remove, :extend, :readonly, :validate, :accessible, :cached ) [:extend] = create_extension_modules(association_id, extension, [:extend]) create_reflection(:has_many, association_id, , self) end |
#has_and_belongs_to_many(association_id, options = {}, &extension) ⇒ Object
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
# File 'lib/activerecord/lib/active_record/associations.rb', line 313 def has_and_belongs_to_many(association_id, = {}, &extension) reflection = create_has_and_belongs_to_many_reflection(association_id, , &extension) add_multiple_associated_validation_callbacks(reflection.name) unless [:validate] == false add_multiple_associated_save_callbacks(reflection.name) collection_accessor_methods(reflection, HasAndBelongsToManyAssociation, ) # Don't use a before_destroy callback since users' before_destroy # callbacks will be executed after the association is wiped out. old_method = "destroy_without_habtm_shim_for_#{reflection.name}" class_eval <<-end_eval unless method_defined?(old_method) alias_method :#{old_method}, :destroy_without_callbacks def destroy_without_callbacks #{reflection.name}.clear #{old_method} end end_eval add_association_callbacks(reflection.name, ) end |
#has_many(association_id, options = {}, &extension) ⇒ Object
Adds the following methods for retrieval and query of collections of associated objects: collection
is replaced with the symbol passed as the first argument, so has_many :clients
would add among others clients.empty?
.
-
collection(force_reload = false)
- Returns an array of all the associated objects. An empty array is returned if none are found. -
collection<<(object, ...)
- Adds one or more objects to the collection by setting their foreign keys to the collection’s primary key. -
collection.delete(object, ...)
- Removes one or more objects from the collection by setting their foreign keys toNULL
. This will also destroy the objects if they’re declared asbelongs_to
and dependent on this model. -
collection=objects
- Replaces the collections content by deleting and adding objects as appropriate. -
collection_singular_ids
- Returns an array of the associated objects’ ids -
collection_singular_ids=ids
- Replace the collection with the objects identified by the primary keys inids
-
collection.clear
- Removes every object from the collection. This destroys the associated objects if they are associated with:dependent => :destroy
, deletes them directly from the database if:dependent => :delete_all
, otherwise sets their foreign keys toNULL
. -
collection.empty?
- Returnstrue
if there are no associated objects. -
collection.size
- Returns the number of associated objects. -
collection.find
- Finds an associated object according to the same rules as Base.find. -
collection.build(attributes = {}, ...)
- Returns one or more new objects of the collection type that have been instantiated withattributes
and linked to this object through a foreign key, but have not yet been saved. Note: This only works if an associated object already exists, not if it’snil
! -
collection.create(attributes = {})
- Returns a new object of the collection type that has been instantiated withattributes
, linked to this object through a foreign key, and that has already been saved (if it passed the validation). Note: This only works if an associated object already exists, not if it’snil
!
Example: A Firm class declares has_many :clients
, which will add:
-
Firm#clients
(similar toClients.find :all, :conditions => "firm_id = #{id}"
) -
Firm#clients<<
-
Firm#clients.delete
-
Firm#clients=
-
Firm#client_ids
-
Firm#client_ids=
-
Firm#clients.clear
-
Firm#clients.empty?
(similar tofirm.clients.size == 0
) -
Firm#clients.size
(similar toClient.count "firm_id = #{id}"
) -
Firm#clients.find
(similar toClient.find(id, :conditions => "firm_id = #{id}")
) -
Firm#clients.build
(similar toClient.new("firm_id" => id)
) -
Firm#clients.create
(similar toc = Client.new("firm_id" => id); c.save; c
)
The declaration can also include an options hash to specialize the behavior of the association.
Options are:
-
:class_name
- Specify the class name of the association. Use it only if that name can’t be inferred from the association name. Sohas_many :products
will by default be linked to the Product class, but if the real class name is SpecialProduct, you’ll have to specify it with this option. -
:conditions
- Specify the conditions that the associated objects must meet in order to be included as aWHERE
SQL fragment, such asprice > 5 AND name LIKE 'B%'
. Record creations from the association are scoped if a hash is used.has_many :posts, :conditions => {:published => true}
will create published posts with@blog.posts.create
or@blog.posts.build
. -
:order
- Specify the order in which the associated objects are returned as anORDER BY
SQL fragment, such aslast_name, first_name DESC
. -
:foreign_key
- Specify the foreign key used for the association. By default this is guessed to be the name of this class in lower-case and “_id” suffixed. So a Person class that makes ahas_many
association will use “person_id” as the default:foreign_key
. -
:dependent
- If set to:destroy
all the associated objects are destroyed alongside this object by calling theirdestroy
method. If set to:delete_all
all associated objects are deleted without calling theirdestroy
method. If set to:nullify
all associated objects’ foreign keys are set toNULL
without calling theirsave
callbacks. Warning: This option is ignored when also using the:through
option. -
:finder_sql
- Specify a complete SQL statement to fetch the association. This is a good way to go for complex associations that depend on multiple tables. Note: When this option is used,find_in_collection
is not added. -
:counter_sql
- Specify a complete SQL statement to fetch the size of the association. If:finder_sql
is specified but not:counter_sql
,:counter_sql
will be generated by replacingSELECT ... FROM
withSELECT COUNT(*) FROM
. -
:extend
- Specify a named module for extending the proxy. See “Association extensions”. -
:include
- Specify second-order associations that should be eager loaded when the collection is loaded. -
:group
- An attribute name by which the result should be grouped. Uses theGROUP BY
SQL-clause. -
:limit
- An integer determining the limit on the number of rows that should be returned. -
:offset
- An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. -
:select
- By default, this is*
as inSELECT * FROM
, but can be changed if you, for example, want to do a join but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will rise an error. -
:as
- Specifies a polymorphic interface (Seebelongs_to
). -
:through
- Specifies a Join Model through which to perform the query. Options for:class_name
and:foreign_key
are ignored, as the association uses the source reflection. You can only use a:through
query through abelongs_to
orhas_many
association on the join model. -
:source
- Specifies the source association name used byhas_many :through
queries. Only use it if the name cannot be inferred from the association.has_many :subscribers, :through => :subscriptions
will look for either:subscribers
or:subscriber
on Subscription, unless a:source
is given. -
:source_type
- Specifies type of the source association used byhas_many :through
queries where the source association is a polymorphicbelongs_to
. -
:uniq
- If true, duplicates will be omitted from the collection. Useful in conjunction with:through
. -
:readonly
- If true, all the associated objects are readonly through the association. -
:cached
- If true, all the associated objects will be cached.
Option examples:
has_many :comments, :order => "posted_on"
has_many :comments, :include => :author
has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"
has_many :tracks, :order => "position", :dependent => :destroy
has_many :comments, :dependent => :nullify
has_many :tags, :as => :taggable
has_many :reports, :readonly => true
has_many :posts, :cached => true
has_many :subscribers, :through => :subscriptions, :source => :user
has_many :subscribers, :class_name => "Person", :finder_sql =>
'SELECT DISTINCT people.* ' +
'FROM people p, post_subscriptions ps ' +
'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
'ORDER BY p.first_name'
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/activerecord/lib/active_record/associations.rb', line 105 def has_many(association_id, = {}, &extension) reflection = create_has_many_reflection(association_id, , &extension) configure_dependency_for_has_many(reflection) add_multiple_associated_save_callbacks(reflection.name) add_association_callbacks(reflection.name, reflection.) if [:through] collection_accessor_methods(reflection, HasManyThroughAssociation, ) else collection_accessor_methods(reflection, HasManyAssociation, ) end add_cache_callbacks if [:cached] end |