Rails Guides 是 Rails 官方提供的線上教學文件,還蠻基礎的。

1到9章包含了 Migration, Model, View, Controller, Routes 等寫一個 Rails app 最基本的部份,摸索過 Rails 一段時間的人應該多少都能做出一個簡單的 Blog 或論壇,畢竟還有 scaffold 可以參考。那麼為什麼還要讀 Rails Guides 呢?

就像是節奏型鼓手,反正學會這3個節奏就可以打很多POP歌了。但很快就會遇到瓶頸,倒頭來還是得用打點練習重新打基礎,反而還得克服之前養成的壞習慣。

hh |x-x-x-x-x-x-x-x-|x-x-x-x-x-x-x-x-|x-x-x-x-x-x-x-x-|
S  |----o-------o---|----o-------o---|----o-------o---|
B  |o-------o-------|o-------o-o-----|o-----o-o-------|

小弟拖了很久才終於狠下心開始讀 Rails Guides,這邊整理我讀到的、值得注意的部分,以及我之前不知道的手法。這系列適合的對象是 Rails 學一小段時間、已經能做出簡單的 Blog、論壇的人,或者雖然使用 Rails 很久了、但一直都隨便亂寫的人 (例如我XD)。

另外,想開始讀 Rails Guides 的人,如果有 Kindle、或有在用 Kindle 手機版的話,官方有提供 .mobi 檔可抓。每天通勤、等人的時候就可以讀。

Part 1 包含 2~4 章的筆記:

Getting Started with Rails

(無筆記)

Rails Database Migrations

無法回溯的 Migration

來自 3.5 Using the up/down Methods

有時候有些 Migration 就是無法回溯,加上還有其他考量、打死都不准許回溯的話,此時可以在 down method 裡寫:

raise ActiveRecord::IrreversibleMigration

要回溯還是要命?

在 migration 內使用 model 時,跳過 validation 的方法

來自 5 Using Models in Your Migrations

在 migration 內有使用 model 存取 attributes 的時候,在別台機器上可能會遇到因為 validator 已經宣告了、但 table 欄位實際上還不存在,所以炸掉了。此時可以在 migration 內宣告一個空的 model 來 override 掉真正的。

需注意的是,在開始之前最好還是呼叫 Model.reset_column_information 清掉 cache,否則可能會有問題。詳細使用方式請參考來源。

Active Record Validations and Callbacks

valid? and invalid?

來自 2.4 valid? and invalid?

這兩個 methods 的回傳值剛好相反(如名稱所示)。

# app/models/person.rb
class Person < ActiveRecord::Base
    validates :name, :presence => true
end
p = Person.new
p.errors.messages # => {}

即使 model 裡有寫 validations,此時因為還沒有執行檢查,所以不會有錯誤(p.errors.messages 裡也不會有東西)。 .valid?.invalid? 可以觸發 validations。

p.valid? # => false
p.errors.messages # => {:name=>["can't be blank"]}

自定 validates 的子檢查

來自 6.1 Custom Validators

如果想實作這樣的自定 validation (在此例是 :email => true

# app/models/person.rb
class Person < ActiveRecord::Base
  validates :email, :presence => true, :email => true
end

可繼承 ActiveModel::EachValidator 來實作,詳情請見來源。

取得 errors 的完整句子的訊息

來自 7.3 errors.add

直接印出 errors.messages 會得到類似這樣的結果

1.9.3-p392 :001 > p.errors.messages
 => {:name=>["can't be blank"]}

用 errors.full_messages 或 errors.to_a 可以取得完整句子的訊息

1.9.3-p392 :002 > p.errors.to_a
 => ["Name can't be blank"]
1.9.3-p392 :003 > p.errors.full_messages
 => ["Name can't be blank"]

transaction 內停止執行並 rollback

來自 13 Halting Execution

下列狀況會停止 transaction 並 rollback

  • before callback 回傳 false 或丟 Exception
  • after callback 丟 Exception

需注意的是,Rails Guides 提到直接丟一般的 Exception 會中斷執行,可能導致其他預料外的問題,Guides 是推薦丟 ActiveRecord::Rollback exception,不會 re-raise,所以不用再包一層 begin … rescue。

(這邊我不是很確定會發生什麼樣的問題,還請大大指點)

Active Record Associations

inverse_of

來自 3.5 Bi-directional Associations

使用 inverse_of 在某些情境下可以避免 inconsistencies,以及增進效率(因為是同一個 object)

class Customer < ActiveRecord::Base
  has_many :orders, :inverse_of => :customer
end
class Order < ActiveRecord::Base
  belongs_to :customer, :inverse_of => :orders
end
c = Customer.first
o = c.orders.first
oc = o.customer
c.__id__  # => 70099731440980
oc.__id__ # => 70099731440980 # 相同

如果沒有 inverse_of

class Customer < ActiveRecord::Base
  has_many :orders
end
class Order < ActiveRecord::Base
  belongs_to :customer
end
c = Customer.first
o = c.orders.first
oc = o.customer
c.__id__  # => 70297963786100
oc.__id__ # => 70297971379700 # 不同

但是有些限制:不能搭配 :through:polymorphic:as 使用 and For belongs_to associations, has_many inverse associations are ignored.

build_association 與 create_association

來自 4.1.1 Methods Added by belongs_to

association 請取代成關連的名稱,使用範例如下:

@customer = @order.build_customer(:customer_number => 123, :customer_name => "John Doe")
@customer = @order.create_customer(:customer_number => 123, :customer_name => "John Doe")

總之就是關連物件的 newcreate 用法,還會自動幫填 foreign key。

另外 has_many 那方也有類似的功能,也是會自動幫填 foreign key。

@order = @customer.orders.build(...)
@order = @customer.orders.create(...)

counter_cache

來自 4.1.2 Options for belongs_to

功能如其名,就是關連項目的 count cache,設定好後會用在這裡:

@customer.orders.size

count 仍然會下 select count(*) ...

@customer.orders.count

設定方式請參考來源,不過 Rails Guides 沒有提到一件事:如果是先有資料才補 counter_cache 欄位的話,在 migration 內你應該要給予現有資料正確的 count,但用一般的寫法會炸 Exception,要用 reset_counters 才對。

dependent

來自 4.1.2.5 :dependent

簡單來說就是類似 on delete cascade 的效果。

class Customer < ActiveRecord::Base
  has_many :orders, :dependent => :destroy
end

當 customer instance 被 destroy 的時候,關連的 orders 也都會被 destroy。

除了 :delete,也可以設 :nullify (foreign key 填 null)。

另外請勿用在 (belongs_to && 對面是 has_many) 的狀況,會導致孤兒 records。

加上 :include 增進效率、以及不必加的狀況

來自 4.1.2.7 :include

  1. 官方的範例寫得蠻好的,所以就直接過去看吧。
  2. 兩層才需要手動加 :include,只有一層關連的話會自動做。

關連物件何時儲存

整理自 4 Detailed Association Reference 下四個 When are Objects Saved? 子項目

Relation When are Objects Saved?
belongs_to Assigning an object to a belongs_to association does not automatically save the object. It does not save the associated object either.
has_one When you assign an object to a has_one association, that object is automatically saved (in order to update its foreign key).
has_many When you assign an object to a has_many association, that object is automatically saved (in order to update its foreign key). If you assign multiple objects in one statement, then they are all saved.
has_and_belongs_to_many When you assign an object to a has_and_belongs_to_many association, that object is automatically saved (in order to update the join table). If you assign multiple objects in one statement, then they are all saved.

xxxxx_ids 與 xxxxx_ids=

來自 4.3.1 Methods Added by has_many

除了熟悉的:

@customer.orders
@customer.orders = @orders

有時候你只有 ids,要撈出 objects 反而多一道手續,可以用:

@customer.order_ids  # => [1, 2, 3, ...]
@customer.order_ids = [1, 2, 3]

exists?

來自 4.3.1 Methods Added by has_many

有時候你只是想知道是否有存在符合條件的 object,我之前是這樣寫

@customer.where(:name => "John").present?

但其實可以這樣寫

@customer.exists?(:name => "John")

當資料量稍大以後,後者效能也明顯較好:

用 156,046 筆資料實驗,結果用 where(…).present? 要 121.2ms,用 exists? 只要 0.3ms。

關連物件加 :conditions

來自 4.3.2 Options for has_many > 4.3.2.4 :conditions

# 如果 model 長這樣..
class Customer < ActiveRecord::Base
  has_many :confirmed_orders, :class_name => "Order", :conditions => { :confirmed => true }
end

# 那麼下面這行只會撈出符合 :confirmed => true 的關聯物件
@customer.confirmed_orders

# 下面這行則會自動塞 :confirmed => true
@customer.confirmed_orders.create

另外也接受字串或 Proc 的條件限制,但不支援自動塞值。

改寫預設會下的 SQL

來自 4.3.2 Options for has_many > 4.3.2.5 :counter_sql4.3.2.8 :finder_sql

可以改寫預設會下的 SQL 嗎?可以,請參考來源。除了這兩個外其實還有很多其他的自定選項。

來自 4.3.2 Options for has_many > 來源 4.3.2.15 :order

關連物件預設的排序方式

class Customer < ActiveRecord::Base
  has_many :orders, :order => "date_confirmed DESC"
end

這樣子從關連取得的結果就會預設以 date_confirmed DESC 排序。

before_add 與 after_add 等 callback

來自 4.5 Association Callbacks

有這些 callback 可以用(功能如名字所示):

  • before_add
  • after_add
  • before_remove
  • after_remove

使用範例

class Customer < ActiveRecord::Base
  has_many :orders, :before_add => :check_credit_limit

  def check_credit_limit(order)
    ...
  end
end

寫自定的 find_by_xxx

來自 4.6 Association Extensions

來源的範例寫得很好,也很難再簡潔了,所以這邊就不重貼一次,請直接參考來源。

目前我閱讀到第9章,之後的筆記請稍待我整理完後貼出。