使用actioncable做的notification(GoRails教学,2课)
GoRails视频系列:
1. 用actioncable建立Notifications
2. 见博客:
用ActionCable 建立 Realtime Notifications
在离线保存的全栈文件/我的练习/ajax网页应用/ajax-template /Cable_Notifictaion分支:
git:https://github.com/chentianwei411/actionCable-use
相关连接:
http://www.cnblogs.com/chentianwei/p/9296887.html
https://www.cnblogs.com/chentianwei/p/8690304.html
知识重点:
Gemfile:
redis; devise; puma。
config/cable.yml
development:
adapter: redis
url: redis://localhost:6379/1
Here bundle install -> rails server.
安装好Devise, rails g devise:install, 然后rails g devise User
生成Notifications mode:
rails g model Notification user:references recipient_id: integer action notifiable_type notifiable_id: integer
app/models/user.rb
has_many :notifications, as: :recipient
app/models/notification.rb
belongs_to :user
belongs_to :recipient, class_name: "User"
belongs_to :notifiable, polymorphic: true
Now restart: rails server. ⚠️这里的使用技巧。一个notification里存两个user_id, 使用了别名as
新知识:
指定关联是一个多态关联。👇是图片,后一张是has_many :through
建立多态关联的要点是链接model需要声明XXX_type:string,和XXX_id:integer 并加上索引.(具体见Guide)
has_many :through
app/views/main/index.html.erb
<div id="notifications">
</div>
⬆️用于播放通知信息的webpage.
然后正式开始建立Cable:
第一步:建立频道。rails g channel Notifications
app/channels/notifications_channel.rb
# Be sure to restart your server when you modify this file.
class NotificationsChannel < ApplicationCable::Channel
def subscribed
stream_from "notifications:#{current_user.id}"
end
def unsubscribed
stop_all_streams
end
end
Now 建立链接,让current_user可以使用cable。
app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verfied_user
end
protected
def find_verfied_user
if current_user = env['warden'].user
current_user
else
reject_unauthorized_connection
end
end
end
end
链接的作用是保证只有登陆的账号才能链接:
官方使用的是:verified_user = User.find_by(id: cookies.signed[:user_id])
第2步:设置接收的信息和位置。(80template不会用generator生成,需要自己建立)
app/assets/javascripts/channels/notifications.js
App.notifications = App.cable.subscriptions.create("NotificationsChannel", {
connected: function() {
},
disconnected: function() {
},
received: function(data) {
$("#notifications").prepend(data.html);
}
});
第三步创建broadcast
rails console:输入
ActionCable.server.broadcast "notifications:1", {html: "<div>Hello world</div>"}
这行放哪都行。这是给自己发送信息。
实际:给其他用户发送信息。需要创建一条notification:
Notification.create(recipient: User.first, user: User.last, action: "followed", notifiable: User.first)
⚠️ notifiable是多态关联的列的合集,包括type和🆔:
notifiable_type: "User"
notifiable_id: 1 (1是第一个用户的🆔号)
如此,后一个用户会接收前一个用户的信息。
之后做2件事情,一个是建立一个接收的地方partial。另一个是发布广播。
可以把这些工作放到后台,用到puma和activeJob。
rails g job NotificationRelay
app/jobs/notification_relay_job.rb
class NotificationRelayJob < ApplicationJob
queue_as :default
def perform(notification)
html = ApplicationController.render
partial: "notifications/#{notification.notifiable_type.underscore.pluralize}/#{notification.action}",
locals: {notification: notification}, formats: [:html]
ActionCable.server.broadcast "notifications:#{notification.recipient_id}", html: html
end
end
ActionController::Renderer让你可以直接使用render渲染模版,无需再使用controller action。
ApplicationController.renderer.render template: '...'
#简写:
ApplicationController.render template: '...'
#当在一个controller内渲染时,可以使用相同的options:
FooController.render :action, locals: { ... }, assigns: { ... }
#实例:
address = Address.last #一个实例对象。
AddressesController.render(address) #调用view/addresses/_address.html.erb
解释:
Render templates with any options
from ActionController::Base#render_to_string
⚠️,partial的地址取了notification记录中的属性,实际未必需要这么麻烦把?
现在Job会广播渲染的view,这个view会显示在👆的接收之处(第2步)。
在rails console
notification = Notification.first
NotificationRelayJob.perform_later(notification)
Now,成功渲染了
什么方法是最好激活job的方法:?添加一个after_commit的hook
app/models/notification.rb
after_commit -> { NotificationRelayJob.perform_later(self) }
在channels/application_cable/connection.rb中已经有拒绝非验证的链接.
如果不是当前用户则拒绝链接。
def find_verfied_user
if current_user = env['warden'].user
current_user
else
logger.error "An unauthorized connection attempt was rejected"
raise UnauthorizedError
reject_unauthorized_connection
end
end
但问题是JavaScript不知道一个用户是否登陆了。在JS端我们需要弄明白如何决定一个用户是否登陆。
最简单的是, 包括一个<meta>在HTML <head>中。
<% if user_signed_in? %>
<%= tag :meta, name: "current-user", data: {id: current_user.id} %>
<% end %>
这里使用了tag帮助方法。这个tag在head中,JS现在可以决定是否初始化一个ActionCable连接。
当然还需要进行判断,在notifications.js中进行
if ($("meta[name='current-user']").length > 0) {
// subscription code ommitted
}
改为vallina javscript:
document.getElementByName('current-user').length > 0
也可以写个函数在里面,这样可以自己决定是否验证。
// shouldConnect: function() {
// return document.getElementsByName("current-user").length > 0;
// }
不知道如何把vallina javascript转化为coffeescript:
自动转化工具。