Computer >> Máy Tính >  >> Lập trình >> Ruby

Xây dựng ứng dụng trò chuyện thời gian thực trong Rails bằng ActionCable và Turbo

Bạn đã bao giờ vào Facebook và nhận được thông báo mà không cần phải làm mới trang chưa? Loại chức năng thời gian thực này có được trên hầu hết các ứng dụng sử dụng khung JavaScript, chẳng hạn như React thông qua quản lý trạng thái. Hầu hết các ứng dụng này hoạt động như các ứng dụng một trang, vì chúng không yêu cầu tải lại trang trong quá trình sử dụng để cập nhật dữ liệu trong thời gian thực. trạng thái hiện tại của ứng dụng. Ví dụ:nếu bạn đang sử dụng ứng dụng Rails hiển thị danh sách các phim có sẵn trong rạp và phim được thêm bởi quản trị viên, phim mới thêm sẽ không hiển thị trên trang tổng quan của bạn trừ khi bạn làm mới trang.

Tại sao nên sử dụng ActionCable?

ActionCable thu hẹp khoảng cách này và giúp bạn có thể cập nhật thời gian thực và chức năng động này trong ứng dụng Rails. Nó sử dụng một giao thức truyền thông được gọi là WebSocket để đưa trạng thái vào ứng dụng trong khi vẫn hoạt động hiệu quả và có thể mở rộng. Với nó, người dùng có thể nhận được nội dung cập nhật trên trang tổng quan của họ mà không cần phải làm mới trang của họ.

Điều kỳ diệu của Turbo-Rails

TurboRails bao gồm hệ dẫn động turbo, khung turbo và dòng turbo. Khi một yêu cầu được gửi từ một phần của trang được bao bọc trong khung turbo, phản hồi HTML sẽ thay thế khung mà nó tạo ra nếu chúng có cùng id. Mặt khác, luồngurbo cho phép cập nhật từng phần này qua web kết nối ổ cắm. ActionCable phát các cập nhật này từ một kênh và luồng Turbo tạo người đăng ký kênh này và cung cấp các cập nhật. Do đó, các bản cập nhật không đồng bộ có thể được tạo trực tiếp để đáp ứng với các thay đổi của mô hình.

Những gì chúng tôi định xây dựng

Mục đích của bài viết này là giới thiệu cách Turbo hoạt động với ActionCable đằng sau hậu trường để phát và hiển thị các bản cập nhật theo thời gian thực trong Ứng dụng Rails 6. Do đó, chúng tôi sẽ xây dựng một ứng dụng trò chuyện trong đó bất kỳ người dùng nào cũng có thể tạo phòng trò chuyện và tất cả người dùng đều có thể gửi tin nhắn đến phòng đó và nhận cập nhật trong thời gian thực. Chúng tôi cũng sẽ cho phép người dùng trò chuyện riêng tư với nhau. Chúng tôi sẽ không triển khai lời mời trò chuyện nhóm; những thứ này nằm ngoài phạm vi của bài đăng blog này vì nó chỉ liên quan đến các thiết kế cơ sở dữ liệu bổ sung chứ không phải Turbo và ActionCable. Tuy nhiên, sau bài học này, nó sẽ là một miếng bánh nếu bạn chọn đi xa hơn.

Điều này sẽ trông như thế nào hoặc nó phải có những gì?

  • Trang chỉ mục liệt kê tất cả người dùng và phòng trò chuyện hiện có.
  • Trang này sẽ được cập nhật động khi người dùng mới đăng ký hoặc phòng mới được tạo.
  • Một biểu mẫu hiện để tạo phòng trò chuyện mới.
  • Hộp trò chuyện tin nhắn để tạo tin nhắn khi ở trong bất kỳ phòng trò chuyện nào.

7 bước đơn giản để bắt đầu

Trong ứng dụng này, chúng tôi yêu cầu người dùng chỉ đăng nhập bằng tên người dùng duy nhất của họ, có được bằng cách sử dụng phiên.

1. Tạo một ứng dụng đường ray mới

rails new chatapp
cd chatapp

2. Tạo một mô hình người dùng và di chuyển nó

rails g model User username
rails db:migrate

Sau đó, chúng tôi thêm xác thực duy nhất cho tên người dùng vì chúng tôi muốn tất cả tên người dùng là duy nhất cho chủ sở hữu của họ. Chúng tôi cũng tạo một phạm vi để tìm nạp tất cả người dùng ngoại trừ người dùng hiện tại cho danh sách người dùng của chúng tôi, vì chúng tôi không muốn người dùng trò chuyện với mình :).

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

3. Tạo mô hình phòng trò chuyện

Phòng trò chuyện có tên và có thể là phòng trò chuyện riêng tư (dành cho các cuộc trò chuyện riêng tư giữa hai người dùng) hoặc công khai (dành cho tất cả mọi người). Để chỉ ra điều này, chúng tôi thêm một is_private vào bảng phòng của chúng tôi.

 rails g model Room name:string is_private:boolean

Trước khi di chuyển tệp này, chúng tôi sẽ thêm giá trị mặc định vào is_private để tất cả các phòng được tạo là công khai theo mặc định, trừ khi có quy định khác.

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

Sau bước này, chúng tôi di chuyển tệp của mình bằng lệnh rails db:migrate . Cũng cần thêm xác thực tính duy nhất cho thuộc tính name và phạm vi để tìm nạp tất cả các phòng chung cho danh sách phòng của chúng tôi.

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

4. Thêm kiểu

Để thêm kiểu dáng tối thiểu cho ứng dụng này, chúng tôi sẽ thêm CDN bootstrap vào tệp application.html.erb của chúng tôi

<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. Thêm xác thực

Việc thêm xác thực vào ứng dụng sẽ yêu cầu current_user luôn thay đổi. Hãy thêm mã sau vào các tệp đã nêu vào ứng dụng của bạn để kích hoạt xác thực.

#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 %>

Thêm các tuyến đường sau vào tệp 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. Tạo Bộ điều khiển

Tạo RoomsController bằng cách sử dụng rails g controller Rooms index và thêm các biến cho danh sách người dùng và phòng vào phương pháp chỉ mục của chúng tôi.

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. Thiết lập các tuyến đường

Thêm phòng, người dùng và các tuyến gốc vào route.rb để trang đích của chúng tôi là trang chỉ mục liệt kê tất cả các phòng và người dùng, đồng thời chúng tôi có thể điều hướng đến bất kỳ phòng nào bạn chọn.

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

Thiết lập chế độ xem

Phần giới thiệu đầu tiên của chúng tôi về 'điều kỳ diệu' của Turbo là việc nhận các bản cập nhật theo thời gian thực trên trang tổng quan của chúng tôi trong trường hợp các phòng mới được thêm vào hoặc người dùng mới đăng ký. Để đạt được điều này, trước hết, chúng tôi tạo hai phần tử:> _room.html.erb để hiển thị từng phòng và _user.html.erb để hiển thị từng người dùng. Chúng tôi sẽ hiển thị danh sách này trong index.html.erb tệp được tạo khi tạo RoomsController, vì đây là trang đích của chúng tôi.

# 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>

Chúng tôi tiến hành hiển thị các tệp này trong index.html.erb của chúng tôi , không phải bằng cách tham chiếu trực tiếp đến chúng mà bằng cách hiển thị các biến tìm nạp bộ sưu tập. Nhớ lại rằng trong RoomsController của chúng tôi, các biến @users @rooms đã được xác định.

#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>

Trong bảng điều khiển, hãy chạy các lệnh sau:

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

Bật máy chủ Rails của bạn bằng rails s . Bạn sẽ được nhắc đăng nhập; làm như vậy với tên người dùng của bất kỳ người dùng nào được tạo ở trên và bạn sẽ có phòng mới được tạo của mình và người dùng mà bạn không đăng nhập sẽ hiển thị như được hiển thị trong hình ảnh bên dưới.

Xây dựng ứng dụng trò chuyện thời gian thực trong Rails bằng ActionCable và Turbo

Giới thiệu Turbo

Để cập nhật theo thời gian thực, chúng tôi cần cài đặt Turbo.

bundle add turbo-rails
rails turbo:install

Chạy các lệnh sau nếu bạn chưa cài đặt Redis:

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

Nhập turbo-rails vào application.js sử dụng import "@ hotwired / turbo-rails"

Tiếp theo, chúng tôi sẽ thêm các hướng dẫn cụ thể vào các mô hình của mình và yêu cầu họ phát bất kỳ phiên bản mới nào được thêm vào một kênh cụ thể. Quá trình phát sóng này được thực hiện bởi ActionCable, như chúng ta sẽ thấy ngay sau đây.

#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

Ở đây, chúng tôi đang yêu cầu mô hình người dùng phát tới một kênh có tên "người dùng" sau mỗi phiên bản người dùng mới được tạo.

#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

Ở đây, chúng tôi cũng đang yêu cầu mô hình phòng phát tới một kênh có tên là "phòng" sau khi mỗi phiên bản phòng mới được tạo.

Khởi động bảng điều khiển của bạn nếu nó chưa được khởi động hoặc sử dụng tải lại! lệnh nếu nó đã được thiết lập và đang chạy. Sau khi tạo một phiên bản mới của bất kỳ cái nào trong số này, chúng tôi thấy rằng ActionCable phát phiên bản đã thêm vào kênh được chỉ định dưới dạng luồng turbo bằng cách sử dụng một phần được gán cho nó làm mẫu. Đối với một phòng mới được thêm vào, nó sẽ phát một phần _room.html.erb với các giá trị tương ứng với phiên bản mới được thêm vào, như được hiển thị bên dưới.

Xây dựng ứng dụng trò chuyện thời gian thực trong Rails bằng ActionCable và Turbo

Tuy nhiên, vấn đề là mẫu đã phát không hiển thị trên trang tổng quan. Điều này là do chúng tôi cần thêm bộ thu phát vào chế độ xem của mình để bất kỳ nội dung nào được phát bởi ActionCable đều có thể được nhận và nối. Chúng tôi thực hiện việc này bằng cách thêm turbo_stream_from , chỉ định kênh mà chúng tôi hy vọng sẽ nhận được chương trình phát sóng. Như đã thấy trong hình trên, luồng đã phát có thuộc tính đích và điều này chỉ định id của vùng chứa mà luồng sẽ được nối vào. Điều này có nghĩa là mẫu được phát sẽ tìm kiếm một vùng chứa có id là "phòng" sẽ được thêm vào; do đó, chúng tôi bao gồm một div với id đã nói vào tệp chỉ mục của chúng tôi. Để đạt được điều này, trong index.html.erb của chúng tôi , chúng tôi thay thế <% =render @users%> với:

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

<% =render @rooms%> với

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

Tại thời điểm này, chúng ta có thể trải nghiệm sự kỳ diệu của Turbo. Chúng tôi có thể làm mới trang của mình và bắt đầu thêm người dùng và phòng mới từ bảng điều khiển của chúng tôi và xem chúng được thêm vào trang của chúng tôi trong thời gian thực. Yippee !!!

Bạn mệt mỏi với việc tạo phòng mới từ bảng điều khiển? Hãy thêm một biểu mẫu cho phép người dùng tạo phòng mới.

#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 %>

Trong biểu mẫu trên, @room được sử dụng, nhưng nó vẫn chưa được xác định trong bộ điều khiển của chúng tôi; do đó, chúng tôi xác định nó và thêm nó vào phương thức chỉ mục của RoomsController.

@room = Room.new

Khi nhấp vào nút tạo, nó sẽ định tuyến đến một phương thức tạo trong RoomsController, phương thức này không tồn tại vào lúc này; do đó, chúng ta cần thêm nó.

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

Chúng tôi có thể thêm biểu mẫu này vào tệp chỉ mục của mình bằng cách hiển thị một phần của nó như sau:

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

Ngoài ra, chúng ta có thể thêm một số lớp bootstrap để chia trang thành một phần dành cho danh sách phòng và người dùng và phần còn lại để trò chuyện.

<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>

Xây dựng ứng dụng trò chuyện thời gian thực trong Rails bằng ActionCable và Turbo Hình ảnh các phòng đã thêm được cập nhật theo thời gian thực

Bây giờ, khi tạo các phòng mới, chúng ta có thể thấy rằng các phòng này đã được tạo và trang được cập nhật theo thời gian thực. Bạn cũng có thể nhận thấy rằng biểu mẫu không bị xóa sau mỗi lần gửi; chúng tôi sẽ xử lý vấn đề này sau bằng cách sử dụng kích thích.

Trò chuyện nhóm

Đối với các cuộc trò chuyện nhóm, chúng tôi cần có khả năng định tuyến đến các phòng riêng lẻ nhưng vẫn ở trên cùng một trang. Chúng tôi thực hiện việc này bằng cách thêm tất cả các biến bắt buộc của trang chỉ mục vào phương pháp hiển thị RoomsController của chúng tôi và vẫn hiển thị trang chỉ mục.

#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

Một biến phụ có tên @single_room đã được thêm vào phương thức hiển thị. Điều này cung cấp cho chúng tôi phòng cụ thể được chuyển đến; do đó, chúng tôi có thể thêm một câu lệnh điều kiện vào trang chỉ mục của chúng tôi để hiển thị tên của căn phòng mà chúng tôi đã điều hướng đến khi một tên phòng được nhấp vào. Điều này được thêm vào trong div với tên lớp col-md-10 , như được hiển thị bên dưới.

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

Xây dựng ứng dụng trò chuyện thời gian thực trong Rails bằng ActionCable và Turbo Hình ảnh hiển thị khi di chuyển vào một số phòng

Bây giờ chúng ta sẽ chuyển sang một số thứ hấp dẫn hơn, đó là tin nhắn. Chúng tôi phải cung cấp cho phần trò chuyện của mình độ cao 100vh để phần này lấp đầy trang và bao gồm hộp trò chuyện trong đó để tạo tin nhắn. Hộp trò chuyện sẽ yêu cầu một mô hình tin nhắn. Mô hình này sẽ có tham chiếu của người dùng và tham chiếu về phòng, vì thông báo không thể tồn tại nếu không có người tạo và căn phòng có ý nghĩa.

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

Chúng tôi cũng cần xác định mối liên kết này trong mô hình người dùng và phòng bằng cách thêm dòng sau vào chúng.

has_many :messages

Hãy thêm một biểu mẫu vào trang của chúng tôi để tạo thông điệp và thêm kiểu dáng cho nó:

#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;
  }

Biểu mẫu này bao gồm một @message Biến đổi; do đó, chúng ta cần xác định nó trong bộ điều khiển của chúng ta. Chúng tôi thêm điều này vào phương thức hiển thị của RoomsController.

@message = Message.new

Trong route.rb của chúng tôi tệp, chúng tôi thêm tài nguyên thư trong tài nguyên phòng, vì tài nguyên này đính kèm với các tham số, id của phòng mà thư đang được tạo từ đó.

resources :rooms do
  resources :messages
end

Bất cứ khi nào một tin nhắn mới được tạo, chúng tôi muốn nó được truyền đến phòng mà nó đã được tạo. Để làm điều này, chúng tôi cần một phần thông báo hiển thị thông báo. Vì đây là những gì sẽ được phát sóng, chúng tôi cũng cần một turbo_stream nhận thông báo đã phát cho phòng cụ thể đó và một div sẽ đóng vai trò là vùng chứa cho việc nối các thông báo này. Đừng quên rằng id của vùng chứa này phải giống với đích của chương trình phát sóng.

Chúng tôi thêm điều này vào mô hình thông báo của chúng tôi:

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

Bằng cách này, nó sẽ phát tới phòng cụ thể mà nó đã được tạo.

Chúng tôi cũng thêm luồng, vùng chứa thông báo và biểu mẫu thông báo vào tệp chỉ mục của chúng tôi:

#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' >

Chúng tôi tạo một phần tin nhắn sẽ được phát đi và trong đó, chúng tôi chỉ hiển thị tên người dùng của người gửi nếu phòng là phòng công khai.

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

Từ bảng điều khiển, nếu chúng ta tạo một tin nhắn, chúng ta có thể thấy rằng nó được truyền đến phòng của nó bằng cách sử dụng mẫu được chỉ định.

Xây dựng ứng dụng trò chuyện thời gian thực trong Rails bằng ActionCable và Turbo

Để bật tính năng tạo tin nhắn từ trang tổng quan, chúng ta cần thêm phương thức tạo vào MessagesController.

#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

Đây là những gì chúng tôi nhận được:Xây dựng ứng dụng trò chuyện thời gian thực trong Rails bằng ActionCable và Turbo

Như chúng ta có thể thấy trong video ở trên, các tin nhắn được thêm vào, nhưng nếu chúng ta chuyển sang một phòng trò chuyện khác, có vẻ như chúng ta sẽ mất các tin nhắn trong phòng trước đó khi chúng ta quay lại. Điều này là do chúng tôi không tìm nạp các thư thuộc về một phòng để hiển thị. Để làm điều này, trong phương thức hiển thị RoomsController, chúng tôi thêm một biến tìm nạp tất cả các thông báo thuộc về một phòng và trên trang chỉ mục, chúng tôi hiển thị các thông báo đã tìm nạp. Chúng ta cũng có thể thấy rằng mẫu tin nhắn không bị xóa sau khi tin nhắn được gửi đi; điều này sẽ được xử lý với Stimulus.

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

Bây giờ, mỗi phòng sẽ có thông báo được tải khi vào cửa.

Chúng tôi cần làm cho điều này trông đẹp hơn bằng cách căn chỉnh thư của người dùng hiện tại ở bên phải và của những người khác ở bên trái. Cách đơn giản nhất để đạt được điều này là gán các lớp dựa trên điều kiện message.user ==current_user , nhưng các biến cục bộ không có sẵn cho các luồng; do đó, đối với một tin nhắn được phát, sẽ không có current_user .Chúng ta có thể làm gì? Chúng ta có thể gán một lớp cho vùng chứa tin nhắn dựa trên id người gửi tin nhắn và sau đó tận dụng lợi thế của current_user phương thức trợ giúp để thêm một kiểu vào application.html.erb của chúng tôi tập tin. Bằng cách này, nếu id của người dùng hiện tại là 2, lớp trong thẻ kiểu trong application.html.erb sẽ là .msg-2 , cũng sẽ tương ứng với lớp trong thư của chúng tôi một phần khi người gửi thư là người dùng hiện tại.

#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>

Chúng tôi thêm hộp thư kiểu dáng:

#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
}

Trong thẻ head của application.html.erb của chúng tôi tệp.

#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>

Chúng tôi thêm ! Important vào thẻ background-color vì chúng tôi muốn màu nền được ghi đè cho người dùng hiện tại.

Các cuộc trò chuyện của chúng tôi sau đó trông như thế này:Xây dựng ứng dụng trò chuyện thời gian thực trong Rails bằng ActionCable và Turbo

Trò chuyện Riêng tư

Hầu hết công việc cần thiết cho các cuộc trò chuyện riêng tư đã được thực hiện trong quá trình thiết lập trò chuyện nhóm. Tất cả những gì chúng ta cần làm bây giờ là:

  • Tạo một phòng riêng để trò chuyện riêng tư khi định tuyến tới một người dùng cụ thể nếu phòng đó không tồn tại.
  • Tạo người tham gia cho các phòng như vậy để kẻ xâm nhập không thể gửi tin nhắn đến các phòng đó, ngay cả từ bảng điều khiển.
  • Ngăn không cho các phòng riêng mới tạo được đưa vào danh sách phòng.
  • Hiển thị tên của người dùng thay vì tên của phòng khi đó là một cuộc trò chuyện riêng tư.

Khi định tuyến đến một người dùng cụ thể, người dùng hiện tại cho biết rằng họ muốn trò chuyện riêng tư với người dùng đó. Do đó, trong UsersController của chúng tôi , chúng tôi kiểm tra xem liệu một phòng riêng như vậy có tồn tại giữa hai người này hay không. Nếu có, nó sẽ trở thành @single_room của chúng tôi Biến đổi; nếu không, chúng tôi sẽ tạo ra nó. Chúng tôi sẽ tạo một tên phòng đặc biệt cho mỗi phòng trò chuyện riêng để chúng tôi có thể tham khảo khi cần thiết. Chúng tôi cũng cần bao gồm tất cả các biến cần thiết của trang chỉ mục trong phương thức hiển thị để vẫn ở trên cùng một trang.

#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

Như chúng ta thấy ở trên, chúng ta cần thêm một create_private_room vào mô hình Phòng của chúng tôi, sẽ tạo ra một phòng riêng và những người tham gia. Điều này dẫn đến việc chúng tôi tạo một mô hình mới có tên là Người tham gia , sẽ cho biết người dùng và phòng riêng mà người đó thuộc về.

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

Trong mô hình phòng của chúng tôi, chúng tôi thêm create_private_room và thay đổi after_create của chúng tôi gọi để chỉ phát tên phòng nếu đó không phải là phòng riêng.

#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

Để ngăn người dùng khác gửi tin nhắn đến phòng riêng khi họ không phải là người tham gia, chúng tôi thêm before_create kiểm tra mô hình Tin nhắn. Do đó, đối với các phòng riêng tư, một xác nhận được thực hiện rằng người gửi tin nhắn thực sự là người tham gia cuộc trò chuyện riêng tư đó trước khi một tin nhắn như vậy được tạo. Chúng tôi cần lưu ý rằng từ trang tổng quan, không thể gửi tin nhắn đến phòng riêng nếu bạn không phải là người tham gia vì bạn chỉ cần nhấp vào tên người dùng và sau đó phòng được tạo cho cả hai người dùng. Việc kiểm tra này chỉ nhằm tăng cường bảo mật vì người không tham gia có thể tạo thông báo từ bảng điều khiển.

#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

Để hiển thị tên người dùng của người dùng thay vì tên phòng trong cuộc trò chuyện riêng tư, chúng tôi chỉ ra trên trang chỉ mục của mình rằng nếu @user có biến, tên người dùng của người dùng sẽ được hiển thị. Lưu ý rằng biến này chỉ có trong phương thức hiển thị UsersController. Điều này dẫn đến thẻ h4 hiển thị tên phòng thay đổi thành sau:

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

Bây giờ, khi chúng tôi điều hướng đến một người dùng, chúng tôi sẽ thấy tên người dùng của người dùng thay vì tên của phòng và chúng tôi có thể gửi và nhận tin nhắn. Đừng quên thêm liên kết đăng xuất vào trang chủ của chúng tôi.

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

Như bạn có thể thấy bên dưới, đối với các cuộc trò chuyện riêng tư, tên người dùng của người gửi không được hiển thị. Chúng tôi có thể xác định thông điệp của chính mình theo vị trí của chúng.

Xây dựng ứng dụng trò chuyện thời gian thực trong Rails bằng ActionCable và Turbo

Kích thích

Chúng tôi sẽ sử dụng kích thích để xóa các biểu mẫu vì không xảy ra kết xuất lại toàn trang và các biểu mẫu không bị xóa khi tạo phiên bản mô hình mới.

bundle add stimulus-rails
rails stimulus:install

Điều này thêm các tệp sau, như được thấy trong hình ảnh bên dưới. Xây dựng ứng dụng trò chuyện thời gian thực trong Rails bằng ActionCable và Turbo

Chúng tôi tạo một reset_form_controller.js để đặt lại biểu mẫu của chúng tôi và thêm chức năng sau vào nó.

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

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

Sau đó, chúng tôi thêm thuộc tính dữ liệu vào biểu mẫu của mình, cho biết bộ điều khiển và hành động.

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

Ví dụ: form_with thẻ của biểu mẫu thông báo của chúng tôi thay đổi thành như sau:

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

Cuối cùng, đây là tất cả những gì cần thiết; các biểu mẫu của chúng tôi sẽ rõ ràng sau khi tạo một thư hoặc phòng mới. Chúng ta cũng nên lưu ý rằng hành động kích thích "ajax:success-> reset-form # reset" cũng có thể xóa biểu mẫu khi ajax:thành công sự kiện xảy ra.

Kết luận

Trong ứng dụng này, chúng tôi đã tập trung vào hành động bổ sung của Turbo Streams, nhưng đây không phải là tất cả những gì Turbo Streams đòi hỏi. Trên thực tế, Turbo Streams bao gồm năm hành động:thêm, thêm trước, thay thế, cập nhật và xóa. Để thực hiện xóa và cập nhật tin nhắn trò chuyện trong thời gian thực, các thao tác này sẽ rất hữu ích và bạn có thể cần phải có một số kiến ​​thức về khung Turbo và cách chúng hoạt động. các tính năng, trên kết nối kém hoặc nếu có sự cố máy chủ, WebSocket của bạn có thể bị ngắt kết nối. Do đó, bạn chỉ nên sử dụng Turbo Streams trong ứng dụng của mình khi nó cực kỳ quan trọng.