MyFirstRails

Rails/Stimulusでのリアルタイムチャット!

概要

Railsにこれから初めて触れる方を対象にしたチュートリアルです

RailsとStimulusを使ってリアルタイムに更新されるチャットアプリを作成します

チュートリアル

Railsのひな型を作る

まず、rails newを実行し、Railsアプリのひな型を作成します

rails new realtime_chat --webpack=stimulus

--webpackはRailsでWeboackを使いやすくしたWebpackerというものを使用するというオプションです

Vue、React、Angular、Elm、Stimulusを使用することができます

今回はStimulusを使用するので--webpack=stimulusとしています

次に、作成したRailsアプリのディレクトリへと移動します。

cd realtime_chat

SQLite3のバージョンを修正

SQLite3のバージョン修正

先ほどのrails newsqlite3のインストールがエラーになっている場合は、以下のようにバージョンを指定してください

gem 'sqlite3', '1.3.13'

その後、bundle installを実行します

bundle install

Foremanを使う

Webpackerを使う場合、ruby ./bin/webpack-dev-serverというコマンドを実行しつつ、rails sでローカルサーバーを起動する必要があります

その為、現状のままではターミナルを複数開いておく必要があり、少々面倒です

そこで、複数のコマンドを並列して実行できるforemanを使用します

まず、Gemfilegem 'foreman'を追記します

gem 'foreman'

その後、bundle install

bundle install

次に、foremanで使用するProcfile.devを作成します

web: bundle exec rails s
webpacker: ruby ./bin/webpack-dev-server

あとは、foreman start -f Procfile.devをターミナルで実行するだけです

foreman start -f Procfile.dev

localhost:5000にアクセスできればOkです(foremanを使用する場合、使用するポートが5000へと変更されています)

チャットルームの作成

rails g scaffoldコマンドを使い、チャットルーム作成のCRUDを作ります

rails g scaffold room title:string

その後、rails db:migrateを実行し、マイグレーションを行います

rails db:migrate

あとは、foreman start -f Procfile.devを実行してローカルサーバを起動します

localhost:5000/roomsにアクセスし、チャットルームを作成できればOKです

チャット機能の作成

ルームを作成したので、次はチャットができるようにしたいと思います

まず、チャットを取り扱うTalkモデルを作成したいと思います

rails g model talk content:string room:references

その後、マイグレーションを行います

rails db:migrate

次に、app/models/room.rbにリレーションを追加します

class Room < ApplicationRecord
    has_many :talks
end

そして、config/routes.rbにルーティングを追加します

Rails.application.routes.draw do
 root 'rooms#index'
  resources :rooms do
    resources :talks, :only => [:index, :create]
  end
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

次に、各チャットルームにチャットの入力フォームを作成します

まずはapp/views/rooms/show.html.erbに以下を追加します

<h2>Chats</h2>
    <div data-controller="chat">
        <div data-target="chat.talks"></div>

        <input data-target="chat.content">
        <button data-action="click->chat#submit">add</div>
    </div>

<%= javascript_pack_tag 'application.js' %>

このように変更します

<p id="notice"><%= notice %></p>

<p>
  <strong>Title:</strong>
  <%= @room.title %>
</p>

<h2>Chats</h2>
    <div data-controller="chat" data-chat-refresh-interval="100">
        <div data-target="chat.talks"></div>

        <input data-target="chat.content">
        <button data-action="click->chat#submit">add</div>
    </div>

<%= javascript_pack_tag 'application.js' %>

<%= link_to 'Edit', edit_room_path(@room) %> |
<%= link_to 'Back', rooms_path %>

その後、app/javascript/controllers/chat_controller.jsを作成します

import { Controller } from "stimulus";
import axios from "axios";

export default class extends Controller {
    static get targets() {
        return ["talks", "content"];
    }

    connect() {
        this.load();

        if (this.data.has("refreshInterval")) {
            this.startRefreshing()
        }
    }

    load() {
        axios.get(`${location.pathname}/talks`).then((res) => {
            this.talksTarget.innerHTML = res.data;
        }, (error) => {
            console.log(error);
        })
    }

    submit() {
        axios.post(`${location.pathname}/talks`, { talk: { content: `${this.contentTarget.value}` }}).then((res) => {
            this.contentTarget.value = "";
            console.log(res);
        }, (error) => {
            console.log(error);
        })
    }

    startRefreshing() {
        this.refreshTimer = setInterval(() => {
          this.load()
        }, this.data.get("refreshInterval"))
    }

    stopRefreshing() {
        if (this.refreshTimer) {
          clearInterval(this.refreshTimer)
        }
    }
}

作成後、yarn add axiosaxiosを追加します

yarn add axios

最後に、app/controllers/talks_controller.rbを作成します

class TalksController < ActionController::API
    before_action :set_room

    def index
        @talks = @room.talks.all
        render json: @talks.map{|talk| "<p>#{talk.content}</p>"}.inject(:+)
    end

    def create
        @room.talks.create! talks_params
        redirect_to @room
    end

    private
        def set_room
            @room = Room.find(params[:room_id])
        end

         def talks_params
            params.required(:talk).permit(:content)
        end
end

これで、チャットを送信するとリアルタイムに更新されます!