Creating Easy, Readable Attributes With ActiveRecord Enums
設想一個問題的狀态可能為“暫停”,“通過”或“标注”。或者一個電話号碼可能是“家庭号碼”,“辦公号碼”,“手機号碼”或者“傳真号碼”(1982年的話)
有些子產品需要這種類型的資料:隻對應少許值的屬性,并且這些值幾乎永遠不會改變。
如果使用純Ruby的話,可以通過使用
symbol
來解決這個問題。
可以建立
PhoneNumberType
或者
QuestionStatus
子產品,并通過定義
belongs_to
關系來關聯這些值,但是這麼簡單的需求似乎并不值得這麼做,因為僅僅将這些值放在
yaml
檔案中一樣可以滿足需求。
現在我們來看一個解決這類需求的真正利器:在Rails4.1中引入的 ActiveRecord enums。
Model中的少量值
ActiveRecord enums用法很簡單,你可以為model建立一個整數類型的列:
bin/rails g model phone number:string phone_number_type:integer
列出這個屬性可能的值
class Phone < ActiveRecord::Base
enum phone_number_type: [:home, :office, :mobile, :fax]
end
```
現在你可以直接操作字元串而不是數字了。
從前:
```ruby
irb(main)::> Phone.first.phone_number_type
=>
<div class="se-preview-section-delimiter"></div>
采用ActiveRecord enums後:
irb(main)::> Phone.first.phone_number_type
=> "fax"
<div class="se-preview-section-delimiter"></div>
通過字元串或者數字都可以改變屬性的值
irb(main)::> phone.phone_number_type = ; phone.phone_number_type
=> "office"
irb(main)::> phone.phone_number_type = "mobile"; phone.phone_number_type
=> "mobile"
<div class="se-preview-section-delimiter"></div>
甚至是通過感歎方法
irb(main)::> phone.office!
=> true
irb(main)::> phone.phone_number_type
=> "office"
<div class="se-preview-section-delimiter"></div>
檢視屬性是否支援某些值:
irb(main)::> phone.office?
=> true
<div class="se-preview-section-delimiter"></div>
查詢滿足屬性值的所有對象:
irb(main):8:> Phone.office
Phone Load (.ms) SELECT "phones".* FROM "phones" WHERE "phones"."phone_number_type" = ? [["phone_number_type", ]]
<div class="se-preview-section-delimiter"></div>
檢視屬性所有可用值:
irb(main):9:> Phone.phone_number_types
=> {"home"=>, "office"=>, "mobile"=>, "fax"=>}
<div class="se-preview-section-delimiter"></div>
在HTML表單中也可以友善使用:
<div class="field">
<%= f.label :phone_number_type %><br>
<%= f.select :phone_number_type, Phone.phone_number_types.keys %>
</div>
<div class="se-preview-section-delimiter"></div>
需要注意的是
Enums
并不是沒有缺陷,如果不想在日後陷入麻煩的話,這裡有幾個問題需要注意。
定義enum時,注意順序。假設你突然決定使用字母順序來排列enum值:
class Phone < ActiveRecord::Base
enum phone_number_type: [:fax, :home, :mobile, :office]
end
<div class="se-preview-section-delimiter"></div>
那麼你電話号碼的類型比對就有問題咯。可以通過設定序号值來解決這個問題:
class Phone < ActiveRecord::Base
enum phone_number_type: {fax: , home: , mobile: , office: }
end
但是講真,最好的選擇是不要改變值的順序。
另一個更大的問題存在于Rails之外。盡管Rails将這些enum值當作字元串處理,而在資料庫中它們隻是一些數字。其他人看原始資料時并不知道這些數字所代表的含義,這就意味着讀取這個資料庫在所有應用都要有一份enum值的映射。
碰到這樣需求的時候可以選擇将enum映射存入資料庫或yaml檔案中。但這不符合
DRY
原則,因為現在你在兩個地方定義了enum。而且随着應用規模的擴充,可能我們一開始想要避免的做法反而更好:建立另一個模型和關聯,即
Phone
belong_to
PhoneNumberType
但是如果想保持簡單,
enums
依然是不錯的選擇。