180
117

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Rails5 から enum 使う時は_prefix(接頭辞)_suffix(接尾辞)を使おう

Last updated at Posted at 2018-10-21

ニュース

Rails7 がリリースされたことで enum 構文の記載方法が少し拡張されました。(得られる結果は現状同じ)

- Before(Rails5, Rails6)

Key-Valueとして列挙していた
  enum status:          { active: 10, archived: 20 }, _prefix: true
  enum comments_status: { active: 10, inactive: 20 }, _suffix: true

- After(Rails7)

カンマで区切り引数として定義する事が可能に
  enum :status,          { active: 0,  archived: 10 }, prefix: true
  enum :comments_status, { active: 10, inactive: 20 }, suffix: true

余計なアンダースコアなども抜け落ちてスッキリし、Afterの方が印象は良いですね。

この記事で伝えたいこと

Rails5 からActiveRecord::Enum を定義する際は_prefix_suffixという武器がある事を知っていただきたい。

核心部分だけ見たい方は、ショートカット

:point_up: 2019.09.12
文章の 「です・ます」調を統一。

:point_up: 2019.06.01
例として使用するテーブルの説明を追記

Rails のバージョン

bash
 ./bin/rails --version
Rails 5.2.0

_prefix、_suffix の定義

:books: You can use the :_prefix or :_suffix options when you need to define multiple enums with same values. If the passed value is true, the methods are prefixed/suffixed with the name of the enum. It is also possible to supply a custom value:

同じ値を持つ複数のenumを定義する必要がある場合は、_prefixまたは:_suffixオプションを使用できます。渡された値がtrueの場合、メソッドにはenumの名前の接頭辞/接尾辞が付けられます。カスタム値を指定することもできます。

が今回のテーマ。さっそく同じ値を持つ複数の enum を定義していこう。
とその前に、よくある enum 定義とそれが良くない理由を深掘りしていき理解度を深めよう。


定義したい内容

Conversation (カンバセーション:会話、対話) というテーブルを例として、2つの enum を定義したい。

  • status (ステータス。0=有効、10=アーカイブ)
  • comments_status (コメントのステータス。0=有効、10=無効)

まちがった定義その1

/app/models/conversation.rb
class Conversation < ActiveRecord::Base
  enum status: { active: 0, archived: 10 }
  enum comments_status: { active: 0  inactive: 10 }
end

結果

irb(main):004:0> c = Conversation.find(1)
ArgumentError (You tried to define an enum named "comments_status" on the model "Conversation", but this will generate a instance method "active?", which is already defined by another enum.)

エラー。後から追加したcomments_statusactiveが重複しているため。

まちがった定義その2

/app/models/conversation.rb
class Conversation < ActiveRecord::Base
  enum status: { active: 0, archived: 10 }
  enum comments_status: { comments_status_active: 0,  comments_status_inactive: 10 }
end

間違いその1のエラーを回避するために後から追加したcomment_statusの値を書き換えている。確かにエラーにはならないが、結果的にわかり辛くなった。以下のように比較する際に気持ち悪くなりそう。

conversation.comments_status_active? # 分かりやすい
conversation.active? # 「status.active か comments_status.active」 のどちらか分かり辛い。

まちがった定義その3

/app/models/conversation.rb
class Conversation < ActiveRecord::Base
  enum status: { status_active: 0, status_archived: 10 }
  enum comments_status: { comments_status_active: 0,  comments_status_inactive: 10 }
end

今度は統一性を持たすためにそれぞれ接頭辞をつけるようにした。
これで重複エラーも回避、統一性があり気持ち悪さも回避と問題解消。

conversation.comments_status_active? # 分かりやすい
conversation.status_active? # 分かりやすい

確かにこれで「重複もしない」、「混乱もしにくい」と問題なさそうだが、_prefix(接頭辞)、_suffix(接尾辞)機能を活用できていないという点で良い設定とは言えないだろう。

_prefix(接頭辞)_suffix(接尾辞)を使う

ここからが本テーマの中枢。

_prefix(接頭辞)

/app/models/conversation.rb
class Conversation < ActiveRecord::Base
  enum status: [:active, :archived], _prefix: true
  enum comments_status: [:active, :inactive], _prefix: true
end

各 enum 行の後ろに _prefix: true を付与。
:active は、 enum status と enum comments_status のどちらにも存在する値だが、 _prefix を付与することで重複エラーにはならない。

_prefix は接頭辞となるため以下のような操作が可能になる。

# 頭に status_ を付与
> conversation.status_archived! # 更新(UPDATE)
> conversation.status_archived? # true
> conversation.status_active? # false

# 頭に comments_status_ を付与
> conversation.comments_status_active! # 更新(UPDATE)
> conversation.comments_status_active? # true
> conversation.comments_status_inactive? # false

# 接頭辞なしはエラーになる
> conversation.active?
Traceback (most recent call last):
        1: from (irb):11
NoMethodError (undefined method `active?' for #<Conversation:0x00007ff78097c910>)

_suffix(接尾辞)

/app/models/conversation.rb
class Conversation < ActiveRecord::Base
  enum status: [:active, :archived], _suffix: true
  enum comments_status: [:active, :inactive], _suffix: true
end

続いて _suffix (接尾辞)。以下のような操作が可能となる。

# お尻に _status を付与
> conversation.archived_status! # 更新(UPDATE)
> conversation.archived_status? # true
> conversation.active_status? # false

# お尻に _comments_status を付与
> conversation.active_comments_status! # 更新(UPDATE)
> conversation.active_comments_status? # true
> conversation.inactive_comments_status? # false

# 接尾辞なしはエラーになる
> conversation.active?
Traceback (most recent call last):
        1: from (irb):11
NoMethodError (undefined method `active?' for #<Conversation:0x00007ff78097c910>)

カスタム値

/app/models/conversation.rb
class Conversation < ActiveRecord::Base
  enum status: [:active, :archived], _prefix: true
  enum comments_status: [:active, :inactive], _prefix: :comments
end

comments_status 側だけ_prefix: :comments とカスタム値を設定。
こうすることで、 接頭辞comments_status_ をつけるよりcomments_だけでいいので文字数が減り+意味も理解しやすいという利点がある。

# 頭に comments_ を付与
> conversation.comments_active! # 更新(UPDATE)
> conversation.comments_active? # true
> conversation.comments_inactive? # false

まとめ

上記の間違い1,2,3は実際に私が遭遇した内容であり、もしどこかで同じようなプロジェクトに遭遇している方はぜひ _prefix, _suffix を利用しより良いコードを体験してみてほしい。

良い controller は良い model が作る。 良い model は良い enum が作る

参考リンク

Ruby on Rails 5.1.6 > ActiveRecord::Enum
Change the World!> ActiveRecord::Enum の使い方と重複エラーの避け方
Qiita > Rails5でenum定義したカラムの元の値を取得

180
117
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
180
117

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?