deviseのエラーメッセージがうまく表示されない。


概要

deviseのエラーメッセージがうまく表示されないので、理由をなんとなく突き止めた。
解決とは言えないけど、decviseの仕組みが少しだけ理解できたので忘備録として残しておきます。

解決策

部分テンプレートを呼び出す時に、テンプレートに変数名をresourceから@モデル名に変更した。

エラーメッセージ
<%= render "devise/shared/error_messages", resource: resource %><%= render "devise/shared/error_messages", resource: @profile %>

deviseは複数のモデルのインスタンスを扱えるフレームワークなので、resourceでうまく処理できるみたいです。
本当は別の処理もありそうだけど、ここを@モデル名に変えたらうまくいきました。

その他のコード

ルーティング

ユーザー登録とプロフィール登録をウィザード形式にしているため、profileは'users/registration'コントローラーで処理させています。
※なんかここにエラーがでた理由が隠されていそうなんだけど詳しくは不明。

routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations'
  }
  devise_scope :user do
    get 'profiles', to: 'users/registrations#new_profile'
    post 'profiles', to: 'users/registrations#create_profile'
  end
end

テーブル設計

deviseを使って、usersテーブルとprofilesテーブルにユーザー情報を保存しようとしている。

schema.rb
# usersテーブルとprofielsテーブルはこんな感じ。

  create_table "profiles", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "favorite_beer"
    t.text "twitter_link"
    t.text "info"
    t.bigint "user_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["user_id"], name: "index_profiles_on_user_id"
  end


  create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "nickname", null: false
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

入力フォーム

プロフィール登録のビューはこんな感じ。
ここの部分テンプレートでエラーメッセージを呼び出しているが、そこにうまくprofilesテーブルの情報が渡せなかった様子。

new_profile
<h2>プロフィール登録</h2>
<%= form_with model: @profile, local: true do |f| %>
  <%= render "devise/shared/error_messages", resource: @profile %>

  <div class="field">
    <%= f.label :avatar %><br />
    <%= f.file_field :avatar %>
  </div>

  <div class="field">
    <%= f.label :favorite_beer %><br />
    <%= f.text_field :favorite_beer, autofocus: true, autocomplete: "favorite_beer" %>
  </div>

  <div class="field">
    <%= f.label :twitter_link %><br />
    <%= f.text_field :twitter_link, autocomplete: "twitter_link" %>
  </div>

  <div class="field">
    <%= f.label :info %><br />
    <%= f.text_area :info %>
  </div>

  <div class="actions">
    <%= f.submit "新規登録" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

エラーメッセージの呼び出しもと

部分テンプレートで呼び出しているdeviseのエラーメッセージは以下の通り

divese/shared/_error_messages.html.erb
<% if resource.errors.any? %>
<%# この↑の部分のresourceに@profileを渡した%>
  <div id="error_explanation">
    <h2>
      <%= I18n.t("errors.messages.not_saved", #ここら辺の記述はdevise.ja.ymlに記載がある
                 count: resource.errors.count,
                 resource: resource.class.model_name.human.downcase)
       %>
    </h2>
    <ul>
      <% resource.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
    </ul>
  </div>
<% end %>

deviseを日本語化しているファイル

deviseは既に日本語化しているので、ymlファイルも日本語になっています。

config/locales/devise.ja.yml
ja:
  activerecord:
    attributes:
# ~省略~     
    models:
      user: ユーザ
      profile: プロフィール # ここも日本語化したかったので追記しました
# ~省略~
  errors:
    messages:
      already_confirmed: は既に登録済みです。ログインしてください。
      confirmation_period_expired: の期限が切れました。%{period} までに確認する必要があります。 新しくリクエストしてください。
      expired: の有効期限が切れました。新しくリクエストしてください。
      not_found: は見つかりませんでした。
      not_locked: は凍結されていません。
# このしたのnot_savedのところが部分テンプレートで呼び出されている様子
      not_saved:
        one: エラーが発生したため %{resource} は保存されませんでした。
        other: "%{count} 件のエラーが発生したため %{resource} は保存されませんでした。"

まとめ

今後まだまだ勉強していきたいと思います。
もしこの文章がとんでもない勘違いを含んでいましたら、ご指摘いただければと思います。