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

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

ただいまの
回答率

89.23%

【Ruby on Rails】フォロー機能の非同期が一部反映されない。

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 228

is02

score 13

前提・実現したいこと

フォロー機能の非同期が一部出来ない。
イメージ説明

上記の画像のように、画像を投稿した最新のものには、非同期フォローが出来ているんですけど、
過去に投稿したものには非同期で反映されません。(リロードすると反映されます)

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

検証ツールのconsoleにて、非同期が出来ていない部分を確認してみると、
500 (Internal Server Error)というエラーが出ていました。

該当のソースコード

モデル

user.rb

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :post_images, dependent: :destroy
  has_many :favorites, dependent: :destroy
  has_many :fav_post_images, through: :favorites, source: :post_image
  has_many :cosplay_favorites, dependent: :destroy
  has_many :cosplay_fav_post_images, through: :favorites, source: :post_image
  has_many :post_comments, dependent: :destroy
  has_many :relationships
  has_many :followings, through: :relationships, source: :follow
  has_many :reverse_of_relationships, class_name: 'Relationship', foreign_key: 'follow_id'
  has_many :followers, through: :reverse_of_relationships, source: :user

  # 2~8文字以内で名前が入っているかの確認
  validates :name, presence: true, length: { in: 2..8 }, uniqueness: true
  # 自己紹介 100文字以内
  validates :introduction, length: { maximum: 100 }
  # 可能なコスプレ 100文字以内
  validates :like_cos, length: { maximum: 100 }

  # refile定義
  attachment :profile_image

  # フォロー機能のメソッド
  def follow(other_user)
    unless self == other_user # フォローしようとしている人が自分自身ではないか
      # フォローしようとしている人が自分以外ならフォローする
      self.relationships.find_or_create_by(follow_id: other_user.id)
    end
  end

  def unfollow(other_user)
    relationship = self.relationships.find_by(follow_id: other_user.id)
    relationship.destroy if relationship # フォローしていたらアンフォローする
  end

  def following?(other_user)
    self.followings.include?(other_user) # other_userが含まれていたらtrueを返す
  end
end


relationship.rb

class Relationship < ApplicationRecord
  belongs_to :user
  # userモデルを参照する
  belongs_to :follow, class_name: 'User'

  validates :user_id, presence: true
  validates :follow_id, presence: true
end


post_image.rb

class PostImage < ApplicationRecord
    belongs_to :user
    has_many :favorites, dependent: :destroy
    has_many :fav_users, through: :favorites, source: :user
    has_many :cosplay_favorites, dependent: :destroy
    has_many :cosplay_fav_users, through: :cosplay_favorites, source: :user
    has_many :post_comments, dependent: :destroy

    # refile定義
    attachment :real_image
    attachment :cosplay_image

    validates :real_image, presence: true
    validates :cosplay_image, presence: true

    # 投稿を降順に並び替えし、最新のものを上にくるようにする
    default_scope -> { order(created_at: :asc) }
end

コントローラー

users_controller.rb

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    @post_images = @user.post_images
  end

  def edit
    @user = User.find(params[:id])
  end

  def update
    @user = User.find(params[:id])
    if @user.update(user_params)
      redirect_to user_path(@user.id)
    else
      render 'edit'
    end
  end

  def following
    @user = User.find(params[:id])
    @users = @user.followings
    render 'show_follow'
  end

  def followers
    @user = User.find(params[:id])
    @users = @user.followers
    render 'show_follower'
  end

  private

  def user_params
    params.require(:user).permit(:name, :profile_image, :introduction, :like_cos)
  end
end


relationships_controller.rb

class RelationshipsController < ApplicationController
  before_action :set_user

  def create
    following = current_user.follow(@user)
    following.save
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end

  def destroy
    following = current_user.unfollow(@user)
    following.destroy
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end

  private

  def set_user
    @user = User.find(params[:follow_id])
  end
end


post_images_controller.rb

class PostImagesController < ApplicationController
    def new
        @post_image = PostImage.new
    end

    # 投稿データの保存
    def create
        @post_image = PostImage.new(post_image_params)
        @post_image.user_id = current_user.id
        if @post_image.save
          redirect_to post_images_path
        else
          render 'new'
        end
    end

    def index
        @post_images = PostImage.page(params[:page]).reverse_order
    end

    def show
        @post_image = PostImage.find(params[:id])
        @post_comment = PostComment.new
    end

    def destroy
        @post_image = PostImage.find(params[:id])
        @post_image.destroy
        redirect_to post_images_path
    end

    private

    # 投稿データのストロングパラメータ
    def post_image_params
        params.require(:post_image).permit(:real_image_name, :cosplay_image_name, :real_image, :cosplay_image, :caption, :favorites_count)
    end
end

ビュー

relationships/_follow_button.rb

<% unless current_user == user %>
  <div id="follow_form">
    <% if current_user.following?(user) %>
      <%= form_for(current_user.relationships.find_by(follow_id: user.id), html: { method: :delete }, remote: true) do |f| %>
        <%= hidden_field_tag :follow_id, user.id %>
        <%= f.submit 'フォロー中' %>
      <% end %>
    <% else %>
      <%= form_for(current_user.relationships.build, remote: true) do |f| %>
        <%= hidden_field_tag :follow_id, user.id %>
        <%= f.submit 'フォローする' %>
      <% end %>
    <% end %>
  </div>
<% end %>


create.js.erb

$("<%= '#follow_form' %>").html('<%= escape_javascript(render("relationships/follow_button", user: @user  )) %>');


destroy.js.erb

$("<%= '#follow_form' %>").html('<%= escape_javascript(render("relationships/follow_button", user: @user  )) %>');


post_images/index.html.erb

<% @post_images.each do |post_image| %>
  <%= render 'relationships/follow_button', user: post_image.user %>
<% end %>

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

ruby 2.5.7p206
Rails 5.2.4.1

追記

_follow_button.html.erb

<% unless current_user == user %>
  <div id="follow_form_<%= user.id %>">
    <% if current_user.following?(user) %>
      <%= form_for(current_user.relationships.find_by(follow_id: user.id), html: { method: :delete }, remote: true) do |f| %>
        <%= hidden_field_tag :follow_id, user.id %>
        <%= f.submit 'フォロー中' %>
      <% end %>
    <% else %>
      <%= form_for(current_user.relationships.build, remote: true) do |f| %>
        <%= hidden_field_tag :follow_id, user.id %>
        <%= f.submit 'フォローする' %>
      <% end %>
    <% end %>
  </div>
<% end %>

create.js.erb, destroy.js.erb

$("<%= @userid %>").html('<%= escape_javascript(render("relationships/follow_button", user: @user  )) %>');

relationships_controller.rb

class RelationshipsController < ApplicationController
  before_action :set_user

  def create
    following = current_user.follow(@user)
    following.save
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end

  def destroy
    following = current_user.unfollow(@user)
    following.destroy if following
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end

  private

  def set_user
    @user = User.find(params[:follow_id])
    @userid = "#follow_form_#{@user.id}"
  end
end
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • is02

    2020/02/07 23:28

    【非同期出来ている箇所のログ】
    「フォローを外すときのログ」
    Started DELETE "/relationships/43" for 10.0.2.2 at 2020-02-07 14:27:10 +0000
    Processing by RelationshipsController#destroy as JS
    Parameters: {"utf8"=>"?", "follow_id"=>"5", "commit"=>"フォロー中", "id"=>"43"}
    User Load (5.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 4], ["LIMIT", 1]]
    ? /var/lib/gems/2.5.0/gems/activerecord-5.2.4.1/lib/active_record/log_subscriber.rb:98
    User Load (5.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 5], ["LIMIT", 1]]
    ? app/controllers/relationships_controller.rb:25
    Relationship Load (6.7ms) SELECT "relationships".* FROM "relationships" WHERE "relationships"."user_id" = ? AND "relationships"."follow_id" = ? LIMIT ? [["user_id", 4], ["follow_id", 5], ["LIMIT", 1]]
    ? app/models/user.rb:37
    (0.3ms) begin transaction
    ? app/models/user.rb:38
    Relationship Destroy (30.3ms) DELETE FROM "relationships" WHERE "relationships"."id" = ? [["id", 43]]
    ? app/models/user.rb:38
    (30.9ms) commit transaction
    ? app/models/user.rb:38
    (0.2ms) begin transaction
    ? app/controllers/relationships_controller.rb:15
    (0.2ms) commit transaction
    ? app/controllers/relationships_controller.rb:15
    Rendering relationships/destroy.js.erb
    User Exists (5.4ms) SELECT 1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follow_id" WHERE "relationships"."user_id" = ? AND "users"."id" = ? LIMIT ? [["user_id", 4], ["id", 5], ["LIMIT", 1]]
    ? app/models/user.rb:42
    Rendered relationships/_follow_button.html.erb (19.3ms)
    Rendered relationships/destroy.js.erb (190.5ms)
    Completed 200 OK in 960ms (Views: 659.6ms | ActiveRecord: 84.8ms)

    「フォローするときのログ」
    Started POST "/relationships" for 10.0.2.2 at 2020-02-07 14:28:18 +0000
    (6.7ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
    ? /var/lib/gems/2.5.0/gems/activerecord-5.2.4.1/lib/active_record/log_subscriber.rb:98
    Processing by RelationshipsController#create as JS
    Parameters: {"utf8"=>"?", "follow_id"=>"5", "commit"=>"フォローする"}
    User Load (6.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 4], ["LIMIT", 1]]
    ? /var/lib/gems/2.5.0/gems/activerecord-5.2.4.1/lib/active_record/log_subscriber.rb:98
    User Load (3.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 5], ["LIMIT", 1]]
    ? app/controllers/relationships_controller.rb:25
    Relationship Load (4.1ms) SELECT "relationships".* FROM "relationships" WHERE "relationships"."user_id" = ? AND "relationships"."follow_id" = ? LIMIT ? [["user_id", 4], ["follow_id", 5], ["LIMIT", 1]]
    ? app/models/user.rb:32
    (0.1ms) begin transaction
    ? app/models/user.rb:32
    User Load (4.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 5], ["LIMIT", 1]]
    ? app/models/user.rb:32
    Relationship Create (18.6ms) INSERT INTO "relationships" ("user_id", "follow_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["user_id", 4], ["follow_id", 5], ["created_at", "2020-02-07 14:28:18.906367"], ["updated_at", "2020-02-07 14:28:18.906367"]]
    ? app/models/user.rb:32
    (18.5ms) commit transaction
    ? app/models/user.rb:32
    (0.1ms) begin transaction
    ? app/controllers/relationships_controller.rb:6
    (0.1ms) commit transaction
    ? app/controllers/relationships_controller.rb:6
    Rendering relationships/create.js.erb
    User Exists (3.7ms) SELECT 1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follow_id" WHERE "relationships"."user_id" = ? AND "users"."id" = ? LIMIT ? [["user_id", 4], ["id", 5], ["LIMIT", 1]]
    ? app/models/user.rb:42
    Relationship Load (4.6ms) SELECT "relationships".* FROM "relationships" WHERE "relationships"."user_id" = ? AND "relationships"."follow_id" = ? LIMIT ? [["user_id", 4], ["follow_id", 5], ["LIMIT", 1]]
    ? app/views/relationships/_follow_button.html.erb:4
    Rendered relationships/_follow_button.html.erb (34.0ms)
    Rendered relationships/create.js.erb (145.7ms)
    Completed 200 OK in 874ms (Views: 450.4ms | ActiveRecord: 107.9ms)

    キャンセル

  • is02

    2020/02/07 23:30

    【非同期出来ていない箇所のログ】
    「フォローするときのログ」
    Started POST "/relationships" for 10.0.2.2 at 2020-02-07 14:29:08 +0000
    (6.7ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
    ? /var/lib/gems/2.5.0/gems/activerecord-5.2.4.1/lib/active_record/log_subscriber.rb:98
    Processing by RelationshipsController#create as JS
    Parameters: {"utf8"=>"?", "follow_id"=>"5", "commit"=>"フォローする"}
    User Load (3.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 4], ["LIMIT", 1]]
    ? /var/lib/gems/2.5.0/gems/activerecord-5.2.4.1/lib/active_record/log_subscriber.rb:98
    User Load (3.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 5], ["LIMIT", 1]]
    ? app/controllers/relationships_controller.rb:25
    Relationship Load (5.3ms) SELECT "relationships".* FROM "relationships" WHERE "relationships"."user_id" = ? AND "relationships"."follow_id" = ? LIMIT ? [["user_id", 4], ["follow_id", 5], ["LIMIT", 1]]
    ? app/models/user.rb:32
    (0.1ms) begin transaction
    ? app/controllers/relationships_controller.rb:6
    User Load (3.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 5], ["LIMIT", 1]]
    ? app/controllers/relationships_controller.rb:6
    (0.2ms) commit transaction
    ? app/controllers/relationships_controller.rb:6
    Rendering relationships/create.js.erb
    User Exists (2.8ms) SELECT 1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follow_id" WHERE "relationships"."user_id" = ? AND "users"."id" = ? LIMIT ? [["user_id", 4], ["id", 5], ["LIMIT", 1]]
    ? app/models/user.rb:42
    CACHE Relationship Load (0.0ms) SELECT "relationships".* FROM "relationships" WHERE "relationships"."user_id" = ? AND "relationships"."follow_id" = ? LIMIT ? [["user_id", 4], ["follow_id", 5], ["LIMIT", 1]]
    ? app/views/relationships/_follow_button.html.erb:4
    Rendered relationships/_follow_button.html.erb (23.4ms)
    Rendered relationships/create.js.erb (91.1ms)
    Completed 200 OK in 551ms (Views: 267.8ms | ActiveRecord: 50.8ms)

    非同期出来ていない箇所は、フォロー外すときのログが出ませんでした。

    キャンセル

  • winterboum

    2020/02/08 06:07

    14 following = current_user.unfollow(@user)
    15 following.destroy
    14で該当するunfollowがなかったのでnilが入りそれで15となりました。
    following.destroy if follwing とでもしてください。
    そもそもdestroyすべき対象が居ないの削除ボタンが機能するのがおかしいですが

    キャンセル

回答 2

checkベストアンサー

+1

同一ページ内にid="follow_form"の要素が複数個存在しているのが原因です。
jsの$("<%= '#follow_form' %>")は最初に見つかった#follow_form要素を返すので
最新のものしかフォローが反映されないと思ったのもそのためでしょう。
同一ページ内で要素のIDが重複しないようにしましょう。

ex.

<%# relationships/_follow_button.html.erb %>
  <div id='follow_form_<%= user.id %>>
<%# create.js.erb %>
<%# destroy.js.erb %>
$('<%= "#follow_form_#{@user.id}" %>').html('<%= escape_javascript(render("relationships/follow_button", user: @user  )) %>');


erbの書き方に慣れてないのでもしかしたら書き方違うかも。

追記

もしかして...

<%# relationships/_follow_button.html.erb %>
  <div class='follow_form_<%= user.id %>>
<%# create.js.erb %>
<%# destroy.js.erb %>
$('<%= ".follow_form_#{@user.id}" %>').html('<%= escape_javascript(render("relationships/follow_button", user: @user  )) %>');

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/02/09 23:31

    お二方、回答ありがとうございます。
    仰った書き方でやってみて反応しなかったので、追記に書いたやり方で書いてみたのですが、投稿が最新のものしか反応してくれません。アドバイス頂けると幸いです。

    キャンセル

  • 2020/02/09 23:43

    回答に書いたのは @userid ではなく @user.id です。
    あと、適当に user.id としてますが、この @post_images.each do ... end で同じユーザーの投稿が複数表示される場合同じくIDが重複してしまうのでその場合はpost_imageのidでIDをつけるなり重複しないようにしてくださいね。

    キャンセル

  • 2020/02/09 23:46 編集

    もしかしてと思ったので一応書いておきますが、回答にも書いた通り、ID指定では要素は最初に見つかった1つしか取得できないので、もし複数の要素を一気に変更したいのであればclassを使ってください。classはページ内で重複してもOKです。同じclassはまとめて取得できてまとめて変更できます。

    回答にも追記しておきました。

    キャンセル

  • 2020/02/09 23:54

    ありがとうございます!なるほど、id指定だと1つしか要素を取得できないんですね。クラス指定でやったら出来ました!

    Mugheartさん、winterboumさん、長らくお付き合い頂き本当にありがとうございました。
    今後ともよろしくお願いします。

    キャンセル

+1

最初の書き込みで勘違いしたのは、以下の現象からです。

  def create
    following = current_user.follow(@user)
    following.save


「失敗」のlogはこの部分でINSERTされていません。つまり、既にそのデータは有ったと言うことを意味しています。にも関わらず フォローするボタンが出ていたのはおかしいですね。
ここを詰める必要がありそうです。
User、Relationship の状態を一覧し、
viewでのボタンの出方を確認し、
ボタンを押した後のRelationship の状態を確認し
viewの出方を確認する
というのを見てみたい


いろいろ問題がありそうですが、まずここがおかしいのでは?
def following?(other_user)   self.followings.include?(other_user) # other_userが含まれていたらtrueを返す end
<% if current_user.following?(user) %>

current_user : loginしているユーザ
user : 画面に表示されているユーザ
ですから、
この if 文は userとcurrent_user が入れ替わっているようです
修正
勘違い。follow の方向を逆に見てました、ごめんなさい。
も少し考えて回答し直します

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/02/09 11:28

    それと2020/02/08 23:55にコメントした中にあるlog編集しなおしてください。
    1)不要な部分があるので削除 Started GET から始まり次のStartの直前まで
    2)POST,DELETE の始まりの説明が例えば 「フォロー中」→「フォローする」 なのを、以下を追加してください
    ・誰がloginし、誰のにしようとしているのか(user.idこみで)
    ・成功したのか失敗したノア

    キャンセル

  • 2020/02/09 13:21

    #follow_form が index ページに複数あるのが原因ですよ。
    jsで $("<%= '#follow_form' %>") としていますが、これは最初に見つかった #follow_form 要素を返すので最新のものしかフォローが反映されないと思ったのもそのためでしょう。
    それそれの要素のIDがページ内で重複しないようにしましょう。

    キャンセル

  • 2020/02/09 14:45

    そか。
    Mugheartさん、それで回答書いてください

    キャンセル

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

  • ただいまの回答率 89.23%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる