RailsとHerokuでノーティフィケーションをプッシュする / PusherとTurbolinksの兼ね合い
リアルタイムでユーザーにお知らせを通知したい。
Facebookの「○○さんがあなたの投稿をLikeしました」みたいなやつ。
やってみたら意外とすぐできた。
(herokuのaddonのおかげ)
こんなの。
Pusher
http://pusher.com/
めんどくさそうだと思ってたけど、Pushserを使えばそんなに難しくない。
Websocketの仕組みは分からないけど、意識しなくて良い。
RailsとHeroku使ってます。HerokuじゃなくてもPusherは使えますが。
herokuドキュメント
Pusher | Heroku Dev Center
Adding in-app notifications with Pusher | Heroku Dev Center
herokuドキュメントに沿ってやったんですが、
Turbolinksとの絡みがあるので、いろいろ変えました。
Gemfile
1
gem 'pusher'
HerokuのダッシュボードからPusherを追加。
リミットはあるけど無料で使える。
設定
APIキーとかを追加。Herokuのプロダクションは何にもしなくても面倒をみてくれるみたい。
Pusher.url とか Pusher.app_id とかは自分のローカル環境とHeroku上のアプリとでは違うものなので注意。
config/environments/development.rb
ruby
1
2
3
4
5
6
7
8
9
10
11
# Real time notification
require 'pusher'
Pusher.url = "http://7f115110ccdsxxxxxxxxx:[email protected]/apps/100000"
Pusher.logger = Rails.logger
# Lines below are needed only for development environment.
# In production, Pusher will automatically take care of this.
require 'pusher'
Pusher.app_id = '000000'
Pusher.key = '7f1151xxxxxxxxxxxxxxxxxx'
Pusher.secret = 'f7b9a0f68dc6xxxxxxxxxxxxx'
Javascriptを読み込み
ダウンロードしてassets pipelineに入れてもいいかもしれないけど、リンクがおすすめって書いてあったのでとりあえずそうしあた。
We highly recommended that you link to our hosted version which is minified and served by a CDN. By linking to a minor version (e.g. 2.2) you will automatically receive patch updates.
views/layouts/application.html.erb
html
1
2
<!-- Pusher (Heroku addon) -->
<script src="//js.pusher.com/2.2/pusher.min.js" type="text/javascript"></script>
環境変数を設定
Javascriptを必要に応じて呼び出したいので、Api_keyを環境変数にしました。
公式ドキュメントにはないけど。api_keyをどうやってproduction/developmentで切り替えるかだけど、HTMLに埋め込んじゃいました。ついでにログインしているユーザーのIDも。
なんかいい方法ないかな。
ローカル環境
1
2
$ vi ~/.bash_profile
export PUSHER_API_KEY="7b01788fd3cxxxxd05470"
Heroku
1
$ heroku config:set YADOKALY_PUSHER_API_KEY=7b01788fdxxxxx05470
HTML(views/layouts/application.html.erbとかどこでも)
html
1
2
<span id="current_user_id" data-value="<%= current_user.id %>" style="display:none;"></span>
<span id="pusher_api_key" data-value="<%= ENV['PUSHER_API_KEY'] %>" style="display:none;"></span>
サーバーサイド
current_userにログインしているユーザーが入っているとうい前提です。
テストとしてpushというメソッドをつくりました。
ruby
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class PusherController < ApplicationController
protect_from_forgery :except => :auth # stop rails CSRF protection for this action
def auth
if current_user
response = Pusher[params[:channel_name]].authenticate(params[:socket_id])
render :json => response
else
render :text => "Not authorized", :status => '403'
end
end
def push
Pusher['private-'+current_user.id.to_s].trigger('new_message', {:sender => current_user.name, :link => "/chatroom/123"})
render nothing: true
end
end
対応するRouteを追加します。
config/routes.rb
ruby
1
2
post 'pusher/auth'
get 'pusher/push'
クライアントサイド
/pusher/pushを叩くリンクをViewのどこかに入れておいてください。
erb
1
<%= link_to "push", "/pusher/push", remote: true %>
bodyの中に書くとTurbolinksと色々あって、1回ノーティフィケーション送ったつもりが2個も3個も表示されるって状況になったので、ページ遷移するたびにPushrer instancesをカラにしてみました。
assets/javascripts/pusher.coffee
coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
pusher_reset = ->
# Disconnect and remove all pusher instance
# to avoide multiple execution.
if Pusher
$.each(Pusher.instances, (index, instance) ->
instance.disconnect()
)
Pusher.instances = []
console.log 'pusher_reset fired.'
ready = ->
# Pusher notification
# Pusher['private-1'].trigger() in controller
current_user_id = $('#current_user_id').attr('data-value')
api_key = $('#pusher_api_key').attr('data-value')
if Pusher && current_user_id
pusher = new Pusher(api_key, { cluster: 'eu' })
channel = pusher.subscribe('private-' + current_user_id)
channel.bind('new_message', (data) ->
console.log 'fire'
# Will be executed when the method in controller called.
if !$('.notify-wrap').length
$('body').prepend('<div class="notify-wrap"></div>')
msg = data.sender + ' さんからメッセージを受信しました。'
msgDiv = '<div class="notify">' +
'<span class="got-it"><i class="fa fa-times-circle"></i></span>' +
'<a href="' + data.link + '">' + msg + '</a>' +
'</div>'
$(msgDiv).prependTo('.notify-wrap').animate({ right: 0 }, 350)
### if you want to hide it after some seconds
setTimeout( ->
$('#notify').fadeOut()
, 10000)
###
)
$('body').on 'click', '.got-it', (e) ->
e.preventDefault()
$(this).closest('.notify').remove()
# For turbolinks
$(document).ready(ready)
$(document).on 'page:load', ready
$(document).on 'page:receive', pusher_reset
見た目
ちょっとかっこよくします。
横からスッと入ってくるようにしたいので、right: -300px;をデフォルトにしておいて、
前述の.animate({ right: 0 }, 350) でアニメーションさせてます。
assets/stylesheets/pusher.scss
scss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
$c_light_gray: #F7F7F7;
$c_blue: #114aa9;
$c_gray: #666666;
.notify-wrap {
z-index: 10;
position: fixed;
top: 10px;
right: 10px;
.notify {
position: relative;
right: -300px;
width: 200px;
border-radius: 5px;
font-size: 0.85em;
border: 3px solid $c_light_gray;
background-color: #fff;
box-shadow: 0 4px 10px #ddd;
margin-bottom: 10px;
&:hover {
border-color: lighten($c_blue, 42);
}
a {
height: 100%;
width: 100%;
display: block;
padding: 10px 15px;
&:hover {
text-decoration: none;
//color: #fff;
}
}
.got-it {
position: absolute;
top: -7px;
right: -7px;
font-size: 20px;
width: 24px;
height: 24px;
line-height: 20px;
border: 2px solid #fff;
background-color: #fff;
border-radius: 50%;
text-align: center;
cursor: pointer;
&:hover {
color: #fff;
border: 2px solid $c_gray;
background-color: $c_gray;
}
}
}
}
以上かな。
これで pushボタンを押したらスッとノーティフィケーションが現れるはず。
楽しい!
参考
Pusher | Heroku Dev Center
Adding in-app notifications with Pusher | Heroku Dev Center
Documentation | Pusher
Pusher connections stack up when using turbo links · Issue #48 · chrismccord/sync · GitHub
Turbolinks support for Pusher.js
rails/turbolinks · GitHub