認証機能にDeviseを利用しているシステムで「Routing Error」によりサインアウトできない場合の対処
はじめに
最初に述べておくと、実はDeviseは何も関係ない。jqueryとjquery_ujsのバージョンには気をつけろというお話。
環境
Gemfileの中身は以下の通り。
gem 'rails', '3.2.11' gem 'devise', '3.2.4' gem 'jquery-rails', '3.1.0'
問題
ある画面でリンクを用意し、そのリンクを押下してサインアウトしようとすると、、、
<a href="/users/sign_out" data-method="delete" rel="nofollow">サインアウト</a>
以下の「Routing Error」がでてしまう。
No route matches [GET] "/users/sign_out"
ルーティングを見てみると、
% bundle exec rake routes | grep sign_out destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy
サインアウトはDELETEメソッドで呼び出す必要がある事が分かる。 エラーの通りGETで呼び出してしまっている事が問題という事が分かった。
またもう一つの事実として 問題はjquery1.10.2をロードしている画面のみで発生しており、 少し古いjquery1.6.2をロードしている画面では発生しない(サインアウトできる)ことが分かった。
TwitterのBootstapを利用する為に新しいjqueryを導入する必要があった訳だが、思わぬところでつまずいてしまった。
調査
Railsでは現在GET/POSTしかないHTTPでCRUD操作を実現する為に、formのinputタグのnameに「_method」を指定する。
CRUDのD(DELETE)を行う場合は<input name="_method" type="hidden" value="delete" />
と指定する。
http://guides.rubyonrails.org/form_helpers.html
今回の問題が発生したのはaタグで生成したリンクだが、実はこのリンクは押下時にformタグに置き換えられている。
これを実現しているのは、jquery_ujs.jsというスクリプトでGemの「jquery-rails」によってインストールされる。
linkClickSelectorメソッド内で呼び出されるhandleMethodメソッド内でリンクがformタグに置き換わっている事が分かる。
「data-method」で「delete」と指定していれば<input name="_method" value="delete" type="hidden" />
と置き換わる。
… $(rails.linkClickSelector).live('click.rails', function(e) { var link = $(this); if (!rails.allowAction(link)) return rails.stopEverything(e); if (link.data('remote') !== undefined) { rails.handleRemote(link); return false; } else if (link.data('method')) { rails.handleMethod(link); return false; } }); … handleMethod: function(link) { var href = link.attr('href'), method = link.data('method'), csrf_token = $('meta[name=csrf-token]').attr('content'), csrf_param = $('meta[name=csrf-param]').attr('content'), form = $('<form method="post" action="' + href + '"></form>'), metadata_input = '<input name="_method" value="' + method + '" type="hidden" />'; if (csrf_param !== undefined && csrf_token !== undefined) { metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />'; } form.hide().append(metadata_input).appendTo('body'); form.submit(); } …
原因
ここまで調査して、ようやくブラウザのデバッグ用コンソールに以下のメッセージが出力されている事に気付く。
[Error] TypeError: undefined is not a function (evaluating '$(rails.linkClickSelector).live') (anonymous 関数) (jquery_ujs.js, line 266)
最初はlinkClickSelectorがないと言われているのかと思ったが、「liveメソッドがない」という事だった。 調べていくと、jquery1.9以降にliveメソッドが存在しないという事が分かった。
後一歩だ。
jquery_ujs.jsが古いという事は、jquery-railsのバージョンが古いはず!と思ったのに、GemのバージョンはRails3系のもので問題なさそう。
こちらの記事を参考にしてみると、以下のメッセージがでた。
% bundle exec rails g jquery:install deprecated You are using Rails 3.1 with the asset pipeline enabled, so this generator is not needed. The necessary files are already in your asset pipeline. Just add `//= require jquery` and `//= require jquery_ujs` to your app/assets/javascripts/application.js If you upgraded your app from Rails 3.0 and still have jquery.js, rails.js, or jquery_ujs.js in your javascripts, be sure to remove them. If you do not want the asset pipeline enabled, you may turn it off in application.rb and re-run this generator.
まとめると、以下を行えば良さそうだ。
- Rails3.1でasset pipelineがenableの場合、このコマンドでjsをインストールする必要がある。
- app/assets/javascripts/application.jsに「//= require jquery」および「//= require jquery_ujs」を追記する。
- Rails3.0からアップグレードした場合、jquery.js/rails.js/jquery_ujs.jsを削除しなければならない。
対処
今回の問題は asset pipelineを有効にしていても、app/assets/javascripts配下に古いバージョンのjsが存在するとそちらを優先する為に発生していたようだ。 古いバージョンのjsを削除し、以下のように設定すると問題なく動作した。
% cat app/assets/javascripts/application.js // Place your application-specific JavaScript functions and classes here // This file is automatically included by javascript_include_tag :defaults // //= require 'jquery_ujs'
% cat app/views/layouts/hoge.html.erb <head> … <%= stylesheet_link_tag 'bootstrap_flatly/bootstrap.css' %> <%= stylesheet_link_tag 'bootstrap_flatly/usebootstrap.css' %> <%= stylesheet_link_tag 'bootstrap_flatly/dashboard.css' %> <%= stylesheet_link_tag 'bootstrap_flatly/sticky-footer-navbar.css' %> <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> <!--[if lt IE 9]> <%= javascript_include_tag 'bootstrap_flatly/html5shiv.js' %> <%= javascript_include_tag 'bootstrap_flatly/respond.min.js' %> <![endif]--> <%= javascript_include_tag 'https://code.jquery.com/jquery-1.10.2.min.js' %>★←jqueryはapplication.js内ではロードさせない <%= javascript_include_tag 'bootstrap_flatly/bootstrap.min.js' %> <%= javascript_include_tag 'bootstrap_flatly/usebootstrap.js' %> <%= javascript_include_tag 'jquery-ui-1.8.16.custom.min.js' %> <%= javascript_include_tag 'jquery.inputtips-1.0.0-min.js' %> <%= javascript_include_tag :application %>★←application.jsのロード … </head>
上記は参考までにBootstrapのflatlyというテーマとダッシュボード用のテーマを導入した際にロードしたcssとjs。 Bootstrapのテーマが古いjqueryだと動作しないため、application.jsでjquery.jsをロードしないように注意が必要。