Create a team chat with Rails Action Cable

Introduction

I made a team chat function with Action Cable in the portfolio, so I will output it.

Reference article

I referred to the following article. Thank you very much. ・ Make with Rails 5 + Action Cable! Simple chat app (from DHH's demo video)[Rails 6.0] Create real-time chat with Action Cable and Devise's greedy set (revised version)

code

Tweak the code in the reference article to send chats to each team. This time, we will create a chat action on the team controller so that we can send chats. Create the part other than Action Cable according to the MVC structure.

model The model describes the many-to-many relationship between the team model and the user model. In addition, the message model, which is the content of the chat, is created as a model of the intermediate table between the user and the team.

app/models/team.rb


class Team < ApplicationRecord #Team model
  belongs_to :owner, class_name: 'User', foreign_key: :owner_id
  has_many :team_assigns, dependent: :destroy 
  has_many :members, through: :team_assigns, source: :user
  has_many :messages, dependent: :destroy
end

app/models/user.rb


class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  has_one_attached :icon #Add a user icon in Active Storage
  has_many :team_assigns, dependent: :destroy
  has_many :teams, through: :team_assigns, source: :team
  has_one :owner_team, class_name: 'Team', foreign_key: :owner_id, dependent: :destroy
  has_many :messages, dependent: :destroy
end

app/models/team_assign.rb


class TeamAssign < ApplicationRecord
  belongs_to :team
  belongs_to :user
end

app/models/message.rb


class Message < ApplicationRecord
  belongs_to :user
  belongs_to :team
end

controller The controller created a chat action within the team. Receive the team ID from the link, search from the Team class, get the chat that the team has and pass it to view.

app/controllers/teams_controller.rb


  def chat
    @team = Team.find(params[:id])
    messages = @team.messages
  end

view Since I applied bootstrap, I am using container, alert, etc. Basically, the received chat is displayed on the screen by rendering a message partial. We also have a form for sending chats.

html:app/views/teams/chat.html.erb


<h1 id="chat" data-team_id="<%= @team.id %>">
  <%= @team.name %>Chat room
</h1>

<div class="container">
  <% if @team.members.count == 1 %>
    <div class="alert alert-danger" role="alert">
There are no team members to chat with. Add members from the team screen
    </div>
  <% end %>
  <form class="form-group">
    <label>chat:</label>
    <input type="text" data-behavior="team_speaker", placeholder="Contents", class="form-control">
  </form>
  <div id="messages_<%= @team.id %>">
    <ul class="list-group">
      <%= render @messages %>
    </ul>
  </div>
</div>

html:app/views/messages/_message.html.erb


<div class="message">
  <li class="list-group-item">
    <div class="row">
      <div class="col-md-2">
        <% if message.user.icon.attached? %>
          <%= image_tag message.user.icon.variant(resize:'50x50').processed %>
        <% end %>
        <p><%= message.user.name %></p>
      </div>
      <div class="col-md-10">
        <small class="text-muted"><%= l message.created_at, format: :short %></small><br>
        <%= message.content %>
      </div>
    </div>
  </li>
</div>

From here, implement Action Cable. First, create a team channel. Directories and files are created for the channel.

$ rails g channel team

coffeescript First, describe the settings for monitoring the server from the browser. Use jQuery to get the team ID from view and create a channel to subscribe to.

app/assets/javascripts/channels/team.coffee


App.team = App.cable.subscriptions.create {
  channel: "TeamChannel",
  team_id: $("#chat").data("team_id")}, 

  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) ->
    #Receive the chat received by the speak method on the Channel side
    $('#messages_'+data['team_id']).prepend data['message']

  speak: (message) ->
    team_id = $('#chat').data('team_id')
    @perform 'speak', message: message, team_id: team_id

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

channel.rb Next, describe the settings for monitoring the browser from the server. The browser's @perform speak calls the server-side speak action. The speak action creates an instance of the message class based on the information received from the browser.

app/channels/team_channel.rb


class TeamChannel < ApplicationCable::Channel
  def subscribed
    stream_from "team_channel_#{params['team_id']}" #Show which team you are monitoring
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def speak(data)
    message = Message.create!(
      content: data['message'],
      team_id: data['team_id'],
      user_id: current_user.id
    )
  end
end

I am using current_user in the channel, but for that I need to add the following code.

app/channels/application_cable/connection.rb


module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
    end

    private

      def find_verified_user
        verified_user = User.find_by(id: env['warden'].user.id)
        return reject_unauthorized_connection unless verified_user
        verified_user
      end
  end
end

Message model

Makes the job run when an instance is created in the Message model (after commit).

app/models/message.rb


class Message < ApplicationRecord
  belongs_to :user
  belongs_to :team
  after_create_commit { MessageBroadcastJob.perform_later self } #Postscript
end

job Describe the setting to send the created message from job to the browser. First, create a MessageBroadcastJob from the rails command.

$ rails g job MessageBroadcast

When using ActiveStorage, if http_host of ApplicationController.renderer is not specified, the reference destination of the image will be an error. If you want to use a URL that starts with https: // instead of http: // in production, you need to specify https: true.

app/jobs/message_broadcast_job.rb


class MessageBroadcastJob < ApplicationJob
  queue_as :default

  def perform(message)
    ActionCable.server.broadcast(
      "team_channel_#{message.team_id}",
      message: render_message(message),
      team_id: message.team_id
    )
  end

  private
    def render_message(message)
      renderer = ApplicationController.renderer.new(
        http_host: 'http_host number', #Local, localhost:3000. Changed depending on the production environment
    #https: true          https://For URLs starting with, you need to add this code
      )
      renderer.render(partial: 'messages/message', locals: { message: message })
    end
end

By this job, the partial of the message is passed to the receive of coffeescript and reflected in real time.

Summary

I thought ActionCable was complicated, but I was able to follow and understand the passing of data by reference. From this experience, I realized again that it is important to follow the data flow one by one even if it looks complicated, and it is the best shortcut to use binding.pry on the server side and debugger on the browser side. ..

Recommended Posts

Create a team chat with Rails Action Cable
[Rails6] Create a new app with Rails [Beginner]
[Rails withdrawal] Create a simple withdrawal function with rails
[Rails 5] Create a new app with Rails [Beginner]
[Rails] rails new to create a database with PostgreSQL
Create an EC site with Rails 5 ⑨ ~ Create a cart function ~
Create a Chat app with WebSocket (Tyrus) + libGDX + Kotlin
Create a playground with Xcode 12
Tutorial to create a blog with Rails for beginners Part 1
[Rails] I tried to create a mini app with FullCalendar
A series of steps to create portfolio deliverables with Rails
Tutorial to create a blog with Rails for beginners Part 2
Tutorial to create a blog with Rails for beginners Part 0
Create a Vue3 environment with Docker!
Create portfolio with rails + postgres sql
Real-time comment function with Action Cable (2/2)
[Rails Tutorial Chapter 5] Create a layout
[Rails] Creating a new project with rails new
Create a new app in Rails
Real-time comment function with Action Cable (1/2)
Create My Page with Rails devise
Create exceptions with a fluid interface
Create a Maven project with a command
Create an EC site with Rails5 ② ~ Bootstrap4 settings, Controller / action definition ~
Create a jar file with the command
Create a simple web application with Dropwizard
Create Rails 6 + MySQL environment with Docker compose
Create a simple on-demand batch with Spring Batch
Create a GUI JSON Viewer with Ruby/GTK3
[Ruby on Rails] Create a pie chart for each column with Chartkick
Create a MySQL environment with Docker from 0-> 1
Create a simple bar chart with MPAndroidChart
Create a temporary class with new Object () {}
Make a login function with Rails anyway
[Rails] Let's create a super simple Rails API
Create a LINEnews-style tech news summary app with Rails x LineBot! [Part 1]
[rails] How to create a partial template
Make a site template easily with Rails
Create a SPA with authentication function with Rails API mode + devise_token_auth + Vue.js 3 (Rails edition)
Create a website with Spring Boot + Gradle (jdk1.8.x)
I want to test Action Cable with RSpec test
[Memo] Create a CentOS 8 environment easily with Docker
Create a CSR with extended information in Java
Create a simple bulletin board with Java + MySQL
[Windows] [IntelliJ] [Java] [Tomcat] Create a Tomcat9 environment with IntelliJ
[Rails] How to create a graph using lazy_high_charts
[Java] Create a collection with only one element
Create a SandBox account with fastlane spaces ip
Let's make a search function with Rails (ransack)
Create a multi-key map with the standard library
How to easily create a pull-down in Rails
[Rails] How to create a Twitter share button
Create an EC site with Rails5 ⑤ ~ Customer model ~
I made a LINE bot with Rails + heroku
Create a web api server with spring boot
Create an EC site with Rails 5 ⑩ ~ Create an order function ~
Create a Spring Boot development environment with docker
I made a portfolio with Ruby On Rails
Docker command to create Rails project with a single blow in environment without Ruby
[Introduction] Try to create a Ruby on Rails application
[Rails] How to create a signed URL for CloudFront