method

has_one

has_one(association_id, options = {})
public

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?.

  • 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, sets it as the foreign key, and saves the associate object.
  • association.nil? - Returns true if there is no associated object.
  • build_association(attributes = {}) - Returns a new object of the associated type that has been instantiated with attributes and linked to this object through a foreign key, but has not yet been saved. Note: This ONLY works if an association already exists. It will NOT work if the association is nil.
  • create_association(attributes = {}) - Returns a new object of the associated type that has been instantiated with attributes, linked to this object through a foreign key, and that has already been saved (if it passed the validation).

Example: An Account class declares has_one :beneficiary, which will add:

  • Account#beneficiary (similar to Beneficiary.find(:first, :conditions => "account_id = #{id}"))
  • Account#beneficiary=(beneficiary) (similar to beneficiary.account_id = account.id; beneficiary.save)
  • Account#beneficiary.nil?
  • Account#build_beneficiary (similar to Beneficiary.new("account_id" => id))
  • Account#create_beneficiary (similar to b = Beneficiary.new("account_id" => id); b.save; b)

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. So has_one :manager will by default be linked to the Manager 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 a WHERE SQL fragment, such as rank = 5.
  • :order - Specify the order in which the associated objects are returned as an ORDER BY SQL fragment, such as last_name, first_name DESC.
  • :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. If set to :nullify, the associated object’s foreign key is set to NULL. Also, association is assigned.
  • :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 a has_one association will use "person_id" as the default :foreign_key.
  • :include - Specify second-order associations that should be eager loaded when this object is loaded.
  • :as - Specifies a polymorphic interface (See belongs_to).
  • :select - By default, this is * as in SELECT * 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.
  • :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 a has_one or belongs_to association on the join model.
  • :source - Specifies the source association name used by has_one :through queries. Only use it if the name cannot be inferred from the association. has_one :favorite, :through => :favorites will look for a :favorite on Favorite, unless a :source is given.
  • :source_type - Specifies type of the source association used by has_one :through queries where the source association is a polymorphic belongs_to.
  • :readonly - If true, the associated object is readonly through the association.

Option examples:

  has_one :credit_card, :dependent => :destroy  # destroys the associated credit card
  has_one :credit_card, :dependent => :nullify  # updates the associated records foreign key value to NULL rather than destroying it
  has_one :last_comment, :class_name => "Comment", :order => "posted_on"
  has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
  has_one :attachment, :as => :attachable
  has_one :boss, :readonly => :true
  has_one :club, :through => :membership
  has_one :primary_address, :through => :addressables, :conditions => ["addressable.primary = ?", true], :source => :addressable

3Notes

Support for the option through

RobinWu · Sep 25, 20082 thanks

=== class Magazine < ActiveRecord::Base has_many :subscriptions end

class Subscription < ActiveRecord::Base
belongs_to :magazine
belongs_to :user
end

class User < ActiveRecord::Base
has_many :subscriptions
has_one :magazine, :through => :subscriptions, :conditions => ['subscriptions.active = ?', true]
end

build_association deletes existing dependent record

wsmith67 · Oct 14, 20092 thanks

Surprisingly (at least I was surprised), when an associated record exists, the build_association method immediately NULLs the foreign key in the database.

So if you write a singleton "new" action for the association in the obvious way (calling build_association), then just visiting the page will disconnect an existing associated record. This violates the principle that a GET request shouldn't affect the database.

To avoid this, you can check for an existing association first, and redirect to the show action.

has_one through belongs_to not working

THAiSi · Apr 27, 2009

code example:

class Company < ActiveRecord::Base
has_many :route_lists
end

class RouteList < ActiveRecord::Base
belongs_to :company
has_many :routes
end

class Route < ActiveRecord::Base
belongs_to :route_list
has_one :company :through => :route_list
end

This creates an invalid SQL query, where the keys in the join between route and routelist are switched, when used as an include:

Routes.find :all, :conditions => ["companies.type = ?", "Account"], :include => :company

route_lists.route_list_id = route.id

instead of: route_lists.id = route.route_list_id