Railsにこれから初めて触れる方を対象にしたチュートリアルです
RailsとStimulusを使ってリアルタイムに更新されるチャットアプリを作成します
まず、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のバージョン修正
先ほどのrails newでsqlite3のインストールがエラーになっている場合は、以下のようにバージョンを指定してください
gem 'sqlite3', '1.3.13'
その後、bundle installを実行します
bundle install
Webpackerを使う場合、ruby ./bin/webpack-dev-serverというコマンドを実行しつつ、rails sでローカルサーバーを起動する必要があります
その為、現状のままではターミナルを複数開いておく必要があり、少々面倒です
そこで、複数のコマンドを並列して実行できるforemanを使用します
まず、Gemfileにgem '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 axiosでaxiosを追加します
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
これで、チャットを送信するとリアルタイムに更新されます!