Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

ActionCable 및 Turbo를 사용하여 Rails에서 실시간 채팅 앱 구축

Facebook에 접속했는데 페이지를 새로고침하지 않고도 알림을 받은 적이 있습니까? 이러한 종류의 실시간 기능은 상태 관리를 통한 React와 같은 JavaScript 프레임워크를 사용하는 대부분의 애플리케이션에서 달성됩니다. 이러한 애플리케이션의 대부분은 실시간으로 데이터를 업데이트하기 위해 사용하는 동안 페이지를 다시 로드할 필요가 없기 때문에 단일 페이지 애플리케이션으로 작동합니다. 오랫동안 Rails 애플리케이션은 일반적으로 애플리케이션의 현재 상태. 예를 들어 극장에서 볼 수 있는 영화 목록을 표시하는 Rails 앱을 사용 중이고 관리자가 영화를 추가한 경우 페이지를 새로고침하지 않으면 새로 추가된 영화가 대시보드에 표시되지 않습니다.

액션 케이블을 사용해야 하는 이유

ActionCable은 이 간극을 메우고 Rails 애플리케이션에서 실시간 업데이트와 이 동적 기능을 가능하게 합니다. WebSocket이라는 통신 프로토콜을 사용하여 성능과 확장성을 유지하면서 애플리케이션에 상태를 도입합니다. 이를 통해 사용자는 페이지를 새로고침하지 않고도 대시보드에서 업데이트된 콘텐츠를 얻을 수 있습니다.

터보 레일의 마법

TurboRails는 터보 드라이브, 터보 프레임 및 터보 스트림으로 구성됩니다. 터보 프레임으로 래핑된 페이지의 일부에서 요청이 전송되면 HTML 응답이 동일한 id를 소유하고 있는 경우 해당 프레임을 대체합니다. 반면에 터보 스트림은 웹을 통해 이러한 부분 페이지 업데이트를 활성화합니다. 소켓 연결. ActionCable은 채널에서 이러한 업데이트를 브로드캐스트하고 Turbo 스트림은 이 채널에 대한 구독자를 생성하고 업데이트를 전달합니다. 결과적으로 모델 변경에 대한 응답으로 직접 비동기식 업데이트를 생성할 수 있습니다.

구축하려는 내용

이 기사의 의도는 Turbo가 장면 뒤에서 ActionCable과 함께 작동하여 Rails 6 앱에서 실시간 업데이트를 브로드캐스트하고 표시하는 방법을 보여주는 것입니다. 따라서 우리는 모든 사용자가 채팅방을 만들 수 있고 모든 사용자가 해당 방에 메시지를 보내고 실시간으로 업데이트를 받을 수 있는 채팅 앱을 구축할 것입니다. 우리는 또한 사용자들이 서로 비공개로 채팅할 수 있도록 할 것입니다. 우리는 그룹 채팅 초대를 구현하지 않을 것입니다. 이는 Turbo 및 ActionCable이 아닌 추가 데이터베이스 디자인만 포함하므로 이 블로그 게시물의 범위를 벗어납니다. 그러나 이 수업이 끝난 후 더 진행하려면 케이크 조각이 될 것입니다.

이것은 어떻게 보이거나 무엇을 수반해야 합니까?

  • 기존의 모든 대화방과 사용자를 나열하는 색인 ​​페이지입니다.
  • 이 페이지는 새 사용자가 등록하거나 새 방이 생성될 때 동적으로 업데이트되어야 합니다.
  • 새 채팅방을 만들기 위한 양식입니다.
  • 모든 대화방에 있을 때 메시지를 작성할 수 있는 메시지 대화 상자.

시작하기 위한 7단계

이 앱에서는 사용자가 고유한 사용자 이름으로만 로그인할 것을 요구하며 이는 세션을 통해 이루어집니다.

1. 새 레일 앱 만들기

rails new chatapp
cd chatapp

2. 사용자 모델 생성 및 마이그레이션

rails g model User username
rails db:migrate

그런 다음 모든 사용자 이름이 소유자에게 고유하도록 하기 위해 사용자 이름에 대한 고유한 유효성 검사를 추가합니다. 또한 사용자가 자신과 채팅하는 것을 원하지 않기 때문에 사용자 목록에 대해 현재 사용자를 제외한 모든 사용자를 가져오는 범위를 만듭니다.

#app/models/user.rb
class User < ApplicationRecord
  validates_uniqueness_of :username
  scope :all_except, ->(user) { where.not(id: user) }
end

3. 채팅방 모델 만들기

대화방에는 이름이 있으며 비공개 대화방(두 사용자 간의 비공개 대화) 또는 공개(모든 사람이 사용 가능)가 될 수 있습니다. 이를 나타내기 위해 is_private를 추가합니다. 칼럼을 우리 방 테이블에 놓으십시오.

 rails g model Room name:string is_private:boolean

이 파일을 마이그레이션하기 전에 is_private에 기본값을 추가합니다. 열로 생성된 모든 방은 달리 명시되지 않는 한 기본적으로 공개됩니다.

class CreateRooms < ActiveRecord::Migration[6.1]
  def change
    create_table :rooms do |t|
    t.string :name
    t.boolean :is_private, :default => false

    t.timestamps

    end
  end
end

이 단계가 끝나면 rails db:migrate 명령을 사용하여 파일을 마이그레이션합니다. . 또한 이름 속성에 대한 고유성 유효성 검사를 추가하고 방 목록의 모든 공개 방을 가져오는 범위를 추가해야 합니다.

#app/models/room.rb
class Room < ApplicationRecord
  validates_uniqueness_of :name
  scope :public_rooms, -> { where(is_private: false) }
end

4. 스타일 추가

이 앱에 최소한의 스타일을 추가하기 위해 부트스트랩 CDN을 application.html.erb 파일에 추가합니다.

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

5. 인증 추가

앱에 인증을 추가하려면 current_user가 필요합니다. 항상 변수. 인증을 활성화하려면 명시된 파일에 다음 코드를 앱에 추가해 보겠습니다.

#app/controllers/application_controller.rb
helper_method :current_user

def current_user
  if session[:user_id]
    @current_user  = User.find(session[:user_id])
  end
end

def log_in(user)
  session[:user_id] = user.id
  @current_user = user
  redirect_to root_path
end

def logged_in?
  !current_user.nil?
end

def log_out
  session.delete(:user_id)
  @current_user = nil
end
#app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def create
    user = User.find_by(username: params[:session][:username])
    if user
      log_in(user)
    else
      render 'new'
    end
  end

  def destroy
    log_out if logged_in?
    redirect_to root_path
  end

end
#app/views/sessions/new.html.erb
<%= form_for (:session) do |f| %>
  <%= f.label :username, 'Enter your username' %>
  <%= f.text_field :username, autocomplete: 'off' %>
  <%= f.submit 'Sign in' %>
<% end %>

route.rb 파일에 다음 경로를 추가합니다.

#routes.rb
Rails.application.routes.draw do
  get '/signin', to: 'sessions#new'
  post '/signin', to: 'sessions#create'
  delete '/signout', to: 'sessions#destroy'
end

6. 컨트롤러 만들기

rails g controller Rooms index를 사용하여 RoomsController를 만듭니다. 사용자 및 방 목록에 대한 변수를 색인 방법에 추가합니다.

class RoomsController < ApplicationController

  def index
    @current_user = current_user
    redirect_to '/signin' unless @current_user
    @rooms = Room.public_rooms
    @users = User.all_except(@current_user)
  end
end

7. 경로 설정

routes.rb에 방, 사용자 및 루트 경로를 추가합니다. 방문 페이지가 모든 방과 사용자를 나열하는 색인 ​​페이지가 되도록 하고 원하는 방으로 이동할 수 있습니다.

#routes.rb
  resources :rooms
  resources :users
  root 'rooms#index'

보기 설정

Turbo의 '마법'에 대한 첫 번째 소개는 새로 추가된 방이나 새로 등록한 사용자의 경우 대시보드에서 실시간 업데이트를 수신하는 것입니다. 이를 달성하기 위해 우선 두 개의 부분을 만듭니다. _room.html.erb 각 방과 _user.html.erb 표시 각 사용자를 표시합니다. 이 목록을 index.html.erb에 렌더링합니다. 랜딩 페이지이므로 RoomsController가 생성될 때 생성된 파일입니다.

# app/views/rooms/_room.html.erb
<div> <%= link_to room.name, room %> </div>
# app/views/users/_user.html.erb
<div> <%= link_to user.username, user %> </div>

index.html.erb에서 이러한 파일을 계속 렌더링합니다. 파일을 직접 참조하는 것이 아니라 컬렉션을 가져오는 변수를 렌더링하여 파일을 만듭니다. 우리의 RoomsController에서 변수 @users를 기억하십시오. 및 @rooms 이미 정의되었습니다.

#app/views/rooms/index.html.erb
<div class="container">
  <h5> Hi <%= @current_user.username %> </h5>
  <h4> Users </h4>
  <%= render @users %>
  <h4> Rooms </h4>
  <%= render @rooms %>
</div>

콘솔에서 다음 명령을 실행합니다.

Room.create(name: 'music')
User.create(username: 'Drake')
User.create(username: 'Elon')

rails s를 사용하여 Rails 서버를 켭니다. . 로그인하라는 메시지가 표시됩니다. 위에서 만든 사용자의 사용자 이름으로 그렇게 하면 새로 만든 방과 로그인하지 않은 사용자가 아래 이미지와 같이 표시되어야 합니다.

ActionCable 및 Turbo를 사용하여 Rails에서 실시간 채팅 앱 구축

터보 소개

실시간 업데이트를 위해서는 Turbo가 설치되어 있어야 합니다.

bundle add turbo-rails
rails turbo:install

Redis가 설치되어 있지 않은 경우 다음 명령을 실행하십시오.

sudo apt install redis-server
#installs redis if you don't have it yet
redis-server
#starts the server

turbo-rails 가져오기 application.jsimport "@hotwired/turbo-rails"를 사용하여 파일

다음으로 모델에 특정 지침을 추가하고 새로 추가된 인스턴스를 특정 채널에 브로드캐스트하도록 요청합니다. 이 방송은 곧 보게 되겠지만 ActionCable에 의해 수행됩니다.

#app/models/user.rb
class User < ApplicationRecord
  validates_uniqueness_of :username
  scope :all_except, ->(user) { where.not(id: user) }
  after_create_commit { broadcast_append_to "users" }
end

여기에서는 사용자의 새 인스턴스가 생성될 때마다 "users"라는 채널에 브로드캐스트하도록 사용자 모델에 요청합니다.

#app/models/room.rb
class Room < ApplicationRecord
  validates_uniqueness_of :name
  scope :public_rooms, -> { where(is_private: false) }
  after_create_commit {broadcast_append_to "rooms"}
end

여기에서는 각각의 새로운 방 인스턴스가 생성된 후 방 모델이 "방"이라는 채널로 브로드캐스트하도록 요청하고 있습니다.

콘솔이 아직 시작되지 않은 경우 시작하거나 reload!를 사용하십시오. 이미 실행 중이면 명령. 이들 중 하나의 새 인스턴스를 생성한 후 ActionCable이 추가된 인스턴스를 템플릿으로 할당된 부분을 사용하여 지정된 채널에 터보 스트림으로 브로드캐스트하는 것을 볼 수 있습니다. 새로 추가된 방의 경우 _room.html.erb 부분을 브로드캐스트합니다. 아래와 같이 새로 추가된 인스턴스에 해당하는 값으로.

ActionCable 및 Turbo를 사용하여 Rails에서 실시간 채팅 앱 구축

그런데 문제는 방송된 템플릿이 대시보드에 표시되지 않는다는 것입니다. 이는 ActionCable에 의해 브로드캐스트되는 모든 것을 수신 및 추가할 수 있도록 브로드캐스트 수신자를 뷰에 추가해야 하기 때문입니다. turbo_stream_from을 추가하여 이를 수행합니다. 태그로 방송을 수신할 채널을 지정합니다. 위의 이미지에서 볼 수 있듯이 방송된 스트림에는 target 속성이 있으며 이는 스트림이 추가될 컨테이너의 ID를 지정합니다. 이것은 브로드캐스트된 템플릿이 추가할 "rooms" ID를 가진 컨테이너를 검색한다는 것을 의미합니다. 따라서 인덱스 파일에 해당 ID를 가진 div를 포함합니다. 이를 달성하기 위해 index.html.erb에서 파일에서 <%= render @users %>를 대체합니다. 함께:

<%= turbo_stream_from "users" %>
<div id="users">
  <%= render @users %>
</div>

<%= render @rooms %>

<%= turbo_stream_from "rooms" %>
<div id="rooms">
  <%= render @rooms %>
</div>

이 순간, 우리는 터보의 마법을 경험할 수 있습니다. 페이지를 새로고침하고 콘솔에서 새 사용자와 방을 추가하기 시작하고 실시간으로 페이지에 추가되는 것을 볼 수 있습니다. 야!!!

콘솔에서 새 방을 만드는 것이 지겹습니까? 사용자가 새 방을 만들 수 있는 양식을 추가해 보겠습니다.

#app/views/layouts/_new_room_form.html.erb
<%= form_with(model: @room, remote: true, class: "d-flex" ) do |f| %>
  <%= f.text_field :name, class: "form-control", autocomplete: 'off' %>
  <%= f.submit data: { disable_with: false } %>
<% end %>

위의 형식에서 @room 사용되지만 컨트롤러에서 아직 정의되지 않았습니다. 따라서 이를 정의하고 RoomsController의 인덱스 메서드에 추가합니다.

@room = Room.new

생성 버튼을 클릭하면 현재 존재하지 않는 RoomsController의 생성 메소드로 라우팅됩니다. 따라서 추가해야 합니다.

#app/controllers/rooms_controller.rb
def create
  @room = Room.create(name: params["room"]["name"])
end

부분을 ​​다음과 같이 렌더링하여 인덱스 파일에 이 양식을 추가할 수 있습니다.

<%= render partial: "layouts/new_room_form" %>

또한 일부 부트스트랩 클래스를 추가하여 페이지를 방 및 사용자 목록과 채팅을 위한 부분으로 나눌 수 있습니다.

<div class="row">
  <div class="col-md-2">
    <h5> Hi <%= @current_user.username %> </h5>
    <h4> Users </h4>
    <div>
      <%= turbo_stream_from "users" %>
      <div id="users">
        <%= render @users %>
      </div>
    </div>
    <h4> Rooms </h4>
    <%= render partial: "layouts/new_room_form" %>
    <div>
      <%= turbo_stream_from "rooms" %>
      <div id="rooms">
        <%= render @rooms %>
      </div>
    </div>
  </div>
  <div class="col-md-10 bg-dark">
    The chat box stays here
  </div>
</div>

ActionCable 및 Turbo를 사용하여 Rails에서 실시간 채팅 앱 구축 실시간으로 업데이트되는 추가된 방의 이미지

이제 새 방을 만들 때 이러한 방이 생성되고 페이지가 실시간으로 업데이트되는 것을 볼 수 있습니다. 또한 제출할 때마다 양식이 지워지지 않는다는 사실도 눈치채셨을 것입니다. 나중에 자극을 사용하여 이 문제를 처리하겠습니다.

그룹 채팅

그룹 채팅의 경우 개별 방으로 라우팅할 수 있어야 하지만 동일한 페이지에 남아 있어야 합니다. 모든 색인 페이지에 필요한 변수를 우리의 RoomsController show 메소드에 추가하고 색인 페이지를 계속 렌더링하여 이를 수행합니다.

#app/controllers/rooms_controller.rb
def show
  @current_user = current_user
  @single_room = Room.find(params[:id])
  @rooms = Room.public_rooms
  @users = User.all_except(@current_user)
  @room = Room.new

  render "index"
end

@single_room이라는 추가 변수 show 메소드에 추가되었습니다. 이것은 우리에게 라우팅되는 특정 방을 제공합니다. 따라서 방 이름을 클릭할 때 탐색한 방의 이름을 표시하는 조건문을 인덱스 페이지에 추가할 수 있습니다. 이것은 클래스 이름이 col-md-10인 div 내에 추가됩니다. , 아래와 같이.

<div class="col-md-10 bg-dark text-light">
  <% if @single_room %>
    <h4 class="text-center"> <%= @single_room.name %> </h4>
  <% end %>
</div>

ActionCable 및 Turbo를 사용하여 Rails에서 실시간 채팅 앱 구축 여러 방으로 이동하는 모습을 보여주는 이미지

이제 좀 더 흥미로운 내용인 메시징으로 넘어갈 것입니다. 채팅 섹션의 높이를 100vh로 지정하여 페이지를 채우고 메시지 생성을 위한 채팅 상자를 포함해야 합니다. 채팅 상자에는 메시지 모델이 필요합니다. 메시지는 작성자와 의도한 방 없이는 존재할 수 없으므로 이 모델에는 사용자 참조와 방 참조가 있습니다.

rails g model Message user:references room:references content:text
rails db:migrate

또한 다음 줄을 추가하여 사용자 및 방 모델에서 이 연결을 식별해야 합니다.

has_many :messages

메시지 생성을 위해 페이지에 양식을 추가하고 스타일을 추가해 보겠습니다.

#app/views/layouts/_new_message_form.html.erb
<div class="form-group msg-form">
  <%= form_with(model: [@single_room ,@message], remote: true, class: "d-flex" ) do |f| %>
    <%= f.text_field :content, id: 'chat-text', class: "form-control msg-content", autocomplete: 'off' %>
    <%= f.submit data: { disable_with: false }, class: "btn btn-primary" %>
  <% end %>
</div>
#app/assets/stylesheets/rooms.scss
  .msg-form {
    position: fixed;
    bottom: 0;
    width: 90%
  }

  .col-md-10 {
    height: 100vh;
    overflow: scroll;
  }

  .msg-content {
    width: 80%;
    margin-right: 5px;
  }

이 양식에는 @message가 포함되어 있습니다. 변하기 쉬운; 따라서 컨트롤러에서 정의해야 합니다. 우리는 이것을 우리의 RoomsController의 show 메소드에 추가합니다.

@message = Message.new

routes.rb에서 파일에서 메시지 리소스를 채팅방 리소스 내에 추가합니다. 이것은 메시지가 생성되는 방의 ID인 params에 첨부되기 때문입니다.

resources :rooms do
  resources :messages
end

새 메시지가 생성될 때마다 메시지가 생성된 방으로 브로드캐스트되기를 원합니다. 이렇게 하려면 메시지를 렌더링하는 부분 메시지가 필요합니다. 이것이 방송될 것이기 때문에 turbo_stream도 필요합니다. 특정 방에 대해 브로드캐스트된 메시지를 수신하고 이러한 메시지를 추가하기 위한 컨테이너 역할을 하는 div를 수신합니다. 이 컨테이너의 id는 방송의 대상과 같아야 함을 잊지 말자.

이것을 메시지 모델에 추가합니다.

#app/models/message.rb
after_create_commit { broadcast_append_to self.room }

이렇게 하면 자신이 생성된 특정 방으로 브로드캐스트됩니다.

또한 스트림, 메시지 컨테이너 및 메시지 양식을 인덱스 파일에 추가합니다.

#within the @single_room condition in app/views/rooms/index.html.erb
<%= turbo_stream_from @single_room %>
<div id="messages">
</div>
<%= render partial: 'layouts/new_message_form' >

방송될 메시지 부분을 만들고 그 안에 방이 공개 방인 경우에만 보낸 사람의 사용자 이름을 표시합니다.

#app/views/messages/_message.html.erb
<div>
  <% unless message.room.is_private %>
    <h6 class="name"> <%= message.user.username %> </h6>
  <% end %>
  <%= message.content %>
</div>

콘솔에서 메시지를 생성하면 할당된 템플릿을 사용하여 메시지가 방으로 방송되는 것을 볼 수 있습니다.

ActionCable 및 Turbo를 사용하여 Rails에서 실시간 채팅 앱 구축

대시보드에서 메시지 생성을 활성화하려면 MessagesController에 create 메소드를 추가해야 합니다.

#app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  def create
    @current_user = current_user
    @message = @current_user.messages.create(content: msg_params[:content], room_id: params[:room_id])
  end

  private

  def msg_params
    params.require(:message).permit(:content)
  end
end

이것은 우리가 얻는 것입니다:ActionCable 및 Turbo를 사용하여 Rails에서 실시간 채팅 앱 구축

위 영상에서 볼 수 있듯이 메시지가 추가되는데, 다른 대화방으로 이동하면 돌아올 때 이전 대화방의 메시지를 잃어버린 것처럼 보입니다. 이는 전시를 위해 방에 속한 메시지를 가져오지 않기 때문입니다. 이를 위해 RoomsController show 메소드에서 방에 속한 모든 메시지를 가져오는 변수를 추가하고 인덱스 페이지에서 가져온 메시지를 렌더링합니다. 또한 메시지가 전송된 후 메시지 양식이 지워지지 않는 것을 볼 수 있습니다. 이것은 Stimulus로 처리될 것입니다.

#in the show method of app/controllers/rooms_controller.rb
@messages = @single_room.messages
#within the div with id of 'messages'
  <%= render @messages %>

이제 각 방의 입구에 메시지가 로드됩니다.

현재 사용자의 메시지를 오른쪽에 정렬하고 나머지 메시지를 왼쪽에 정렬하여 이 모양을 더 보기 쉽게 만들 필요가 있습니다. 이를 달성하는 가장 간단한 방법은 message.user == current_user 조건에 따라 클래스를 할당하는 것입니다. , 하지만 로컬 변수는 스트림에서 사용할 수 없습니다. 따라서 브로드캐스트된 메시지의 경우 current_user가 없습니다. .무엇을 할 수 있습니까? 메시지 발신자 ID를 기반으로 메시지 컨테이너에 클래스를 할당한 다음 current_user application.html.erb에 스타일을 추가하는 도우미 메서드 파일. 이렇게 하면 현재 사용자의 id가 2인 경우 application.html.erb의 style 태그에 있는 클래스가 .msg-2가 됩니다. , 메시지 발신자가 현재 사용자인 경우 부분 메시지의 클래스에도 해당합니다.

#app/views/messages/_message.html.erb
<div class="cont-<%= message.user.id %>">
  <div class="message-box msg-<%= message.user.id %> " >
    <% unless message.room.is_private %>
      <h6 class="name"> <%= message.user.username %> </h6>
    <% end %>
  <%= message.content %>
  </div>
</div>

message-box를 추가합니다. 스타일링:

#app/assets/stylesheets/rooms.scss
.message-box {
  width: fit-content;
  max-width: 40%;
  padding: 5px;
  border-radius: 10px;
  margin-bottom: 10px;
  background-color: #555555 ;
  padding: 10px
}

application.html.erb의 head 태그에서 파일.

#app/views/layouts/application.html.erb
<style>
  <%= ".msg-#{current_user&.id}" %> {
  background-color: #007bff !important;
  padding: 10px;
  }
  <%= ".cont-#{current_user&.id}" %> {
  display: flex;
  justify-content: flex-end
  }
</style>

!important를 추가합니다. background-color 태그 현재 사용자에 대해 배경색이 재정의되기를 원하기 때문입니다.

그러면 채팅이 다음과 같이 표시됩니다. ActionCable 및 Turbo를 사용하여 Rails에서 실시간 채팅 앱 구축

비공개 채팅

비공개 채팅에 필요한 대부분의 작업은 그룹 채팅 설정 중에 이루어졌습니다. 이제 다음 작업만 하면 됩니다.

  • 특정 사용자에게 라우팅할 때 해당 채팅방이 존재하지 않는 경우 비공개 채팅을 위한 개인 채팅방을 만듭니다.
  • 침입자가 콘솔에서도 이러한 방에 메시지를 보낼 수 없도록 이러한 방에 대한 참가자를 만듭니다.
  • 새로 생성된 개인방이 방 목록에 방송되는 것을 방지합니다.
  • 비공개 채팅 시 채팅방 이름 대신 사용자 이름을 표시합니다.

특정 사용자에게 라우팅할 때 현재 사용자는 해당 사용자와 비공개로 채팅하기를 원한다는 것을 나타냅니다. 따라서 UsersController에서 , 우리는 이 둘 사이에 그런 개인 방이 있는지 확인합니다. 그렇다면 @single_room이 됩니다. 변하기 쉬운; 그렇지 않으면 우리가 만들 것입니다. 필요할 때 참조할 수 있도록 각 개인 대화방에 대해 특별한 방 이름을 만들 것입니다. 또한 동일한 페이지에 유지하려면 show 메소드에 인덱스 페이지에 필요한 모든 변수를 포함해야 합니다.

#app/controllers/users_controller.rb
class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    @current_user = current_user
    @rooms = Room.public_rooms
    @users = User.all_except(@current_user)
    @room = Room.new
    @message = Message.new
    @room_name = get_name(@user, @current_user)
    @single_room = Room.where(name: @room_name).first || Room.create_private_room([@user, @current_user], @room_name)
    @messages = @single_room.messages

    render "rooms/index"
  end

  private
  def get_name(user1, user2)
    users = [user1, user2].sort
    "private_#{users[0].id}_#{users[1].id}"
  end
end

위에서 볼 수 있듯이 create_private_room을 추가해야 합니다. 방법을 Room 모델에 추가하여 개인 방과 해당 참가자를 만듭니다. 이를 통해 Participant라는 새 모델을 만들 수 있습니다. , 사용자와 해당 사용자가 속한 개인 방을 나타냅니다.

rails g model Participant user:references room:references
rails db:migrate

방 모델에서 create_private_room을 추가합니다. 메소드를 변경하고 after_create 개인방이 아닌 경우에만 방 이름을 방송하도록 호출합니다.

#app/models/room.rb
has_many :participants, dependent: :destroy
after_create_commit { broadcast_if_public }

def broadcast_if_public
  broadcast_append_to "rooms" unless self.is_private
end

def self.create_private_room(users, room_name)
  single_room = Room.create(name: room_name, is_private: true)
  users.each do |user|
    Participant.create(user_id: user.id, room_id: single_room.id )
  end
  single_room
end

다른 사용자가 참가자가 아닐 때 개인 방에 메시지를 보내는 것을 방지하기 위해 before_create를 추가합니다. 메시지 모델을 확인하십시오. 따라서 개인 방의 경우 해당 메시지가 생성되기 전에 메시지 발신자가 실제로 해당 개인 대화의 참가자라는 확인이 이루어집니다. 사용자 이름을 클릭하기만 하면 두 사용자를 위한 방이 생성되기 때문에 대시보드에서 참가자가 아닌 경우 개인 방에 메시지를 보낼 수 없다는 점에 유의해야 합니다. 콘솔에서 비참가자가 메시지를 작성할 수 있으므로 이 확인은 추가 보안을 위한 것입니다.

#app/models/message.rb
before_create :confirm_participant

def confirm_participant
  if self.room.is_private
    is_participant = Participant.where(user_id: self.user.id, room_id: self.room.id).first
    throw :abort unless is_participant
  end
end

비공개 채팅 중에 채팅방 이름 대신 사용자 이름을 표시하기 위해 인덱스 페이지에 @user 변수가 있으면 사용자의 사용자 이름이 표시되어야 합니다. 이 변수는 UsersController show 메소드에만 존재합니다. 이렇게 하면 방 이름이 다음과 같이 변경되는 h4 태그가 표시됩니다.

<h4 class="text-center"> <%= @user&.username || @single_room.name %> </h4>

이제 사용자를 탐색할 때 방 이름이 아닌 사용자 이름을 보고 메시지를 보내고 받을 수 있습니다. 저희 홈페이지에 로그아웃 링크를 추가하는 것을 잊지 마십시오.

<%= link_to 'Sign Out', signout_path,  :method => :delete %>

아래에서 볼 수 있듯이 비공개 채팅의 경우 보낸 사람의 사용자 이름이 표시되지 않습니다. 우리는 그들의 위치에 따라 우리 자신의 메시지를 식별할 수 있습니다.

ActionCable 및 Turbo를 사용하여 Rails에서 실시간 채팅 앱 구축

자극

전체 페이지 재렌더링이 발생하지 않고 새 모델 인스턴스 생성 시 양식이 지워지지 않기 때문에 자극을 사용하여 양식을 지울 것입니다.

bundle add stimulus-rails
rails stimulus:install

그러면 아래 이미지와 같이 다음 파일이 추가됩니다. ActionCable 및 Turbo를 사용하여 Rails에서 실시간 채팅 앱 구축

reset_form_controller.js를 생성합니다. 파일을 사용하여 양식을 재설정하고 다음 기능을 추가하십시오.

//app/javascript/controllers/reset_form_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  reset() {
    this.element.reset()
  }
}

그런 다음 컨트롤러와 작업을 나타내는 데이터 속성을 양식에 추가합니다.

data: { controller: "reset-form", action: "turbo:submit-end->reset-form#reset" }

예를 들어, form_with 메시지 양식의 태그가 다음과 같이 변경됩니다.

<%= form_with(model: [@single_room ,@message], remote: true, class: "d-flex",
     data: { controller: "reset-form", action: "turbo:submit-end->reset-form#reset" }) do |f| %>

마지막으로 이것이 필요한 전부입니다. 우리의 양식은 새 메시지나 방을 만든 후에 지워집니다. 또한 자극 작업 "ajax:success->reset-form#reset" 또한 ajax:success 이벤트가 발생합니다.

결론

이 앱에서는 Turbo Streams의 추가 작업에 중점을 두었지만 이것이 Turbo Streams가 수반하는 전부는 아닙니다. 실제로 Turbo Streams는 추가, 앞에 추가, 바꾸기, 업데이트 및 제거의 5가지 작업으로 구성됩니다. 채팅 메시지의 삭제 및 업데이트를 실시간으로 구현하려면 이러한 작업이 유용하며 Turbo 프레임 및 작동 방식에 대한 약간의 지식이 필요할 수 있습니다. 특정 기간 동안 WebSocket 업데이트에 의존하는 응용 프로그램의 경우 연결이 불량하거나 서버 문제가 있는 경우 WebSocket 연결이 끊어질 수 있습니다. 따라서 매우 중요한 경우에만 앱에서 Turbo Streams를 사용하는 것이 좋습니다.