User/Micropost associations

為 web APP 建立資料模型(data models)時,最基本的需求就是能夠在不同的模型之間建立關聯(associations)。在我們的例子中,每一個 micropost 會屬於某個使用者,而每個使用者則會擁有多篇 microposts,在實現這些關聯的過程中,我們會先為 Micropost model 和 User model 撰寫測試。

microposts 和所屬使用者之間的 belongs_to (屬於)關係

使用者和 microposts 之間的 has_many (擁有多個)關係

使用本節實現的 belongs_to/has_many 關聯後,Rails 會自動建立一些方法,如下表:

Method Purpose
micropost.user Returns the User object associated with the micropost
user.microposts Returns a collection of the user’s microposts
user.microposts.create(arg) Creates a micropost associated with user
user.microposts.create!(arg) Creates a micropost associated with user (exception on failure)
user.microposts.build(arg) Returns a new Micropost object associated with user
user.microposts.find_by(id: 1) Finds the micropost with id 1 and user_id equal to user.id

注意,從表中得知,相較於下面這些方法:

Micropost.create
Micropost.create!
Micropost.new

我們得到了:

user.microposts.create
user.microposts.create!
user.microposts.build

後者才是建立 micropost 的正確方式,即透過關聯的使用者建立。透過這種方式建立的 micropost,user_id 會自動設為正確的值,所以我們可以把以下程式碼:

@user = users(:michael)
# This code is not idiomatically correct.
@micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)

改成:

@user = users(:michael)
@micropost = @user.microposts.build(content: "Lorem ipsum")

new 方法一樣,build 會回傳一個儲存在記憶體內的物件,不會修改資料庫。一旦我們定義了正確的關聯,@micropost 變數就會自動把 user_id 屬性設成與之關聯的 user id。

為了讓 @user.microposts.build 能夠運作,我們需要更新 User model 和 Micropost model 的程式碼,讓它們產生關聯。其中一個在建立遷移時已經自動幫我們產生,也就是 belongs_to :user,還有一個 has_many :microposts 必須手動增加:

app/models/micropost.rb

class Micropost < ActiveRecord::Base
  belongs_to :user
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end

app/models/user.rb

class User < ActiveRecord::Base
  has_many :microposts
  .
  .
  .
end

建立兩個資料模型之間的關聯後,我們就可以修改測試裡的 setup 方法:

test/models/micropost_test.rb

require 'test_helper'

class MicropostTest < ActiveSupport::TestCase

  def setup
    @user = users(:michael)
    @micropost = @user.microposts.build(content: "Lorem ipsum")
  end

  test "should be valid" do
    assert @micropost.valid?
  end

  test "user id should be present" do
    @micropost.user_id = nil
    assert_not @micropost.valid?
  end
  .
  .
  .
end

當然,經過微小的重構測試之後,現在應該會通過(Green):

$ bundle exec rake test