質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

jQuery

jQueryは、JavaScriptライブラリのひとつです。 簡単な記述で、JavaScriptコードを実行できるように設計されています。 2006年1月に、ジョン・レシグが発表しました。 jQueryは独特の記述法を用いており、機能のほとんどは「$関数」や「jQueryオブジェクト」のメソッドとして定義されています。

Ajax

Ajaxとは、Webブラウザ内で搭載されているJavaScriptのHTTP通信機能を使って非同期通信を利用し、インターフェイスの構築などを行う技術の総称です。XMLドキュメントを指定したURLから読み込み、画面描画やユーザの操作などと並行してサーバと非同期に通信するWebアプリケーションを実現することができます。

Q&A

解決済

4回答

1654閲覧

【Ajax, Rails, いいね機能】2回連続いいねボタンを押すとInvalidAuthenticityTokenエラーが出てしまう

naota7118

総合スコア7

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

jQuery

jQueryは、JavaScriptライブラリのひとつです。 簡単な記述で、JavaScriptコードを実行できるように設計されています。 2006年1月に、ジョン・レシグが発表しました。 jQueryは独特の記述法を用いており、機能のほとんどは「$関数」や「jQueryオブジェクト」のメソッドとして定義されています。

Ajax

Ajaxとは、Webブラウザ内で搭載されているJavaScriptのHTTP通信機能を使って非同期通信を利用し、インターフェイスの構築などを行う技術の総称です。XMLドキュメントを指定したURLから読み込み、画面描画やユーザの操作などと並行してサーバと非同期に通信するWebアプリケーションを実現することができます。

0グッド

0クリップ

投稿2020/07/06 13:31

編集2020/07/06 13:42

前提・実現したいこと

Ruby on Railsで転職用の個人アプリを制作しています。
Ajaxを使っていいね機能を実装中に以下のエラーメッセージが発生しました。

2回以上連続で「いいね」ボタンを押すと下記のエラーメッセージが表示されます。
(1回だけ押した場合は問題なく動きます)

実現したい挙動は、連続で「いいね」ボタンを押しても問題なく非同期通信が行われて、「いいね」→「いいねを取り消す」または「いいねを取り消す」→「いいね」と変更できるようにすることです。

間違っているかもしれませんが、非同期通信ではRailsがデフォルトでやってくれているCSRF対策の適用外となるから、自分でAuthenticityTokenを生成する記述をしなければならないのではないかと考え、
自分でAuthenticityTokenを生成する記述を他の記事を参考に書いてみましたが、同じメッセージが表示されてしまいました。

もしお分かりになる方いましたら、ご教示頂ければ幸いです。

発生している問題・エラーメッセージ

ActionController::InvalidAuthenticityToken

該当のソースコード

#####jQuery(like.js)

js

1$(function(){ 2 3 function buildHTML(like){ 4 var html = `<form class="button_to" method="post" action="/posts/${like.post_id}/likes/${like.id}"> 5 <input type="hidden" name="_method" value="delete"> 6 <input type="submit" value="いいねを取り消す"> 7 </form> 8 <div class="likeCounts"> 9 いいね数: 10 ${like.counts} 11 </div>` 12 return html; 13 } 14 15 function buildDeleteHTML(like){ 16 var html = `<form class="button_to" method="post" action="/posts/${like.post_id}/likes"> 17 <input type="hidden" name="_method" value="good"> 18 <input type="submit" value="いいね"> 19 </form> 20 <div class="likeCounts"> 21 いいね数: 22 ${like.counts} 23 </div>` 24 return html; 25 } 26 27 $('.button_to').on('submit', function(e) { 28 e.preventDefault(); 29 $.ajaxPrefilter(function(options, originalOptions, jqXHR) { 30 var token; 31 if (!options.crossDomain) { 32 token = $('meta[name="csrf-token"]').attr('content'); 33 if (token) { 34 return jqXHR.setRequestHeader('X-CSRF-Token', token); 35 } 36 } 37 }); 38 if($('.button_to').children().is('#like')) { 39 var formData = new FormData(this); 40 var url = $(this).attr('action') 41 $.ajax({ 42 url: url, 43 type: "POST", 44 data: formData, 45 dataType: 'json', 46 processData: false, 47 contentType: false 48 }) 49 .done(function(data){ 50 $('.button_to').remove(); 51 $('.likeCounts').remove(); 52 var html = buildHTML(data); 53 $('.like').append(html); 54 console.log('good'); 55 // $('.form__submit').prop('disabled', false); 56 }) 57 .fail(function(){ 58 alert('エラー'); 59 }) 60 } else { 61 var formData = new FormData(this); 62 console.log(this); 63 var url = $(this).attr('action') 64 $.ajax({ 65 url: url, 66 type: "DELETE", 67 data: formData, 68 dataType: 'json', 69 processData: false, 70 contentType: false 71 }) 72 .done(function(data){ 73 $('.button_to').remove(); 74 $('.likeCounts').remove(); 75 var html = buildDeleteHTML(data); 76 $('.like').append(html); 77 // $('.form__submit').prop('disabled', false); 78 }) 79 .fail(function(){ 80 alert('エラー'); 81 }) 82 } 83 }) 84})

#####ルーティング(routes.rb)

ruby

1Rails.application.routes.draw do 2(中略) 3 resources :posts do 4 resources :comments, only: :create 5 resources :likes, only: [:create, :destroy] 6 end 7(中略) 8end

#####コントローラー(likes_controller.rb)

ruby

1class LikesController < ApplicationController 2 def create 3 @like = current_user.likes.create(post_id: params[:post_id]) 4 if @like.save 5 @likeCounts = Like.where(post_id: params[:post_id]) 6 respond_to do |format| 7 format.json 8 end 9 else 10 flash[:alert] = 'エラーが発生しました。' 11 redirect_to post_path(@like.post.id) 12 end 13 end 14 15 def destroy 16 @like = Like.find_by(post_id: params[:post_id], user_id: current_user.id) 17 @like.destroy 18 if @like.destroy 19 @likeCounts = Like.where(post_id: params[:post_id]) 20 respond_to do |format| 21 format.json 22 end 23 else 24 flash[:alert] = 'エラーが発生しました。' 25 redirect_to post_path(@like.post.id) 26 end 27 end 28end
ビュー(show.html.haml)

ruby

1.like 2 - if user_signed_in? 3 - if current_user.already_liked?(@post) 4 .likeIcon 5 %i.far.fa-heart.likeIcon-fa-heart.heart 6 - else 7 .likeIcon 8 %i.far.fa-heart.likeIcon-fa-heart 9 - if current_user.already_liked?(@post) 10 = button_to 'いいねを取り消す', post_like_path(@post), method: :delete, id: 'delete' 11 - else 12 = button_to 'いいね', post_likes_path(@post), id: 'like' 13 .likeCounts 14 いいね数: 15 = @post.likes.count

#####JSON(create.json.jbuilder)

ruby

1json.user_id @like.user_id 2json.post_id @like.post_id 3json.id @like.id 4json.counts @likeCounts.length

#####JSON(destroy.json.jbuilder)

ruby

1json.user_id @like.user_id 2json.post_id @like.post_id 3json.id @like.id 4json.counts @likeCounts.length

調べてわかったこと

エラー原因を調べる過程で下記のことを知りました。
・application_controller.rbのprotect_from_forgeryメソッドを定義したことにより、Railsが自動的にCSRF対策をしてくれている。
・CSRFとは、Webアプリケーションに存在する脆弱性、もしくはその脆弱性を利用した攻撃方法のことで、掲示板や問い合わせフォームなどを処理するWebアプリケーションが、本来拒否すべき他サイトからのリクエストを受信し処理してしまう。その対策をする必要がある。

・フォーム送信前のページでワンタイムトークンが生成され、フォーム送信ページにhiddenで生成したトークンが埋め込まれる。リクエスト送信先でセッションに格納されたトークンとリクエストとして送られてきたトークンが一致するか確認する。この確認で一致していないというのが今回のエラー?

仮説

AuthenticityTokenが生成されていない?
リロードすれば、「いいね」ボタンを押して問題なく非同期で動くことから、2回目に「いいね」ボタンを押した時はapplication_controller.rbのprotect_from_forgeryメソッドが機能していないのではないかと予想した。
非同期通信ではRailsがデフォルトでやってくれているCSRF対策の適用外となるから、自分でAuthenticityTokenを生成する記述をしなければならない?

試したこと

下記のコードをajaxメソッドの前に追記したが、うまくいかなかった。

$.ajaxPrefilter(function(options, originalOptions, jqXHR) { var token; if (!options.crossDomain) { token = $('meta[name="csrf-token"]').attr('content'); if (token) { return jqXHR.setRequestHeader('X-CSRF-Token', token); } } });

補足情報(FW/ツールのバージョンなど)

Ruby 2.5.1
Rails 5.2.4.3

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

jbuilderの記述は

json.extract! @like, :user_id, :post_id, :id json.counts @likeCounts.length

の方が短くて綺麗な気がします。

投稿2020/07/08 17:46

mugioka

総合スコア13

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

naota7118

2020/07/08 21:35

jbuilderの書き方でextract!メソッドというのがあることを初めて知りました。教えていただきありがとうございます。
guest

0

ベストアンサー

実装したい機能に対してのアプローチが少し冗長というか頑張りすぎてる気がしますね、、
このへん参考にして作ってみた方がいい気がします

投稿2020/07/08 17:30

編集2020/07/08 17:34
mugioka

総合スコア13

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

naota7118

2020/07/08 21:43

>実装したい機能に対してのアプローチが少し冗長というか頑張りすぎてる気がしますね、、 このアプローチを取ったのは、現在スクールに通っていまして、スクールのAjax実装のページを参考にするとこちらのアプローチに近い感じだったのが理由です。 でも現役のエンジニアの方からは冗長な印象を受けるというのは、自分にとっては発見でした。 このActionController::InvalidAuthenticityTokenというエラーさえ解決できればうまくいきそうなので、もう少しこのやり方でやってみようかなと思いますが、いつまでもここで止まっているわけにもいかないので、解決できなければ教えて頂いた記事のアプローチで進めようと思います。そちらのアプローチの方が必要最小限のコード量で済みそうですし。 長文失礼しました。教えていただきありがとうございます。
guest

0

あと、

javascript

1 function buildHTML(like){ 2 var html = `<form class="button_to" method="post" action="/posts/${like.post_id}/likes/${like.id}"> 3 <input type="hidden" name="_method" value="delete"> 4 <input type="submit" value="いいねを取り消す"> 5 </form> 6 <div class="likeCounts"> 7 いいね数: 8 ${like.counts} 9 </div>` 10 return html; 11 } 12 13 function buildDeleteHTML(like){ 14 var html = `<form class="button_to" method="post" action="/posts/${like.post_id}/likes"> 15 <input type="hidden" name="_method" value="good"> 16 <input type="submit" value="いいね"> 17 </form> 18 <div class="likeCounts"> 19 いいね数: 20 ${like.counts} 21 </div>` 22 return html; 23 }

上記のjsで追加するhtmlにid="like"がないと元々のビューにあるhtmlと違ってしまうかと思います。
また、value="good"goodの部分はhttpメソッドを入れるところだと思います。この場合はpostでしょうか。

投稿2020/07/07 03:51

gohsan53

総合スコア10

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

naota7118

2020/07/08 09:37

返信が大変遅れまして申し訳ございません。INPUT type="hidden"の時のvalue属性にはhttpメソッドが入るということを知りませんでした。大変勉強になりました。ありがとうございます。教えて頂いた箇所を修正いたしました。ちなみにエラーは今も変わらず表示されたままです。
naota7118

2020/07/09 11:05

その後無事いいね機能の非同期化を完了させることができました。自分では気付かない点を教えていただきありがとうございました!
guest

0

エラーに直接関係ないかもしれませんが、コントローラのdestroyアクションで@likeを2回消そうとしているのが気になりました。

ruby

1@like.destroy 2 if @like.destroy

投稿2020/07/06 14:57

編集2020/07/07 00:10
gohsan53

総合スコア10

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

naota7118

2020/07/07 00:25

gohsan53さん ご回答頂きありがとうございます。 自分では気づきませんでしたが、createアクションを見ると@like.saveは1回しか書いていなくてもsaveできているため、@like.destroyも教えて頂いた通り条件分岐の方だけでよく、1つ目は不要かもしれません。 帰宅しましたらすぐ修正いたします。 帰宅後の返信となるため、返信が遅くなりますことご容赦ください。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問