一對一關聯


在物件表格映射一對一關聯上,Rails提供了has_one與belongs_to兩個方法。如果有Some、Other兩個物件,若說Some擁有一個Other,可以如下定義:

class Some < ActiveRecord::Base
    has_one :other
end

則此時others表格中要有個some_id欄位作為外部鍵,參考至somes表格中的id主鍵。如果說Some屬於Other,可以如下定義:

class Some < ActiveRecord::Base
    belongs_to :other
end

則此時somes表格中要有個other_id欄位作為外部鍵,參考至others表格中的id主鍵。

技術上看來,使用has_one或belongs_to,主要在於外部鍵是放在哪個表格中,實際上還是要依語意而定,例如使用者與房間的關係,可以說使用者擁有一個房間,也可以說房間屬於某個使用者,但若說使用者屬於某個房間,或房間擁有某個使用者,語意上不免奇怪。

假設想要設計的是,使用者擁有一個房間。例如:

# app/models/user.rb
class User < ActiveRecord::Base
    has_one :room
end

# app/models/room.rb
class Room < ActiveRecord::Base
end

那麼可如下建立表格:

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name
      t.integer :age

      t.timestamps
    end
  end
end

class CreateRooms < ActiveRecord::Migration
  def change
    create_table :rooms do |t|
      t.string :address
      t.integer :user_id # 外鍵

      t.timestamps
    end
  end
end

User物件有個room屬性參考至Room實例,一個儲存的例子如下所示,只要儲存User,所參考的Room也會一併儲存

u = User.new(:name => "Justin", :age => 35)
r = Room.new(:address => "NTU-M8-419")
u.room = r
u.save

由於是由User參考了Room實例,所以儲存時使用User實例的save方法,如果使用的是Room來儲存,由於Room並沒有參考至User,就只會儲存Room而不會儲存User

如果想設計的是,房間屬於某個使用者:

# app/models/user.rb
class User < ActiveRecord::Base
end

# app/models/room.rb
class Room < ActiveRecord::Base
    belongs_to :user
end

Room物件有個user屬性會參考至User實例,儲存時要使用Room的save方法

u = User.new(:name => "Justin", :age => 35)
r = Room.new(:address => "NTU-M8-419", :user => u)
r.save

以上兩個例子,實際上都必須先儲存User的資料,才能根據users中的id設置rooms中的user_id,也就是都使用以下的SQL:

INSERT INTO "users" ("age", "created_at", "name", "updated_at") VALUES (?, ?, ?, ?)
INSERT INTO "rooms" ("address", "created_at", "updated_at", "user_id") VALUES (?, ?, ?, ?)

無論是User或Room使用find查詢時,預設被關聯的另一方暫時都不會查詢,直到取用屬性時才會再查詢一次。例如:

u = User.find(1) # 只會從users表格中SELECT
u.room # 此時才會從rooms表格中SELECT