marrong
@marrong

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Rails ActionCableを用いたとき、ログインしているユーザーの値が取得できない。

現在、エンジニアを目指し、独学で学習を進めています。
初めて質問を利用させていただきます。
今回の質問はポートフォリオ作成の過程で出てきた疑問で解決できず困っています。
皆様の知識とお力をお貸しください。

内容としては下記のとおりです。
Railsを使ってWebアプリの作成を行っており、フロントエンド側の処理はJavesciptを使用しています。
制作するアプリは、学習時間をタイマーで測り、学習時間を集計、トータルの学習時間を確認できるようするためのものです。

制作するにはJavesciptとデータベースで相互にデータのやり取りが必要であったため、RailsのActionCableの機能を利用して行うことにしました。

なんとか、Javesciptで処理した値をデータベースに保存することはできましたが、ログインしているユーザーのIDも一緒に保存しようとすると、保存できませんでした。
データベースの構成は下記のとおりです。
a.PNG

①下記のように記載すると、User_idは空白になるものの、データベースに保存はできる状態です。

app/channels/timer_channel.rb
def listset(data)
    @list = List.new(list_id: data["list_id"], list_content: data["list_content"])
    @list.save
    ActionCable.server.broadcast "timer_channel", data["list_id", "list_content"]
end

②下記のように記載すると、@current_userのIDが取得できずデータベースに保存されませんでした。

app/channels/timer_channel.rb
def listset(data)
    @list = List.new(user_id: @current_user.id, list_id: data["list_id"], list_content: data["list_content"])
    @list.save
    ActionCable.server.broadcast "timer_channel", data["list_id", "list_content"]
end

Aplication_controller.rbでは下記のようにログイン情報を保持していました。

aplication_controller.rb
class ApplicationController < ActionController::Base
    before_action :set_current_user

    def set_current_user
        @current_user = User.find_by(id: session[:user_id])
    end
end

解決策を探るため、調べてみると、下記のサイトに『WebSocketサーバからはセッションが利用できないが、Cookieにはアクセスが可能』記載されていたため、代わりにCookieの値を保存して、利用してみることにしました。
参考サイト:https://railsguides.jp/action_cable_overview.html

Cookieの保存はログイン時に行うため、User_controller.rbに定義し、それをChannelで利用できるか試すことにしました。
下記のように定義しています。

app/controllers/users_controller.rb
  def login
    @user = User.find_by(email: params[:email], password: params[:password])
    if @user
      session[:user_id] = @user.id
      cookies.encrypted[:user_id] = @user.id 
      flash[:notice] = "ログインしました"
      redirect_to("/users/timer")
    else
      @email = params[:email]
      @password = params[:password]
      @error_message = "Emailまたは、Passwordが正しくありません"
      render("users/login_form")
    end
  end
app/channels/timer_channel.rb
def listset(data)
    @list = List.new(user_id: cookies.encrypted[:user_id], list_id: data["list_id"], list_content: data["list_content"])
    @list.save
    ActionCable.server.broadcast "timer_channel", data["list_id", "list_content"]
end

しかし、依然と同様に値が取得できていないようで、データベースにUser_idをデータベースに保存することができませんでした。
Application.jsの turbolinks require_tree を切っていたりするのでそこに原因なのかもしれないと思いつつも、もうお手上げ状態です。
、お力添えいただけますと幸いです。

1

1Answer

参考ページのように Connection で current_user をセットして、 Channel では current_user.id を使えばどうでしょうか。

app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
    end

    private
      def find_verified_user
        if verified_user = User.find_by(id: cookies.encrypted[:user_id])
          verified_user
        else
          reject_unauthorized_connection
        end
      end
  end
end
app/channels/timer_channel.rb
def listset(data)
    @list = List.new(user_id: current_user.id, list_id: data["list_id"], list_content: data["list_content"])
    @list.save
    ActionCable.server.broadcast "timer_channel", data["list_id", "list_content"]
end
1Like

Comments

  1. @marrong

    Questioner

    uasiさん、回答ありがとうございます。
    おかげさまでデータベース内にUser_idを保存することができました!
    本当にありがとうございます!

    ちなみに、connection.rbで記載したcurrent_user(アクション?)が読み込める理由は、『サーバーでWebSocketを受け付けるたびに、コネクションのオブジェクトがインスタンス化して、今後作成されるすべてのチャネルサブスクライバの親となるから』という認識で良いのでしょうか。
  2. 概念的にはその通りです。ただクラスとして親子関係になるわけではなく、 Connection で定義した identified_by :xxx がその子に当たる Channel から呼び出せるように動的に委譲メソッドが作られます。
  3. @marrong

    Questioner

    なるほど!
    厳密には親子関係ではなく、Channelから呼び出せる動的なメソッドになるのですね!

    ようやくRailsガイドの下記の一文が理解できた気がします。
    「identified_byはコネクションIDであり、後で特定のコネクションを見つけるときに利用できます。IDとしてマークされたものは、そのコネクション以外で作成されるすべてのチャネルインスタンスに、同じ名前で自動的にデリゲートを作成されます」

    質問にお答えいただき、ありがとうございました!
    またご機会ございましたら、よろしくお願いいたします!

Your answer might help someone💌