| Module | ActiveRecord::Associations::ClassMethods |
| In: |
vendor/activerecord/lib/active_record/associations.rb
vendor/activerecord/lib/active_record/deprecated_associations.rb |
Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are specialized according to the collection or association symbol and the options hash. It works much the same was as Ruby’s own attr* methods. Example:
class Project < ActiveRecord::Base
belongs_to :portfolio
has_one :project_manager
has_many :milestones
has_and_belongs_to_many :categories
end
The project class now has the following methods (and more) to ease the traversal and manipulation of its relationships:
Both express a 1-1 relationship, the difference is mostly where to place the foreign key, which goes on the table for the class saying belongs_to. Example:
class Post < ActiveRecord::Base
has_one :author
end
class Author < ActiveRecord::Base
belongs_to :post
end
The tables for these classes could look something like:
CREATE TABLE posts (
id int(11) NOT NULL auto_increment,
title varchar default NULL,
PRIMARY KEY (id)
)
CREATE TABLE authors (
id int(11) NOT NULL auto_increment,
post_id int(11) default NULL,
name varchar default NULL,
PRIMARY KEY (id)
)
You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be aware of, mostly involving the saving of associated objects.
All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without worrying too much about performance at the first go. Example:
project.milestones # fetches milestones from the database project.milestones.size # uses the milestone cache project.milestones.empty? # uses the milestone cache project.milestones(true).size # fetches milestones from the database project.milestones # uses the milestone cache
By default, associations will look for objects within the current module scope. Consider:
module MyApplication
module Business
class Firm < ActiveRecord::Base
has_many :clients
end
class Company < ActiveRecord::Base; end
end
end
When Firm#clients is called, it’ll in turn call MyApplication::Business::Company.find(firm.id). If you want to associate with a class in another module scope this can be done by specifying the complete class name, such as:
module MyApplication
module Business
class Firm < ActiveRecord::Base; end
end
module Billing
class Account < ActiveRecord::Base
belongs_to :firm, :class_name => "MyApplication::Business::Firm"
end
end
end
If you attempt to assign an object to an association that doesn’t match the inferred or specified :class_name, you’ll get a ActiveRecord::AssociationTypeMismatch.
All of the association macros can be specialized through options which makes more complex cases than the simple and guessable ones possible.
Adds the following methods for retrieval and query for a single associated object that this object holds an id to. association is replaced with the symbol passed as the first argument, so belongs_to :author would add among others author.nil?.
Example: A Post class declares belongs_to :author, which will add:
The declaration can also include an options hash to specialize the behavior of the association.
Options are:
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}'
# File vendor/activerecord/lib/active_record/associations.rb, line 355
355: def belongs_to(association_id, options = {})
356: validate_options([ :class_name, :foreign_key, :remote, :conditions, :order, :dependent, :counter_cache ], options.keys)
357:
358: association_name, association_class_name, class_primary_key_name =
359: associate_identification(association_id, options[:class_name], options[:foreign_key], false)
360:
361: require_association_class(association_class_name)
362:
363: association_class_primary_key_name = options[:foreign_key] || Inflector.underscore(Inflector.demodulize(association_class_name)) + "_id"
364:
365: association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, BelongsToAssociation)
366:
367: module_eval do
368: before_save "association = instance_variable_get(\"@\#{association_name}\")\nif not association.nil? and association.new_record?\nassociation.save(true)\nself[\"\#{association_class_primary_key_name}\"] = association.id\nassociation.send(:construct_sql)\nend\n"
369: end
370:
371: if options[:counter_cache]
372: module_eval(
373: "after_create '#{association_class_name}.increment_counter(\"#{Inflector.pluralize(self.to_s.downcase). + "_count"}\", #{association_class_primary_key_name})" +
374: " unless #{association_name}.nil?'"
375: )
376:
377: module_eval(
378: "before_destroy '#{association_class_name}.decrement_counter(\"#{Inflector.pluralize(self.to_s.downcase) + "_count"}\", #{association_class_primary_key_name})" +
379: " unless #{association_name}.nil?'"
380: )
381: end
382:
383: # deprecated api
384: deprecated_has_association_method(association_name)
385: deprecated_association_comparison_method(association_name, association_class_name)
386: end
Associates two classes via an intermediate join table. Unless the join table is explicitly specified as an option, it is guessed using the lexical order of the class names. So a join between Developer and Project will give the default join table name of "developers_projects" because "D" outranks "P".
Any additional fields added to the join table will be placed as attributes when pulling records out through has_and_belongs_to_many associations. This is helpful when have information about the association itself that you want available on retrieval. Note that any fields in the join table will override matching field names in the two joined tables. As a consequence, having an "id" field in the join table usually has the undesirable result of clobbering the "id" fields in either of the other two tables.
Adds the following methods for retrieval and query. collection is replaced with the symbol passed as the first argument, so has_and_belongs_to_many :categories would add among others categories.empty?.
Example: An Developer class declares has_and_belongs_to_many :projects, which will add:
The declaration may include an options hash to specialize the behavior of the association.
Options are:
Option examples:
has_and_belongs_to_many :projects has_and_belongs_to_many :nations, :class_name => "Country" has_and_belongs_to_many :categories, :join_table => "prods_cats"
# File vendor/activerecord/lib/active_record/associations.rb, line 464
464: def has_and_belongs_to_many(association_id, options = {})
465: validate_options([ :class_name, :table_name, :foreign_key, :association_foreign_key, :conditions,
466: :join_table, :finder_sql, :delete_sql, :insert_sql, :order, :uniq ], options.keys)
467: association_name, association_class_name, association_class_primary_key_name =
468: associate_identification(association_id, options[:class_name], options[:foreign_key])
469:
470: require_association_class(association_class_name)
471:
472: options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(association_class_name))
473:
474: add_multiple_associated_save_callbacks(association_name)
475:
476: collection_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasAndBelongsToManyAssociation)
477:
478: before_destroy_sql = "DELETE FROM #{options[:join_table]} WHERE #{association_class_primary_key_name} = \\\#{self.quoted_id}"
479: module_eval(%{before_destroy "self.connection.delete(%{#{before_destroy_sql}})"}) # "
480:
481: # deprecated api
482: deprecated_collection_count_method(association_name)
483: deprecated_add_association_relation(association_name)
484: deprecated_remove_association_relation(association_name)
485: deprecated_has_collection_method(association_name)
486: end
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?.
Example: A Firm class declares has_many :clients, which will add:
The declaration can also include an options hash to specialize the behavior of the association.
Options are:
Option examples:
has_many :comments, :order => "posted_on"
has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"
has_many :tracks, :order => "position", :dependent => true
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'
# File vendor/activerecord/lib/active_record/associations.rb, line 213
213: def has_many(association_id, options = {})
214: validate_options([ :foreign_key, :class_name, :exclusively_dependent, :dependent, :conditions, :order, :finder_sql, :counter_sql ], options.keys)
215: association_name, association_class_name, association_class_primary_key_name =
216: associate_identification(association_id, options[:class_name], options[:foreign_key])
217:
218: require_association_class(association_class_name)
219:
220: if options[:dependent] and options[:exclusively_dependent]
221: raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options. You may specify one or the other.' # ' ruby-mode
222: elsif options[:dependent]
223: module_eval "before_destroy '#{association_name}.each { |o| o.destroy }'"
224: elsif options[:exclusively_dependent]
225: module_eval "before_destroy { |record| #{association_class_name}.delete_all(%(#{association_class_primary_key_name} = \#{record.quoted_id})) }"
226: end
227:
228: add_multiple_associated_save_callbacks(association_name)
229:
230: collection_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasManyAssociation)
231:
232: # deprecated api
233: deprecated_collection_count_method(association_name)
234: deprecated_add_association_relation(association_name)
235: deprecated_remove_association_relation(association_name)
236: deprecated_has_collection_method(association_name)
237: deprecated_find_in_collection_method(association_name)
238: deprecated_find_all_in_collection_method(association_name)
239: deprecated_collection_create_method(association_name)
240: deprecated_collection_build_method(association_name)
241: end
Adds the following methods for retrieval and query of a single associated object. association is replaced with the symbol passed as the first argument, so has_one :manager would add among others manager.nil?.
Example: An Account class declares has_one :beneficiary, which will add:
The declaration can also include an options hash to specialize the behavior of the association.
Options are:
an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
Option examples:
has_one :credit_card, :dependent => true has_one :last_comment, :class_name => "Comment", :order => "posted_on" has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
# File vendor/activerecord/lib/active_record/associations.rb, line 283
283: def has_one(association_id, options = {})
284: validate_options([ :class_name, :foreign_key, :remote, :conditions, :order, :dependent, :counter_cache ], options.keys)
285:
286: association_name, association_class_name, association_class_primary_key_name =
287: associate_identification(association_id, options[:class_name], options[:foreign_key], false)
288:
289: require_association_class(association_class_name)
290:
291: module_eval do
292: after_save "association = instance_variable_get(\"@\#{association_name}\")\nunless association.nil?\nassociation[\"\#{association_class_primary_key_name}\"] = id\nassociation.save(true)\nassociation.send(:construct_sql)\nend\n"
293: end
294:
295: association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasOneAssociation)
296:
297: module_eval "before_destroy '#{association_name}.destroy unless #{association_name}.nil?'" if options[:dependent]
298:
299: # deprecated api
300: deprecated_has_association_method(association_name)
301: deprecated_build_method("build_", association_name, association_class_name, association_class_primary_key_name)
302: deprecated_create_method("create_", association_name, association_class_name, association_class_primary_key_name)
303: deprecated_association_comparison_method(association_name, association_class_name)
304: end