Dựng trang web chat đơn giản với rails 5.0.0 beta bằng Action Cable

Nhưng mình đã hứa ở bài viết link là ở rails 5 bạn đã có thể tự viết 1 ứng dụng web chat đơn giản ngay trên rails mà không cần phải cài đặt thêm 1 gem gì bên ngoài hỗ trợ thông qua tính năng mới trên bản rails 5.0.0 này đó là Action Cable.Để hiểu rõ hơn về Action Cable và cập nhật rails 5 bạn có thể đọc qua ở bài viết này. Nếu bạn đã đọc xong hoặc chỉ muốn start app ngay thì chúng ta hãy bắt đầu thôi. Đầu tiên bạn phải nâng cấp bản rails lên bản 5.0.0(khuyến cáo nên cập nhật luôn phiên bản ruby của bạn vì rails 5 chỉ làm việc với phiên bản ruby >= 2.2.1) Bạn có thể cập nhật bằng việc viết thêm dòng vaò file Gemfile

#Gemfile

gem 'rails', '>= 5.0.0.beta3'

Sau đó chạy lệnh

bundle

để cài đặt rails 5.0.0.

Hoặc sử dụng lệnh

gem install rails -v 5.0.0.beta1

tiếp theo ta sẽ tạo 1 project chat đơn giản

rails new chat --skip-spring #tạo project chat
cd chat #chuyển thư mục vào app vừa tạo
rails g controller chat index #khởi tạo controller chat với action index
rails g model message content:text, user_name:string #khởi tạo CSDL bảng message với trường content và user_name
rails db:migrate #khởi tạo CSDL
#bạn có thể thấy ta đã có thể dùng lệnh rails db:migrate thay cho rake db:migrate ở phiên bản rails 5 này, và tất cả các lệnh rake khác

Tiếp tục ở controller room ta viết như sau

# app/controllers/rooms_controller.rb

class ChatsController < ApplicationController
  def index
    @messages = Message.all #load tất cả các message
  end
end

tiếp theo ta cần tạo view cho app chat

# app/views/chats/index.html.erb

<h1>Chat room</h1>

<div id="messages">
  <%= render @messages %>
</div>

<form>
  <label>Say something:</label><br>
  <input type="text" data-behavior="room_speaker">
</form>
# app/view/chats/_message.html.erb

<div class=“message”>
  <p><%= message.user_name + ": "+ message.content %></p>
</div>

OK, như vậy đã xong phần view hiện thị. Giờ sẽ tới phần việc chính đó là tạo kênh (channel) chat cho room

rails g channel room speak

tiếp theo ta cần config cho kênh vừa được khởi tạo

# app/channels/room_channel.rb

class RoomChannel < ApplicationCable::Channel
  def subscribed
    stream_from "room_channel"
  end

  def unsubscribed
    # các lệnh mà bạn muốn chạy khi người dùng out ra khỏi kênh
  end

  def speak(data)
    # ActionCable.server.broadcast "room_channel", message: data['message']
    Message.create! user_name: data['user_name'], content: data['message']
  end
end

Để bật chức năng Action Cable chúng ta cần thêm vào file config/routes.rb như sau

# config/routes.rb

mount ActionCable.server => '/cable'

Tiếp theo ta cần bật js cho cable bằng việc bỏ comment cho 2 dòng sau ở file app/assets/javascripts/cable.coffee

#app/assets/javascripts/cable.coffee

@App ||= {}
App.cable = ActionCable.createConsumer()

kế đến ta cần tạo file js để xử lý việc hiện text trong room chat

# app/assets/javascripts/channels/room.coffee

App.room = App.cable.subscriptions.create "RoomChannel",
  connected: ->
    # sau khi đã kết nối được với kênh RoomChannel

  disconnected: ->
    # sau khi kết thúc kết nối với kênh RoomChannel

  received: (data) ->
    $('#messages').append data['message']
    # khi nhận được data từ kênh chat, dòng lệnh trên sẽ in đoạn chat mới nhận được lên màn hình
    # data['message'] ở đây đã bao gồm cả đoạn html lấy từ file view/mesages/_message.html.erb được render ra từ phần **
  speak: (message) ->
    @perform 'speak', message: message
    #thông báo tới các kênh lắng nghe rằng có nội dung mới với thông tin user_name và message

$(document).on 'keypress', '[data-behavior~=room_speaker]', (event) ->
  if event.keyCode is 13 # return = send
    App.room.speak event.target.value,
    event.target.value = ""
    #khởi tạo lại giá trị
    event.preventDefault()

Tiếp tục ta cần xác định thời điểm thông báo nội dung chat tới các client lắng nghe đó là lúc sau khi message được tạo ra

# app/models/message.rb

class Message < ApplicationRecord
  after_create_commit { MessageBroadcastJob.perform_later self }
end

Cuối cùng ta cần khai báo MessageBroadcastJob mà gọi ở trên

trên terminal ta chạy lệnh

rails g job MessageBroadcast

Lệnh trên sẽ sinh ra file message_broad_cast.rb trong folder job, ta cần định nghĩ hàm perform cho class mới này như sau:

class MessageBroadcastJob < ApplicationJob
  queue_as :default

  def perform(message)
    ActionCable.server.broadcast 'room_channel', message: render_message(message)
    #gọi đến action cable để thông báo nội dung message mới đc tạo ra
  end

  private
    def render_message(message)
      ApplicationController.renderer.render(partial: 'chats/message', locals: { message: message })
      # **
      # trả ra nội dung html cho đoạn message để đoạn code js in ra màn hình
    end
end

OK đã xong bây giờ bạn có thể bật trang server local bằng lệnh rails s và vào đường link http://localhost:3000/chats trên nhiều trình duyệt để chat với nhau

Hoặc nếu bạn muốn chat qua mạng nội bộ thì có thể thay http://dia_chi_ip_may_ban:3000/chats

note: nhớ đường dẫn cho controller chat trong file router.rb nhé

#config/routes.rb
resources :chats, only: :index

Extra: Một số hàm mới cập nhật active record trên rails 5 mà ta nên biết

Thêm nhiều hàm mới cho Time & Date

  • on_weekend?

trả giá trị true nếu Date/Time vào thứ 7 hoặc chủ nhật

  Time.current
  => Fri, 12 Feb 2016 09:47:40 UTC +00:00

  Time.current.on_weekend?
  => false

  Time.current.tomorrow
  => Sat, 13 Feb 2016 09:48:47 UTC +00:00

  Time.current.tomorrow.on_weekend?
  => true

tương tự với hàm

  • on_weekday?

trả giá trị true nếu Date/Time vào các ngày trừ 7/chủ nhật

  • pluck hàm pluck có thay đổi lớn đó là nó được dùng cho Enumerable objects ví dụ:
users = [{id: 1, name: 'Max'}, {id: 2, name: 'Mark'}, {id: 3, name: 'George'}]

  users.pluck(:name)
  => ["Max", "Mark", "George"]

  # Takes multiple arguments as well
  users.pluck(:id, :name)
  => [[1, "Max"], [2, "Mark"], [3, "George"]]

do thay đổi đó mà việc dùng pluck với active record có sự thay đổi, bạn có thể thấy qua ví dụ:

 # With Rails 4.x
  users = User.all
  SELECT `users`.* FROM `users`

  users.pluck(:id, :name)
  SELECT "users"."id", "users"."name" FROM "users"

  => [[2, "Max"], [3, "Mark"], [4, "George"]]

  # With Rails 5
  users = User.all
  SELECT "users".* FROM "users"

  # does not fire any query
  users.pluck(:id, :name)
  => [[1, "Max"], [2, "Mark"], [3, "George"]]

bạn có thể thấy sự khác nhau ở câu lệnh query vì ở rails 5 pluck đã không còn được dùng để sinh ra câu lệnh truy vấn mà chỉ dùng để xử lý dữ liệu trả về, vì vậy khi dùng bạn nên chú ý, nếu bạn chỉ muốn lấy 1 vài trường thì select sẽ đảm bảo cho câu query của bạn chạy nhanh hơn.

Ngoài ra bạn có thể xem ở đây để biết thêm nhiều hàm mới khác được áp dụng ở rails 5 mới này

Đặc biệt ở rails 5 bạn đã có thể dùng lệnh or mà không phải viết riêng thành câu truy vấn

# để lấy các Post có id = 1 hoặc 2
Post.where('id = 1').or(Post.where('id = 2'))

bạn có thể kết hợp cho nhiều kiểu điều kiện khác nhau như:

(A && B) || C:

    Post.where(a).where(b).or(Post.where(c))
(A || B) && C:

    Post.where(a).or(Post.where(b)).where(c)

tham khảo: