【Rails】flash[:notice]とか書くから、flashのキーはシンボルだと思ってたら「文字列」だった

Railsでの開発中に遭遇した不具合について、原因と解決策を投稿します。

環境

背景

flashを使って以下のようにメッセージを表示していました。

<% if flash[:notice] %>
  <div class="alert alert-primary">
    <%= flash[:notice] %>
  </div>
<% end %>

flash[:notice]だけではなく、flash[:alert]を受け取った場合もメッセージを表示します。

<% if flash[:notice] %>
  <div class="alert alert-primary">
    <%= flash[:notice] %>
  </div>
<% end %>

<% if flash[:alert] %>
  <div class="alert alert-danger">
    <%= flash[:alert] %>
  </div>
<% end %>

しかし、上記のように種類が増えるにつれてif文を増やしていくのは、少し冗長的なので、case文を使ってもう少しスッキリさせてみます。

<% flash.each do |type, message| %>
  <% case type
    when :notice %>
    <div class="alert alert-primary">
      <%= message %>
    </div>
  <% when :error %>
    <div class="alert alert-danger">
      <%= message %>
    </div>
  <% end %>
<% end %>

不具合の内容

既述の case文を使った書き方にすると、なぜかflashが画面に表示されなくなってしまいました。

不具合の原因

flashflash[:notice] = "message"のように指定するため、flashのキーはシンボルかと思っていました。
しかし、実際に確認してみると、キーは「文字列」になっていることがわかります。

{"alert"=>"An error occurred: Quantity must be greater than 0"}

そのため、シンボルで指定すると条件に一致しません。

これはHashWithIndifferentAccessというRailsの機能で、シンボルと文字列の両方をキーとして受け取れるが、内部的にはそれを「文字列」として保存する仕様によるものだと思います。

解決策

この問題を解決するためには、case文でflashのキーを指定する際に「文字列」で指定する必要があります。

<% flash.each do |key, message| %>
  <% case key
    when "notice" %>
    <div class="alert alert-primary">
      <%= message %>
    </div>
  <% when "alert" %>
    <div class="alert alert-danger">
      <%= message %>
    </div>
  <% end %>
<% end %>

なお、このcase文はヘルパーメソッドとして、外部で定義した方がよりビューが綺麗になりそうです。

# app/helpers/application_helper.rb
def flash_class(type)
  case type
    when "notice" then "alert alert-primary"
    when "alert" then "alert alert-danger"
    # 他のflashタイプもここに追加できます
  end
end
<% flash.each do |type, message| %>
  <div class="<%= flash_class(type) %>">
    <%= message %>
  </div>
<% end %>