jQueryを読み込まずにRailsで開発している場合、deviseのログアウト処理など、method: :delete
を使っている時に困ることがあります。
<%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
このように書いていてもget処理にしかならず、routingエラーが出てしまいます。
原因はざっくり説明するとjQueryを読み込んでいないからで、下記参考記事によるとjquery-ujs
を読み込んでいる時はjquery-ujs
のrails.js
がうまいこと動作させてくれているようです。
jquery-ujs/src/rails.js
// Handles "data-method" on links such as:
// <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
handleMethod: function(link) {
var href = rails.href(link),
method = link.data('method'),
target = link.attr('target'),
csrfToken = rails.csrfToken(),
csrfParam = rails.csrfParam(),
form = $('<form method="post" action="' + href + '"></form>'),
metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';
if (csrfParam !== undefined && csrfToken !== undefined && !rails.isCrossDomain(href)) {
metadataInput += '<input name="' + csrfParam + '" value="' + csrfToken + '" type="hidden" />';
}
if (target) { form.attr('target', target); }
form.hide().append(metadataInput).appendTo('body');
form.submit();
},
この問題の解決策は
- get処理をするように
route.rb
に書く - deviseの設定を書き換え、getをdeleteにする
などがよく挙げられていたのですが、あまり気持ちよくないので別の方法がないか調べていました。
config/initializers/devise.rbでdeleteをgetにする
# Use this hook to configure devise mailer, warden hooks and so forth.
# Many of these configuration options can be set straight in your model.
Devise.setup do |config|
...
# The default HTTP method used to sign out a resource. Default is :delete.
config.sign_out_via = :delete # ここをgetにする
...
end
すると、良さそうな方法が見つかったのでご紹介しようと思います。
button_toを使う
link_to
をbutton_to
に書き換えるだけで、一応deleteとして動くようになります。
<%= button_to 'ログアウト', destroy_user_session_path, method: :delete %>
HTML出力結果
<form class="button_to" method="post" action="/users/sign_out">
<input type="hidden" name="_method" value="delete">
<input type="submit" value="ログアウト">
<input type="hidden" name="authenticity_token" value="(省略)">
</form>
confirm
に対応させる
ただ、これは確認ダイアログを出す処理には対応していません(押してもconfirmが出ない)。これだと、削除処理を行うときは少し心もとないので、以下の様なjsを追加してやります。
window.onload = () => {
class Confirm {
constructor(el) {
this.message = el.getAttribute('data-confirm')
if (this.message) {
el.form.addEventListener('submit', this.confirm.bind(this))
} else {
console && console.warn('No value specified in `data-confirm`', el)
}
}
confirm(e) {
if (!window.confirm(this.message)) {
e.preventDefault();
}
}
}
Array.from(document.querySelectorAll('[data-confirm]')).forEach((el) => {
new Confirm(el)
})
}
これでconfirmも動くようになり、削除などを伴う処理でもいい感じに処理ができるようになりました。
<%= button_to 'ログアウト', destroy_user_session_path, method: :delete, data: { confirm: 'ログアウトしてもよろしいですか?' } %>