[Rails5]Action Cableのサンプルを読み解いてみる

  • 37
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

前回([Rails5]Action Cableを試してみる - Qiita)はActionCableのサンプルアプリケーションのインストールから動作を見るところまでやってみました。今回はソースコードから実装方法を見ていきます。

※公式のドキュメント(rails/actioncable)を見て書いていますが、誤りなどありましたらコメントなどでご指摘いただけると幸いです。

※2015/07/14時点のアルファ版の情報ですので、リリース時には変わっている可能性があります。

※書いてあることはかなり怪しい(調べきれてない)です。

前提となる知識

  • RedisのPub/Sub
  • Active Job

サーバサイド

Connectionの設定

まずはApplicationCable::Connectionクラスを使って、認証情報を定義します。

identified_byはコネクションを識別するキーとなるものです。connectメソッドはコネクションの接続時に呼ばれるメソッドでしょうか。ここではコネクションの識別キーとして、ログイン時に設定したCookieから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
      logger.add_tags 'ActionCable', current_user.name
    end

    protected
      def find_verified_user
        if verified_user = User.find_by(id: cookies.signed[:user_id])
          verified_user
        else
          reject_unauthorized_connection
        end
      end
  end
end

Channelの設定

次にApplicationCable::Channelクラスを使ってチャンネルを定義します。

チャンネルに定義したメソッドがクライアントとのインターフェイスになるようです。ここはアプリケーション全体のChannelの設定をする部分で、このサンプルでは特に何もありません。

app/channels/application_cable/channel.rb

module ApplicationCable
  class Channel < ActionCable::Channel::Base
  end
end

このサンプルで使用するChannelの設定はこちらです。

followメソッドがクライアントから呼び出されると、stream_fromメソッドによりRedisのPubSubのストリーミングが開始されます。

app/channels/comments_channel.rb

class CommentsChannel < ApplicationCable::Channel
  def follow(data)
    stop_all_streams
    stream_from "messages:#{data['message_id'].to_i}:comments"
  end

  def unfollow
    stop_all_streams
  end
end

メッセージの受信とブロードキャスト

画面から登録されたコメントはCommentsController#createへPOSTされ、Commentモデルのafter_commitにてCommentRelayJobに渡されます。

app/models/comment.rb

class Comment < ActiveRecord::Base
  belongs_to :message
  belongs_to :user

  after_commit { CommentRelayJob.perform_later(self) }
end

そしてCommentRelayJobがActionCable.server.broadcastにより登録されたコメントをクライアントにブロードキャストしています。

app/jobs/comment_relay_job.rb

class CommentRelayJob < ApplicationJob
  def perform(comment)
    ActionCable.server.broadcast "messages:#{comment.message_id}:comments",
      comment: CommentsController.render(partial: 'comments/comment', locals: { comment: comment })
  end
end

クライアント側の実装

サーバ設定

まずはじめにWebSocketのサーバを設定します。

app/assets/javascripts/channels/index.coffee

#= require cable
#= require_self
#= require_tree .

@App = {}
App.cable = Cable.createConsumer 'ws://localhost:28080'

コネクションの開始と受信

App.cable.subscriptions.createにより指定したチャンネルでサーバとの接続(Subscription)を開始しします。

接続が開始されたら@perform 'follow'によりサーバからのストリーミングが開始されます。(若干怪しいです...)

あとはreceivedにてサーバから送信されたデータを受信し、処理を行っています。

app/assets/javascripts/channels/comments.coffee

App.comments = App.cable.subscriptions.create "CommentsChannel",
  collection: -> $("[data-channel='comments']")

  connected: ->
    # FIXME: While we wait for cable subscriptions to always be finalized before sending messages
    setTimeout =>
      @followCurrentMessage()
      @installPageChangeCallback()
    , 1000

  received: (data) ->
    @collection().append(data.comment) unless @userIsCurrentUser(data.comment)

  userIsCurrentUser: (comment) ->
    $(comment).attr('data-user-id') is $('meta[name=current-user]').attr('id')

  followCurrentMessage: ->
    if messageId = @collection().data('message-id')
      @perform 'follow', message_id: messageId
    else
      @perform 'unfollow'

  installPageChangeCallback: ->
    unless @installedPageChangeCallback
      @installedPageChangeCallback = true
      $(document).on 'page:change', -> App.comments.followCurrentMessage()

まとめ

色々と怪しいところがありますが、最低限の双方向通信のインターフェイスは見えてきました。

  • クライアントからサーバへの通知はチャンネル内のメソッドを通じて行う
  • サーバからクライアントへの通知はRedisのPub/Subの仕組みを用いて行う(ってこと?チャンネルからもできて良さそうだけどどうなんだろう)

今までRailsでWebSocketを使ったことがないのでこれで楽になるのかはよく分かりませんが、少なくともActionCableにより1つのやり方が提示されたので、実装に迷うことはなくなりそうです。