はじめに
Railsで非同期通信をする際、リロードせずにbooleanを反転させたいケースがあり、やり方がわからず時間を費やしてしまったので自戒の意味を込めて記事に残させていただきます。
やりたいこと
↓画像のようなボタンを作り、非同期でbooleanが反転するようにしたい。
完成版パラメーター
【ボタンクリック1回目】
Parameters: {"user_groups"=>{"subscribed"=>"false"}}
【ボタンクリック2回目(非同期なのでリロードはしない)】
Parameters: {"user_groups"=>{"subscribed"=>"true"}}
ルーティング・コントローラー・ビュー
前提
以下の画像のようにuserとuser_groupは1対多の関係になっています。今回boolean反転させるのはuser_groups
テーブルのsubscribed
カラムです。
ルーティング
今回はユーザーが複数所属するグループが『グループ単位でメルマガを購読するかどうか』を変更するオプション(そんなのあまりないかもしれませんが、例としてです・・)をユーザー側で変更できる実装をする前提で、update_options
というアクション名にします。
update_subscribed
等の名前の方が良いかもしれませんが、今後同じような操作をするボタンを他にも作ることを想定して共通化された名前としています。
また今回使用するのはPATCHアクションのみなので、ルーティングもPATCHの部分のみ記載しています。
resources :user_groups do
member { patch 'update_options' => 'user_groups#update_options' }
end
コントローラー
class UserGroupsController < ApplicationController
before_action :set_group
def index #set_groupで定義した@groupをviewファイルで使用
end
def update_options
@group.update_attributes(user_group_params)
end
private
def user_params
params.require(:user_group).permit(:subscribed)
end
def set_group
@group = current_user.user_group.find_by(id: params[:id])
end
end
ログイン中のユーザーは以下でcurrent_userとして定義しています。
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
end
ビュー
スタイリングは本題とは外れるので大変恐縮ですが今回は触れずに行きます。
= link_to update_options_user_path(user_group: {subscribed: !@group.subscribed}), remote: true, method: :patch do
.btn
| 変更
以上で大体実装ができましたが、このままだとボタンを複数回押した際にパラメーターが切り替わりません。
実際に押してみると、
【ボタンクリック1回目】
Parameters: {"user_groups"=>{"subscribed"=>"false"}}
【ボタンクリック2回目】
Parameters: {"user_groups"=>{"subscribed"=>"false"}}
このように非同期でbooleanが反転されません。もちろんリロードすれば成功します。
解決法①
原因は、index.slimの**= link_to
部分がボタンを押した際に書き換えられていないので、パラメーターに変化がありませんでした。
ですので、update_options
アクション側でDBの値を得て変更させれば『非同期で複数回クリックしてもboolean値が切り替わる』**ことは実現できました。
class UserGroupsController < ApplicationController
def update_options
@group.update_attributes(subscribed: !@group.subscribed)
end
end
= link_to update_options_user_path, remote: true, method: :patch do
.btn
| 変更
このようにすれば、クリックを押すたびにDBのboolean値が切り替わります。
しかし、ユーザーが同時に複数人ログインしていてこのボタンを一斉に押されると、DBの値がユーザーの意図しない値に書き換わってしまうことも考えられるので、やはり**『パラメーターの値によってDBの値が変更される』**方が良さそうです。
解決法②(私はこちらを採用しました)
こちらの方法ではJavaScriptを使って**= link_to
**のvalue(今回はURL部分のみ)部分を、クリックするたびに書き換える実装をします。
= link_to update_options_user_path(user_group: {subscribed: !@group.subscribed}), id: "change-subscribed-link", remote: true, method: :patch do
.btn
| 変更
まずは先程のslimファイルの**= link_to
**部分にidを付与します。
id: "change-subscribed-link"
としました。
次に**update_options
アクションが走った時にJavaScriptが走るようにしたいので、update_options.js.erb
ファイルをindex.slim
**と同ディレクトリに作成し、以下をファイルに記載します。
$("#change-subscribed-link").attr("href", "<%= update_options_user_path(subscribed: {has_filed: !@group.subscribed}) %>")
このようにすることで、複数回クリックしても非同期でbooleanのパラメーターが反転するようになります。
こちらのコードではjQueryのセレクターで該当する**= link_to
**を持ってきて、中身のURL部分だけを上書きしています。
少し見にくいですが、検証ツールでどういう挙動なのか確認できます。
このaタグのhref
部分を**update_options.js.erb
**ファイルで書き換えたということです。
【ボタンクリック1回目】
Parameters: {"user_groups"=>{"subscribed"=>"false"}}
【ボタンクリック2回目(非同期なのでリロードはしない)】
Parameters: {"user_groups"=>{"subscribed"=>"true"}}
パラメーターも無事反転しました!
また今後別のボタンを作り、booleanのみを切り替える動作をさせたい場合は、**update_options
**アクションを使用すれば良いので、一からルーティング、コントローラーを追加する必要もなくなりました!
さいごに
誤っている箇所がありましたらご指摘いただきたく存じます。
また、他にも何か良いやり方等ありましたらご教示いただければ幸いです!