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


# ニュース

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

**- Before（Rails5, Rails6）**

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

**- After（Rails7）**

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

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

# この記事で伝えたいこと

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

核心部分だけ見たい方は、[ショートカット](#_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` （カンバセーション：会話、対話） というテーブルを例として、２つの `enum` を定義したい。
- status （ステータス。0=有効、10=アーカイブ）
- comments_status （コメントのステータス。0=有効、10=無効）

# まちがった定義その１

```ruby:/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.)
```
<font color="Red">エラー</font>。後から追加した`comments_status`の`active`が重複しているため。

# まちがった定義その２

```ruby:/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
```
<font color="red">間違いその１のエラーを回避するために後から追加した`comment_status`の値を書き換えている。</font>確かにエラーにはならないが、結果的にわかり辛くなった。以下のように比較する際に気持ち悪くなりそう。

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

# まちがった定義その３

```ruby:/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
```

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

```ruby
conversation.comments_status_active? # 分かりやすい
conversation.status_active? # 分かりやすい
```
確かにこれで「重複もしない」、「混乱もしにくい」と問題なさそうだが、_prefix（接頭辞）、_suffix（接尾辞）機能を活用できていないという点で<font color="red">良い設定とは言えないだろう。</font>

# _prefix（接頭辞）_suffix（接尾辞）を使う

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

## _prefix（接頭辞）
```ruby:/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` は接頭辞となるため以下のような操作が可能になる。

```ruby
# 頭に 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（接尾辞） 
```ruby:/app/models/conversation.rb
class Conversation < ActiveRecord::Base
  enum status: [:active, :archived], _suffix: true
  enum comments_status: [:active, :inactive], _suffix: true
end
```

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

```ruby
# お尻に _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>)
```

# カスタム値

```ruby:/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_`だけでいいので文字数が減り+意味も理解しやすいという利点がある。

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

## まとめ

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

<font color="red">良い controller は良い model が作る。 良い model は良い enum が作る</font>

## 参考リンク
[Ruby on Rails 5.1.6 > ActiveRecord::Enum](https://api.rubyonrails.org/v5.1/classes/ActiveRecord/Enum.html)
[Change the World!> ActiveRecord::Enum の使い方と重複エラーの避け方](https://www.changesworlds.com/blog/2017/08/how-to-use-activerecord-enum-and-how-to-avoid-duplicate-errors/)
[Qiita > Rails5でenum定義したカラムの元の値を取得
](https://qiita.com/yusuke-matsuda/items/df05c8165e2f084023b0)