Help us understand the problem. What is going on with this article?

最短で作るActionCableチャットアプリ

More than 3 years have passed since last update.

目次

  • 初めに
  • WebSocket と ActionCable
  • Pub/Sub モデルの説明
  • ActionCable の構成要素
  • チャットアプリの作成
  • 終わりに

初めに

ActionCable を説明した記事や閲覧できるソースコードは色々ありますが、だいたい要件に合わせて機能が追加されていたり他の機能も取り込んでいたりして、必要最小限の実装のみ解説している記事がなかなか見つかりませんでした。この記事は ActionCable に必要な最小限の内容に注目しているので応用が効きます。多分これが最短だと思います。

WebSocket と ActionCable

WebSocketとはHTTP上で双方向リアルタイム通信を実現するためのプロトコルです。これとRailsをシームレスに接続するための技術がActionCableであり、 Rails 5.0 から Rails の一部に含まれます。 ActionCable を利用することで、リアルタイムにメッセージをやりとりするアプリが実装できます。

Pub / Sub モデル

WebSocket は "Pub / Sub" と呼ばれるメッセージプロトコルに従っています。アプリを作成する上でこの概念を理解しておくと、書くべきコードの理解が進みます。

メールなど一般のメッセージが「送信者」と「受信者」から構成されるのに対し、 WebSocket は「出版者 (publisher)」と「購読者 (subscriber)」及び「チャネル」から構成されます。

  • 出版者は特に宛先を指定せず、メッセージをチャネルに配信する。
  • 購読者はチャネルを購読する。
  • チャネルにメッセージが送信(ブロードキャスト)されると、購読者はメッセージを取得できる。

ここで、1つのチャネルは複数の購読者が購読できます。購読者は「チャネルにメッセージがブロードキャストされた時」の処理を定義できるため、リアルタイムな処理を実装てきます。

ActionCableの構成要素

典型的な ActionCable アプリケーションに必要な実装です。

コネクション

ファイル: app/channels/application_cable/connection.rb

クライアント (フロント) とサーバーの接続自体を表現します。セッションの値を取得できるので、アカウント認証に使用されます。

チャネル

リクエストを処理し、レスポンスを返すことを受け持ちます。通常の Rails アプリケーションでいう Controller に対応しますが、 ActionCable では上記 Pub/Sub モデルに対応した記法を使用します。

ファイル: app/channels/application_cable/channel.rb

基底クラスです。このクラスを継承することで ActionCable における Channel として振る舞うことができます。何も追記しないことが多いですが、チャネルが複数あり、共通の処理を実装したい場合はここに書けます。

ファイル: app/channels/application_cable/*_channel.rb

ここにそれぞれの処理を実装します。

クライアント

ファイル: app/assets/javascripts/cable.js

クライアントに ActionCable ライブラリを読み込ませるためのファイルです。このファイルで window.App というオブジェクトが定義され、クライアントのどこからでも ActionCable のメソッドを使用することができます。最初に生成されたあとは基本的に編集しません。こんな内容です。

app/assets/javascripts/cable.js
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
//
//= require action_cable
//= require_self
//= require_tree ./channels

(function() {
  this.App || (this.App = {});

  App.cable = ActionCable.createConsumer();

}).call(this);

ファイル: app/assets/javascripts/channels/*.js

チャネル関係の「接続した時」「受信した時」「接続を拒否された時」及び「チャネルにブロードキャストする」処理をここに実装します。

チャットアプリの作成

ベースになるプロジェクト

実際にRailsアプリケーションを作成しながらActionCable
最初にRailsプロジェクトを新規作成します。

$ rails new cable_chat_tmp

個人的な趣味ですが、Slimが好きでCoffeeScriptは嫌いなのでそうします。 jQueryは評価が分かれるところですが入れておきます。

gem 'slim-rails'
gem 'jquery-rails'
gem 'coffee-rails', '~> 4.2' # 削除
app/assets/javascripts/application.js
//= require rails-ujs
//= require turbolinks
//= require jquery
//= require jquery_ujs
//= require_tree .
$ bundle install

ルーティングを定義します。

config/routes.rb
Rails.application.routes.draw do
  root to: 'top#show'
end
$ rails g controller top
app/controllers/top_controller.rb
class TopController < ApplicationController
  def show
  end
end

ビューを作成します。これで最低限ページを表示できるので、 rails server を立ち上げてブラウザで確認します。ここまでは通常のRailsアプリケーションです。

app/views/top/show.html.slim
h1 Chat

チャネルを生成します。専用のコマンドがあるので利用します。

$ rails g channel chat post

以下のファイルができます。

app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    # stream_from "some_channel"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def post
  end
end
app/assets/javascripts/channels/chat.js
App.chat = App.cable.subscriptions.create("ChatChannel", {
  connected: function() {
    // Called when the subscription is ready for use on the server
  },

  disconnected: function() {
    // Called when the subscription has been terminated by the server
  },

  received: function(data) {
    // Called when there's incoming data on the websocket for this channel
  },

  post: function() {
    return this.perform('post');
  }
});

この時点でJavaScriptからActionCableの機能を呼び出すインスタンス App が使えます。rails server を起動し、ブラウザで localhost を開き、開発者ツールのコンソールで確認します。

> App
<- Object {cable: Consumer, chat: Subscription}
> App.cable
<- Consumer {url: "ws://localhost:3000/cable", subscriptions: Subscriptions, connection: Connection}

入力の受け取り

ここからは実際に入力を受け取ってコンテンツに反映する処理を実装していきます。
チャットの入力フォームを作成します。この時 data-behavior 属性を必ず指定します。(Railsの form_tag メソッドは色々余計なものが付いてくるので使いません)

app/views/top/show.html.slim
h1 Chat

form
  input#chat-form type="text" data-behavior="chat_post"

フォームの入力を受け取る部分を実装します。動作確認のため、処理はアラートを表示するだけにします。

app/assets/javascripts/channels/chat.js
App.chat = App.cable.subscriptions.create("ChatChannel", {

  // 中略

  post: function(message) {
    window.alert(message);
    return this.perform('post');
  }
}, $(document).on('keypress', '[data-behavior~=chat_post]', function(event) {
  if (event.keyCode === 13) {
    var chatForm = $('#chat-form');
    App.chat.post(chatForm.val());
    return chatForm.val('');
  }
}));

, $(document)... から先(形式上は App.cable.subscriptions.create メソッドの第3引数)がフロントのイベントを受け取る処理です。 '[data-behavior~=chat_post]' という見慣れない形式の文字列がありますが、これでイベントを取得するHTML要素を指定します。

ここまでで、フォームに何かしら入力してエンターキーを押すとその内容のアラートが表示されます。

actioncable_alert.png

サーバー側実装

入力内容をサーバーに送信する処理を実装します。先ほどのアラートを表示する処理は消して以下の通り書き換えます。

app/assets/javascripts/channels/chat.js
App.chat = App.cable.subscriptions.create("ChatChannel", {
  // 中略

  post: function(message) {
    return this.perform('post', { message: message });
  }

  // 中略
});

perform メソッドにより、フロントのデータをサーバーに送信することができます。第1引数にサーバー側のメソッド名、第2引数に送信するデータを指定します。

サーバー側を実装します。メソッド post 内でデバッガを使用すると、フォームの内容が取得できていることが分かります。今回は機能がシンプルなため遠回りしている印象を受けますが、実際はここでDBと接続したり外部アプリと連携したりできます。

app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from 'chat_channel'
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def post(data)
    ActionCable.server.broadcast('chat_channel', data)
  end
end

ビューに反映

JavaScriptを使用して受け取ったメッセージをビューに反映します。まずは表示部分を作成します。

app/views/top/show.html.slim
h1 Chat

ul#chat-index
  li Posts

form
  input#chat-form type="text" data-behavior="chat_post"

メッセージを受け取った時の処理を実装します。今回は単に append するだけです。

app/assets/javascripts/channels/chat.js
App.chat = App.cable.subscriptions.create("ChatChannel", {
  // 中略

    received: function(data) {
    return $('#chat-index').append('<li>' + data['message'] + '</li>');
  },

  // 中略
});

以上で完成です! 動作確認してみましょう。今回DBを経由していないので送信側はリロードが走ってしまいメッセージが消えてしまいますが、複数ウィンドウを開くと想定した動作になっていることが確認できます。

actioncable_work.png

終わりに

今回はActionCableの説明に注目したため、要素を削って説明しました。このためDBとの連携やフロント実装の深いところなどの内容はありませんが、応用は効かせられると思います。

また、今回作成したソースコードはこちらです→ strviola/cable_chat_tmp

theport
ポート株式会社 PORT INC.は、「世界中に、アタリマエとシアワセを。」をミッションに、新しい時代の1ページを創る会社です。【PORTもくもく会】https://freestyle-mokumoku.connpass.com/
https://www.theport.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした