# 7. actioncable 入门 #### 1. 介绍 websocket的序列文章重点要讲的就是[actioncable](https://github.com/rails/rails/tree/master/actioncable),之前也讲了好多关于各种方式实现聊天室的文章,相信从中,也能学到好多关于websocket实践的知识和经验,这节要来讲讲actioncable。 actioncable是集成在rails 5中的一个功能,它能够轻易的在rails中使用websocket。现在先把actioncable用起来,再慢慢研究其原理和特性。 #### 2. 使用 还是跟先前的例子一样,建立一个聊天室。 ##### 2.1 聊天室界面 首先,rails的版本必须得是5以上,写这篇文章的时候,rails 5正式版还没有出来,目前的版本是5.0.0.beta4。 ``` $ rails new actioncable_demo $ cd actioncable_demo ``` 这样就生成了一个新项目。 接着创建message这个model,存储聊天记录。 ``` $ rails g model message content:text $ rails db:migrate ``` 创建聊天室的界面。 在config/routes.rb中添加路由。 ``` Rails.application.routes.draw do get 'rooms/show' end ``` 创建controller,添加`app/controllers/rooms_controller.rb`文件,内容如下: ``` class RoomsController < ApplicationController def show @messages = Message.all end end ``` 添加view,添加`app/views/rooms/show.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/views/messages/_message.html.erb`文件,内容如下: ``` <div class=“message”> <p><%= message.content %></p> </div> ``` 到目前为止,按照之前的经验,界面都建立好了,如下图所示: ![](https://box.kancloud.cn/8cca60ba03748f4f5a895f71a6826c6e_379x225.png) ##### 2.2 开启websocket 接下来,就是要来处理websocket部分。 先在客户端浏览器中开启websocket请求。 actioncable默认提供了一个文件`app/assets/javascripts/cable.coffee`,把几行注释打开,就可以开启websocket,内容如下: ``` # #= require action_cable #= require_self #= require_tree ./channels # @App ||= {} App.cable = ActionCable.createConsumer() ``` 其实这些js的内容很简单,它做的主要的事情就是前面几篇文章所讲的在客户端浏览器执行`new WebSocket`,具体的内容可以查看其源码。 还要在路由中添加下面这行,把websocket服务以engine的方式挂载起来。 ``` mount ActionCable.server => '/cable' ``` 至此,websocket已经开启了,可以通过chrome浏览器的开发者工具查看链接的信息,只要有101协议的信息,表示就是成功的。 ![](https://box.kancloud.cn/c8d0274c8f9fb60e0acf5389d890d68f_973x204.png) ##### 2.3 channel 现在要让客户端和服务器端连接起来。 actioncable提供了一个叫做`channel`的技术,中文名可以称为`"通道"`。actioncable是一种`pub/sub`的架构,服务器通过channel发布消息,多个客户端通过对应的channel订阅消息,服务器能够广播消息给客户端,从而实现客户端和服务器端的交互。 先新建一个channel。 ``` $ rails g channel room speak ``` 修改`app/channels/room_channel.rb`文件,内容如下: ``` class RoomChannel < ApplicationCable::Channel def subscribed stream_from "room_channel" end def unsubscribed # Any cleanup needed when channel is unsubscribed end def speak(data) # ActionCable.server.broadcast "room_channel", message: data['message'] Message.create! content: data['message'] end end ``` 其中定义了三个方法,分别是`subscribed`,`unsubscribed`,`speak`。 `subscribed`和`unsubscribed`方法是默认就生成的,而`speak`是我们自己定义的。 `subscribed`表示的是当客户端连接上来的时候使用的方法。 `unsubscribed`表示的是当客户端与服务器失去连接的时候使用的方法。 还有,`app/assets/javascripts/channels/room.coffee`文件,内容如下: ``` App.room = App.cable.subscriptions.create "RoomChannel", 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) -> $('#messages').append data['message'] # Called when there's incoming data on the websocket for this channel speak: (message) -> @perform 'speak', message: 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 = "" event.preventDefault() ``` `App.room`里定义了四个方法,除了`speak`,`connected`、`disconnected`、`received`都是actioncable定义的。 这几个方法可以和`RoomChannel`里的方法对应起来,比如: `connected`和`subscribed`对应,表示客户端和服务器端连接之后的情况。 `disconnected`和`unsubscribed`对应,表示客户端和服务器端失去连接之后的情况。 `received`表示从服务器接收到信息之后的情况。因为服务器总是要向客户端推送信息的,接收完信息之后,就可以在这里进行一些页面上的操作,比如DOM更新等。 `room.coffee`文件中有重要的一行`App.room.speak event.target.value`,当键入聊天信息,一按回车键后,就会通过这行代码,把聊天信息,发送到后端服务器,并且会被`room_channel.rb`中的`speak`接收,执行`Message.create! content: data['message']`命令。 ##### 2.4 activejob 现在还没真正完成,还差一部分。 `room.coffee`文件中有一个`received`方法,它有一行指令`$('#messages').append data['message']`。 这个表示当聊天信息发出时,会在聊天信息展示界面上添加聊天的内容。 现在来处理这个,我们通过activejob来处理,还记得之前的`app/views/messages/_message.html.erb`文件吗,现在要发挥它的作用。 先建立一个job。 ``` $ rails g job message_broadcast ``` 修改`app/jobs/message_broadcast_job.rb`文件,内容如下: ``` class MessageBroadcastJob < ApplicationJob queue_as :default def perform(message) ActionCable.server.broadcast 'room_channel', message: render_message(message) end private def render_message(message) ApplicationController.renderer.render(partial: 'messages/message', locals: { message: message }) end end ``` 还要在一个地方执行这个job,是当创建完message的时候。 修改`app/models/message.rb`文件,内容如下: ``` class Message < ApplicationRecord after_create_commit { MessageBroadcastJob.perform_later self } end ``` 做完这一切,重启一下服务器。 现在来看下效果: ![](https://box.kancloud.cn/16588ee53a535517b39a3ee5ba4c2437_799x306.png) 本篇完结。 下一篇: [websocket之actioncable进阶(八)](http://www.rails365.net/articles/websocket-zhi-actioncable-jin-jie-ba)