Ruby on Rails5.0.0.rc1のAction Cableを使ってチャットアプリを作ろう!#Rails #coedorb


Rails開発チームから、5月6日(現地時間)に、Rails5系の最初のリリース候補「Rails 5.0.0 rc1」がリリースされました。
今回は、「Action Cable」を利用して、チャットシステムを作る手順をメモしていきたいと思います。

Rails5では、下記の変更が予定されています。
参照サイト

  • WebSocketを扱うためのフレームワーク「Action Cable」の追加
  • Rails APIモードのコアへのマージ
  • bin/rakeのbin/railsへの統合
  • Turbolinksの改良

参考にさせていただいたサイト

Rails 5 + ActionCableで作る!シンプルなチャットアプリ(DHH氏のデモ動画より)

上記のサイトは、 Rails 5.0.0.beta1環境での解説を行ってくださっていました。
今回、私が試そうとした時には、最新のRails5が Rails 5.0.0.rc1 になっていたため、いくつか手順が変わっていました。
そこで、開発のコードはそのまま使わせていただいて Rails 5.0.0.rc1 での変更点を押さえながら、構築手順をメモ書きしたいと思います。

rc1からの変更点

大きな変更としては、bata1までは、 Redis が必要でしたが、 bata2 から不要になっています。
rc1 では、 sqlite3 でも MySQL でも動作することを確認しました。
その他にも、チャンネル作成時の生成ファイルや、いくつかの修正が不要になっています。
この後の構築手順で確認しながら進めていきたいと思います。

では早速、チャットアプリの開発を進めていきたいと思います。
(以下 「$」 で始まる行は、コマンドを実行します。 「$」 以降を入力してください。)

プロジェクトのディレクトリ作成

$ mkdir chat_rails5_rc1
$ cd chat_rails5_rc1

現状のバージョン確認

$ ruby -v

ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-darwin14]

$ rails -v

Rails 4.2.4

私の環境では、このようなバージョンでした。

Gemfileのテンプレート作成

$ bundle init

Writing new Gemfile to /<任意のパス>/rails5_rc1_chat/Gemfile

Gemfileを編集

source "https://rubygems.org"

gem 'rails', '>= 5.0.0.rc1', '< 5.1'

Railsのプロジェクト生成

$ bundle exec rails new .

       exist
      create  README.md
      create  Rakefile
      create  config.ru
      create  .gitignore
    conflict  Gemfile
Overwrite /Users/kanako/tickle_dev/rails5_rc1_chat/Gemfile? (enter "h" for help) [Ynaqdh] 

[Ynaqdh] まで出力がされるので、上書きしてもよいという意味の Y を入力して enter

サーバを起動して確認

$ bundle exec rails s

=> Booting Puma
=> Rails 5.0.0.rc1 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.4.0 (ruby 2.2.3-p173), codename: Owl Bowl Brawl
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop

サーバを起動して確認しておきたいのが、ログに出力されている Booting Puma です。
Rails4.2までは、デフォルトのWebサーバは WEBrick でしたが、
Rails5から、Puma に変更されています。
WEBrick は、シングルスレッドで同時に複数のリクエストを処理することはできません。
対して、 Puma は、リクエストの並列処理が可能とのことです。
予めスレッドを指定した数だけプールしておくことで、複数のスレッドを立ち上げてリクエストを処理することができる違いがあります。
001

サーバを起動させて、ブラウザから localhost:3000 にアクセスすると上記の画面になれば、Rails5で起動したことになります。

チャットルームを表示させるコントローラーを作成

$ rails g controller rooms show

config/routes.rb を編集

トップページに、これから作るチャットルームのページを表示させるように修正します。

get 'rooms/show'
 ↓
root to: 'rooms#show'

サーバを起動させて確認

$ bundle exec rails s

002

こんな画面が出ればOKです!

モデルの生成

$ rails g model message content:text

データベースに反映

$ rails db:migrate

こちらも、 Rails5 からの変更点のひとつです。
今までは、migrateのコマンドが rake でしたが、 rails に変更されています。

app/controllers/rooms_controller.rb を修正

 class RoomsController < ApplicationController
  def show
    @messages = Message.all
  end
end

チャットルームのViewディレクトリ作成

$ mkdir app/views/messages/

app/views/messages/_message.html.erb を作成

<div class="message">
  <p><%= message.content %></p>
</div>

app/views/rooms/show.html.erb を編集

<h1>Chat room</h1>

<div id="messages">
  <%= render @messages %>
</div>

テストデータ登録

rails console
>>  Message.create! content: 'Hello world!'
>>  exit

サーバを起動させて画面の確認

003

こんな画面が出力されたらOK!

チャンネルを作成

rails g channel room speak

Running via Spring preloader in process 46356
      create  app/channels/room_channel.rb
   identical  app/assets/javascripts/cable.js
      create  app/assets/javascripts/channels/room.coffee

Bata2までと RC1 から、チャンネル作成時に出力されるファイルが違いますが、気にせず前に進みましょう。

出力された app/channels/room_channel.rb を確認

# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
class RoomChannel < ApplicationCable::Channel
  def subscribed
    # stream_from "some_channel"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def speak
  end
end

こちらは、サーバサイドの処理を行なうチャンネルです。

出力された app/assets/javascripts/channels/room.coffee を確認

App.room = App.cable.subscriptions.create "RoomChannel",
  connected: ->
    # Called when the subscription is ready for use on the server

  disconnected: ->
    # Called when the subscription has been terminated by the server

  received: (data) ->
    # Called when there's incoming data on the websocket for this channel

  speak: ->
    @perform 'speak'

こちらは、クライアントサイドの処理を行なうチャンネルです。

RC1からは設定不要 config/routes.rb

Rails.application.routes.draw do
  root to: 'rooms#show'

  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

Bata2までは、必要だった設定は、RC1からは不要なのでこのままでOK!

出力された 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);

このファイルも、出力されたままで、前に進んで大丈夫です。

004


この状態で、WEBサーバを起動し、ブラウザのWebコンソールに App.room.speak() を入力します。

サーバログに、下記が出力されば通信されています。

RoomChannel#speak

app/assets/javascripts/channels/room.coffee を編集

App.room = App.cable.subscriptions.create "RoomChannel",
  connected: ->
    # Called when the subscription is ready for use on the server

  disconnected: ->
    # Called when the subscription has been terminated by the server

  received: (data) ->
    # Called when there's incoming data on the websocket for this channel

  speak: (message) ->
    @perform 'speak', message: message

サーバサイドのチャンネル、 speak にメッセージを送信する speak メソッドを用意します。

app/channels/room_channel.rb を編集

# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
class RoomChannel < ApplicationCable::Channel
  def subscribed
    stream_from "room_channel"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def speak(data)
    Message.create! content: data['message']
  end
end

サーバサイドのチャンネルでは、クライアントサイドから送信されたメッセージを受信する speak を用意します。
speak の処理では、受け取ったメッセージを、 Message モデルに保存しています。
ここで注意すべきは、 speak メソッドの修正の他に、 subscribed メソッドも修正を忘れないようにしましょう。

# stream_from "some_channel"

  ↓ 下記のように修正

stream_from "room_channel"

app/views/rooms/show.html.erb を修正

<h1>Chat room</h1>

<div id="messages">
  <%= render @messages %>
</div>

<form>
  <label>Say something:</label><br>
  <input type="text" data-behavior="room_speaker">
</form>

app/models/message.rb を修正

class Message < ApplicationRecord
  after_create_commit { MessageBroadcastJob.perform_later self }
end

非同期でブロードキャストするジョブを生成

$ rails g job MessageBroadcast

Running via Spring preloader in process 49063
      invoke  test_unit
      create    test/jobs/message_broadcast_job_test.rb
      create  app/jobs/message_broadcast_job.rb

app/jobs/message_broadcast_job.rb を編集

class MessageBroadcastJob < ApplicationJob
  queue_as :default

  def perform(message)
    ActionCable.server.broadcast 'room_channel', message: render_message(message)
  end

  private
    def render_message(message)
      ApplicationController.renderer.render(partial: 'messages/message', locals: { message: message })
    end
end

ActionCable.server.broadcastが、サーバサイドのroom_channelで登録されたコメントを、クライアントにブロードキャストしています。

app/assets/javascripts/channels/room.coffee を修正

App.room = App.cable.subscriptions.create "RoomChannel",
  connected: ->
    # Called when the subscription is ready for use on the server

  disconnected: ->
    # Called when the subscription has been terminated by the server

  received: (data) ->
    $('#messages').append data['message']

  speak: (message) ->
    @perform 'speak', message: message

$(document).on 'keypress', '[data-behavior~=room_speaker]', (event) ->
  if event.keyCode is 13 # return = send
    App.room.speak event.target.value
    event.target.value = ''
    event.preventDefault()

005
サーバを起動し、入力フィールドに文字を入力後に enter すると画面に反映されれば成功です!

Ruby on Railsおすすめ書籍

Ruby on Railsの勉強で参考になった書籍です!

Ruby on Railsで質問したい!

Teratail(テラテイル)

WEBエンジニア専用のQ&Aサイトで、RubyやRailsも質問することができます。活発に質問と回答がされていますので、会員登録をして利用してみると良いと思います(^o^)。

エンジニアのためのQ&Aサイト【teratail】

Ruby on Railsが学べるオンラインスクール

好きな場所で好きな時間に受講できるオンラインスクール!最近は、Ruby on Railsも学ぶことができます!

TechAcademy(テックアカデミー)

オンラインでの週2回マンツーマンでのメンタリング、毎日15〜23時のチャット・レビューサポートで短期間で未経験からオリジナルサービスを開発できるようになる学習プログラム。オリジナルサービスをHerokuを使って公開するところまでを行います。

オンラインブートキャンプ Webアプリケーションコース