じゃるログ

その辺にいるエンジニア

rails における enum の validation

はいどうもこんにちは!みんなのウェディングのエンジニア@jaruです。
この記事は、くふうカンパニーアドベントカレンダーの24日目になります。

qiita.com

Rails における enum とは

突然ですが、皆さんは rails における enum をご存知ですか?

api.rubyonrails.org

そして以下は、Blog の状態を表現するために設定された enum の例です。

class Blog < ApplicationRecord
  enum state: { draft: 0, unpublish: 1, publsihed: 2 }
end

このように enum を設定することで、Blog が下書きなのか、公開中なのか、Blog の状態を表現することができます。

Rails における enum の validation

そして日頃から enum を使っている方は、enum にもちゃんと validation をかけたいなと思ったことはありませんか?ありますよね!
そしておそらく、普段から rails を書いている人はこんな風に書くのではないのでしょうか

validates :state, inclusion: { in: %w(draft unpublish publsihed) }

結論から言うと、この validation は全く機能しません。
そんなバカな!と僕も思いました。inclusion なんて enum の validation をかけるのにぴったりじゃないか!と。でもこれは本当に全く機能しないんですよね。以下、console で色々試してみたいと思います。

実験

以下のように、Blog モデルに enum と validation を設定しています。

class Blog < ApplicationRecord
  validates :state, inclusion: { in: %w(draft unpublish publsihed) }

  enum state: { draft: 0, unpublish: 1, publsihed: 2 }
end

ここに state が draft になっている一つの blog があります。 f:id:soccer1356abc:20181221212131p:plain ありとあらゆる方法を使って、こいつにinvalid_paramsという文字列を保存させてみたいと思います。

①セットしてから save してみる

f:id:soccer1356abc:20181221212446p:plain ArgumentError が返ります。

②update してみる

f:id:soccer1356abc:20181221212322p:plain ArgumentError

③asssign_attribute してから update してみる

f:id:soccer1356abc:20181221212413p:plain アーギュメントエラァ…

④update_columns してみる

f:id:soccer1356abc:20181221212937p:plain お? true ですか?! update できちゃったんですか?!? f:id:soccer1356abc:20181221213527p:plain 更新されてませんでした(意図せずバグっぽいのを発掘してしまった…)

ほとんど全てで ArgumentError が返る

そうなんです。enum で設定している"draft", "unpublish", "published"以外の文字列(シンボルでも同様)を渡そうとすると、validation に到達する前に(おそらく確実に)ArgumentError が発生します(一部例外がありましたが…)。なぜこのような設計になっているのかは以下のissueが参考になります。

github.com

じゃあどうする?

モデルでは validation できないとは言いつつも、本当にごく稀に何者かが enum で設定している以外の parameter を投げつけてきて、エラーが発生してしまって、alert が発生してしまって、つらいな…みたいなこともありますよね。 というわけで、enum が定義されているときの validation は、個人的には以下のように例外処理を書いてしまいます。

def update
    begin
      @blog.update blog_params
      # update 成功時の処理
    rescue ArgumentError
      # 例外発生時の処理
    end
  end

はい、これで変な parameter を投げつけられても alert が発生せずハッピーですね!

まとめ

  • enum はあくまでも状態のセットを定義するための機能
  • validates :state, inclusion: { in: %w(draft unpublish publsihed) } は機能しない
  • controller で、例外処理、もしくは filter でチェックする

最後まで読んでいただいてありがとうございました! 明日のラストは、kazumalabさんの年内最後に振り返りますです!