もう2ヶ月くらい前になりますが、Action Cableを用いたチャットの実装にチャレンジする機会がありました。
このときには、Railsの中でVue.jsをどう扱うのかについての一例を見ることもできたので、記念(?)にざっくりしたコードを残しておきます。
1. rails newと、webpackerでVueの初期化
rails new したあと、Gemfileに gem "webpacker" を追記し、bundle install した後、以下のコマンドを実行します。
$ bundle exec rails webpacker:install $ bundle exec rails webpacker:install:vue
2. モデルを作る
$ bundle exec rails db:create $ bundle exec rails g model message content:text user_id:integer $ bundle exec rails g model user name:string email:string $ bundle exec rails db:migrate
class User < ApplicationRecord has_many :messages, dependent: :restrict_with_error end
class Message < ApplicationRecord belongs_to :user validates :content, presence: true after_create_commit { MessageBroadcastJob.perform_later self } end
3. コントローラの作成
class HomeController < ApplicationController def index @messages = Message.all end end
4. Actioncableのインストール
$ yarn add actioncable
5. Viewを作る(Vueコンポーネントの実装)
app/javascript/packs/application.js
import Vue from 'vue/dist/vue.esm' import UserChat from '../components/user-chat.vue' import ActionCable from 'actioncable'; const cable = ActionCable.createConsumer('ws:localhost:3000/cable'); Vue.prototype.$cable = cable; document.addEventListener('DOMContentLoaded', () => { const app = new Vue({ el: '#main-container', data: {}, components: { UserChat } }) })
app/javascript/packs/components/user-chat.vue
<template>
<div>
<div>
<div v-for="item in messages" :key="item.message.id">
<div>
<div>{{ item.message.content }}</div>
</div>
</div>
</div>
<div>
<div>
<input type="text" v-model="message" placeholder="入力してください ...">
<span>
<button type="button" v-if="userMessageChannel" @click="speak">Send</button>
</span>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
message: "",
messages: [],
userMessageChannel: null,
};
},
props: ['userId'],
created() {
this.userMessageChannel = this.$cable.subscriptions.create( "UserMessageChannel", {
received: (data) => {
this.messages.push(data)
this.message = ''
},
})
},
methods: {
speak() {
this.userMessageChannel.perform('speak', {
message: this.message,
user_id: this.userId,
});
},
// 以下、今回のViewでは使っていませんが、LINEのように自分からのメッセージと他ユーザーからのメッセージでスタイルを分けるときなどに使います
messageClass (user_id) {
return {
"right": user_id === Number(this.userId)
}
},
dataClass (user_id) {
return {
"float-right": user_id === Number(this.userId)
}
}
},
};
</script>
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>VueActioncable</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_pack_tag 'application' %>
</head>
<body>
<div id="main-container">
<%= yield %>
</div>
</body>
</html>
app/views/home/index.html.erb
<% @messages.each do |message| %>
<div>
<%= message.content %>
</div>
<% end %>
<!-- current_userはdevise gemを入れるなどでログイン実装することによって使えます -->
<user-chat user-id="current_user.id"></user-chat>
6. チャンネルを作る
$ bundle exec rails g channel user_message speak
class UserMessageChannel < ApplicationCable::Channel def subscribed stream_from "user_message_channel" end def unsubscribed # Any cleanup needed when channel is unsubscribed end def speak(data) Message.create!( user_id: data['user_id'], content: data['message'] ) end end
7. Jobの作成
$ bundle exec rails g job MessageBroadcast
class MessageBroadcastJob < ApplicationJob queue_as :default def perform(message) ActionCable.server.broadcast "user_message_channel", message: message end end
8. ルーティングを追加する
Rails.application.routes.draw do root to: 'home#index' mount ActionCable.server => '/cable' end
以上です。