Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

RailsのActiveRecord enumで簡単に作れる、読みやすい属性の実装方法

「保留中」「承認済み」「フラグ付き」のいずれかの状態を持ちうる質問。あるいは「自宅」「会社」「携帯」「FAX」(もし1982年なら)のどれかに分類される電話番号。

こうしたデータが必要になるモデルは少なくありません。いくつかの候補の中から必ず1つの値だけを持つ属性であり、しかもその値のセットはほとんど変わることがありません。

純粋なRubyで書くなら、ここは単純にシンボルを使う場面です。

PhoneNumberTypeやQuestionStatusといったモデルを作成し、belongs_toの関連でこれらの値を保持する方法もありますが、そこまでする価値はなさそうです。YAMLファイルに押し込む手もありますが、今度はオブジェクトが何をできるのかを把握するために、まったく別の場所を確認しに行かなければなりません。

Rails 4.1では、この問題に対する解決策としてActiveRecord enumが導入されました。

モデル内で少数の値を扱う

ActiveRecordのenumはとても簡単に使えます。まずモデルにinteger型のカラムを用意します。

bin/rails g model phone number:string phone_number_type:integer

次に、その属性が取りうる値を列挙します。

app/models/phone.rb
class Phone < ActiveRecord::Base
  enum phone_number_type: [:home, :office, :mobile, :fax]
end

これで、生の数値の代わりに文字列として属性を扱えるようになります。

こんな表示ではなく:

irb(main):001:0> Phone.first.phone_number_type
=> 3

こう表示されます。

irb(main):002:0> Phone.first.phone_number_type
=> "fax"

属性の変更は、文字列でも整数でも行えます。

irb(main):003:0> phone.phone_number_type = 1; phone.phone_number_type
=> "office"
irb(main):004:0> phone.phone_number_type = "mobile"; phone.phone_number_type
=> "mobile"

バンメソッド(!付き)を使うことさえ可能です。

irb(main):005:0> phone.office!
=> true
irb(main):006:0> phone.phone_number_type
=> "office"

属性が特定の値を持っているかどうかを確認するメソッドも自動的に生成されます。

irb(main):007:0> phone.office?
=> true

さらに、目的の値を持つすべてのオブジェクトを検索することもできます。

irb(main):008:0> Phone.office
  Phone Load (0.3ms)  SELECT "phones".* FROM "phones" WHERE "phones"."phone_number_type" = ?  [["phone_number_type", 1]]

利用可能なすべての値と、それぞれに対応する数値を確認したい場合は、phone_number_typesクラスメソッドを使います。

irb(main):009:0> Phone.phone_number_types
=> {"home"=>0, "office"=>1, "mobile"=>2, "fax"=>3}

これにより、HTMLフォームへの組み込みも非常に簡単になります。

app/views/phones/_form.html.erb
<div class="field">
  <%= f.label :phone_number_type %><br>
  <%= f.select :phone_number_type, Phone.phone_number_types.keys %>
</div>

RailsのActiveRecord enumで簡単に作れる、読みやすい属性の実装方法

注意すべきポイント

とはいえ、enumにも落とし穴があります。後々トラブルに巻き込まれないためにも、いくつかの点に留意しておきましょう。

enumを定義する際、値の順序が重要です。たとえば、後からコードを見返して「やっぱりアルファベット順に並べ替えよう」と判断したとしましょう。

app/models/phone.rb
class Phone < ActiveRecord::Base
  enum phone_number_type: [:fax, :home, :mobile, :office]
end

すると、既存レコードの型情報が正しく対応しなくなってしまいます。これを回避するには、どの数値がどの値に対応するかを明示的に指定します。

app/models/phone.rb
class Phone < ActiveRecord::Base
  enum phone_number_type: {fax: 3, home: 0, mobile: 2, office: 1}
end

ただし実際には、最初から順序を一貫して保つのが最善の対策です。

より大きな問題は、Railsの世界の外側との連携です。Rails上ではenumの値は文字列のように見えますが、データベース内には単なる整数として保存されています。そのため、生のデータを直接見ても、その数字が何を意味するのかまったく分かりません。これは同時に、そのデータベースを読み取るすべてのアプリケーションが、enumのマッピングを把握している必要があることも意味します。

本当に他の人にもマッピングを見せる必要があるなら、enumの対応表をデータベースやYAMLファイルに出力する方法もあります。しかしそれはDRY原則に反します。enumを2箇所で定義することになってしまうからです。そこまで手間をかけるなら、冒頭で避けていた道——完全に独立したモデルと関連を作成し、PhoneがPhoneNumberTypeにbelongs_toする構成——を選ぶほうが賢明かもしれません。

ただ、シンプルさを優先したいのであれば、enumは間違いなく優れた出発点となります。

P.S. お見逃しの方へ。『Practicing Rails』が7月6日(月)に開始されるRuby Book Bundleに収録されます。他の5冊の厳選Ruby書籍とまとめて、大幅割引で入手できます!

  1. 新しいAndroidスマホを買ったら必ずやるべき15の初期設定ガイド

    新しいスマートフォンを手に入れたばかりですか?スマホを快適に、そして安全に使いこなしたいなら、購入直後に行うべき初期設定をしっかり押さえておきましょう。 21世紀最大の発明を挙げるとすれば、間違いなくAndroidスマートフォンが候補に上がるでしょう。Android OSは世界中で高い需要を誇り、国や地域を問わず、多くの市場で圧倒的なシェアを占めています。 仕事のタスクをこなし、自撮り写真を楽しむ大人から、親のスマホで動画や音楽を楽しむ子どもまで、Androidスマホにできないことはほとんどありません。だからこそ、登場からわずか数年で爆発的に普及し、あらゆる年齢層から支持され続けているのです。

  2. Aquaris E4.5 Ubuntu PhoneをAndroid化してみた – 哀愁漂う乗り換え記録

    またやってしまいました。お蔵入りになっていた最後のUbuntu Touch端末を、平和な方法でAndroidにコンバートすることに決めたのです。何もせず、埃をかぶり、思い出だけを溜め込んでいたあの端末を。こうして、「Ubuntu Phoneへの期待を捨て、Androidを愛するようになった」という哀愁漂う物語が始まりました。 念のため背景を説明しておくと、筆者は昔、Ubuntuがモバイル分野で成功することを本気で願っていました。まず「Edge」が発表され、続いて「Aquaris E4.5」が登場。その普及のため、いくつかのコンテストまで開催しました。Aquaris M10タブレットが発売された