Railsで、deviseのログインフォームをBootstrapを利用してモーダル化した手順

##現在の状態
rails初心者。
Rails5のサンプルアプリにdeviseでログイン機能を追加したところ。
ヘッダーメニューにログインページへのリンクを配置している。

##やりたいこと
bootstrap4のモーダルウインドウを利用して、ログインフォームをモーダル化したい。
ログイン後は、特定のページに飛ぶのではなくログイン前に見ていたページに戻るようにしたい。

##準備
 - Bootstrap4の導入
 - ターミナルに`rails g devise:controllers users`コマンドで、`/controller/users`以下にdeviseのコントローラーファイルを生成する。

##実行
####リンクを修正
 - ログインフォームへのリンクをモーダル呼び出しボタンにするため、`data-toggle`と`data-target`を追加する。

```ruby:application.html.erb
<%= link_to "ログイン", '', class: "nav-link", "data-toggle": "modal", "data-target": "#signinModal" %>
```

####`/sessions/new.html.erb`のログインフォーム部分をモーダルの中(`<div class="modal-body">`以下)に貼り付ける。

```ruby:application.html.erb
<div class="modal fade" id="signinModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true" style="margin: 200px auto; height: 700px;">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLabel">サンプル</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">

        <!-- ここからフォーム -->
        <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
          <div class="field">
            <%= f.label :email %><br />
            <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
          </div>

          <div class="field">
            <%= f.label :password %><br />
            <%= f.password_field :password, autocomplete: "current-password" %>
          </div>

          <% if devise_mapping.rememberable? -%>
            <div class="field">
              <%= f.check_box :remember_me %>
              <%= f.label :remember_me %>
            </div>
          <% end %>

          <div class="actions">
            <%= f.submit "ログイン" %>
          </div>
        <% end %>

      </div>
    </div>
  </div>
</div>
```

####ログインに成功したら、ログイン前に見ていたページにリダイレクトするようにする。
 - ↓こちらを参考に`application_controller.rb`を修正する。といっても貼り付けるだけ。<br>
https://github.com/plataformatec/devise/wiki/How-To:-Redirect-back-to-current-page-after-sign-in,-sign-out,-sign-up,-update

```ruby:application_controller.rb

class ApplicationController < ActionController::Base
  before_action :store_user_location!, if: :storable_location?
  before_action :authenticate_user!

  private
    def storable_location?
      request.get? && is_navigational_format? && !devise_controller? && !request.xhr? 
    end

    def store_user_location!
      store_location_for(:user, request.fullpath)
    end

    def after_sign_in_path_for(resource_or_scope)
      stored_location_for(resource_or_scope) || super
    end
end
```

これで、ログインフォームのモーダル化、ログイン後の元のページへのリダイレクトが設定できた。

####ログインに失敗しても、元々見ていたページにリダイレクトするようにする。
 - 今の状態だと、ログインに失敗したら`/sessions/new.html.erb`に飛んでしまうのでログイン失敗時のリダイレクト先も設定する。
 - `form_for`内に`f.hidden_field`を追加し、現在開いているページのURLを取得して`:url`という名前で`controller`に渡す。

```ruby:application.html.erb
 (略)

<%# ログイン失敗時にこのページに戻れるように、:urlという名前でURLを一緒に送る %>
<%= f.hidden_field :url, value: request.fullpath %>

 (略)
```

 - 次は`sessions_controller`に、ログイン失敗後の処理を追加する。↓を参考に。<br>https://github.com/plataformatec/devise/blob/master/app/controllers/devise/sessions_controller.rb

 - `sessions_controller`の`create`メソッドを以下のように修正。

```ruby:sessions_controller.rb
     (略)

# ログイン失敗後は def failed に飛ぶように変更
  def create
    auth_options = { scope: resource_name, recall: "#{controller_path}#failed" }
    self.resource = warden.authenticate!(auth_options)
    set_flash_message!(:notice, :signed_in)
    sign_in(resource_name, resource)
    yield resource if block_given?
    respond_with resource, location: after_sign_in_path_for(resource)
  end

# ログイン失敗の時は直前のURLにリダイレクトする
  def failed
    flash[:alert] = "メールアドレスまたはパスワードが違います。"
    redirect_to params[:user][:url]
  end
    (略)
```

 - `def create`の１行目`auth_options`の`recall`で、ログインに失敗したときのアクションを指定する。通常は`sessions#new`に飛ぶようになっているが、ここでは`failed`アクションを呼び出す様にしている。
 - その下に`failed`アクションを定義している。ログインフォームから渡されたURLを`params[:user][:url]`で取得し、リダイレクト先とする。

 - 最後に、`routes.rb`にて、カスタマイズした`sessions_controller.rb`を使えるようにする。

```ruby:routes.rb
devise_for :users, controllers: { sessions: 'users/sessions'}
```

これでログインが成功しても失敗しても、元のページに戻れるようになった！

###その他、途中でひっかかったこと
 - ヘッダーメニューからログインフォームを呼び出すので、`link_to`の直後にモーダル本体の処理を書いていたところ、モーダルが影の下に隠れてしまい困った。ヘッダーメニューを上部固定にしていたのだが、`position: absolute;`な要素内にモーダルを配置するとこういうことが起きるらしい。モーダル本体をヘッダーメニューの外に出したら解決した。
http://develop-hish-1ne.hatenablog.com/entry/2014/01/31/163704

 - `view/devise/session/new.html.erb`の中身をモーダルの中に移したところ、`undefined local variable or method 'resource_name'`とエラーが出た。`application_helper`に以下の内容を記述したら解決した。
↓こちらの記事の通りにさせていただいただきました。
http://hajimete-ruby.jugem.jp/?eid=84

```ruby:application_helper.rb
def resource_name
   :user
end

def resource
   @resource ||= User.new
end

def devise_mapping
   @devise_mapping ||= Devise.mappings[:user]
end
```

モデル名が`User`ではない場合は実際のモデル名に合わせる。

 - なんだか無駄に遠回りしているような気がしますが、もっと簡単なやり方などあれば教えていただけると幸いです...。

##参考にさせたいただいたページ
 - https://github.com/plataformatec/devise/wiki/How-To:-Redirect-back-to-current-page-after-sign-in,-sign-out,-sign-up,-update
 - https://github.com/plataformatec/devise/blob/master/app/controllers/devise/sessions_controller.rb
 - https://code.i-harness.com/ja/q/58ffb7
 - https://qiita.com/regashia/items/195d15be3cb40d0ddced
 - http://hajimete-ruby.jugem.jp/?eid=84
 - http://develop-hish-1ne.hatenablog.com/entry/2014/01/31/163704