メッセージ機能で実際にあったお話。

メッセージを送るのに、以下のように preview で内容のバリデーションをしつつ見た目の確認をし、create で保存、かつ、通知メールを出す、という処理があったとする。 (実際のコードは書けないので、かなり適当にサンプルコードは書いてる。。。)

class MessagesController < ApplicationController
  def new
    # 略
  end

  # メッセージのプレビュー
  def preview
    @message = Message.new
    @message.attributes = message_params

    return if @message.valid?

    render :new
  end

  # メッセージ作成
  def create
    @message = Message.new
    @message.attributes = message_params

    Message.transaction do
      @message.save!

      UserMailer.message_notification(@message.receiver)
    end
  end

  private

  def message_params
    # 略
  end
end

※実際の処理には、Message モデルの validation で利用可能文字の制限や文字列の長さ制限をしている。

普通の使い方をしていれば、preview -> create の間に validation エラーになるようなものは発生しない…はずだが…問題が起こることがある。 実際に下記のことが発生していた。

文字コードの誤判定

おそらくブラウザの文字コード誤判定か何かだと思うのだけど、UTF-8で書かれるべき文字列がSJISと思われる化けた文字で送信されていた。

※「と思われる」というのは再現できたけど、ユーザがそうしたかどうかわからないから。ログには化けた文字が送信されていることが記録されていた。

↑の理由により、利用可能文字制限にひっかかってsave! による例外発生で、システムエラーになっていた。

ブラウザの文字コード変換機能を利用するとたしかに再現できた。(見た目も化けるから普通おかしいと思って送信しないとは思うけど…)ので、preview しているとはいえ、validation にひっかかる値が混入する可能性がある。

対応

とりあえず、create のときも、validation でエラーになることを想定し、エラーの場合は編集画面に飛ばすということをしています。

class MessagesController < ApplicationController
  # 略

  # メッセージ作成
  def create
    @message = Message.new
    @message.attributes = message_params

    begin
      Message.transaction do
        @message.save!

        @message.sent_notification(@message.receiver)
      end
    # ここで save! で例外が発生したときは編集画面に戻す
    rescue ActiveRecord::RecordInvalid
      render :new
      return
    end
  end

  # 略
end

まとめ

preview で安心せずに、create でもバリデーションエラーに備える。