RailsとHerokuでノーティフィケーションをプッシュする / PusherとTurbolinksの兼ね合い

- - - - - - - - - - - 
ブログを更新しました。<a href="http://workabroad.jp/posts/2127">元の記事はコチラ</a>
- - - - - - - - - - - 
リアルタイムでユーザーにお知らせを通知したい。
Facebookの「○○さんがあなたの投稿をLikeしました」みたいなやつ。
やってみたら意外とすぐできた。
（herokuのaddonのおかげ）

<!-- extract -->

こんなの。
![20150112-064343_capture](http://image.workabroad.jp/upload/2015/233/workabroad.jpg)

###Pusher

http://pusher.com/
めんどくさそうだと思ってたけど、Pushserを使えばそんなに難しくない。
Websocketの仕組みは分からないけど、意識しなくて良い。
RailsとHeroku使ってます。HerokuじゃなくてもPusherは使えますが。
![Screen Shot 2015-01-11 at 10.50.05 PM](http://image.workabroad.jp/upload/2015/230/workabroad.jpg)

**herokuドキュメント**
[Pusher | Heroku Dev Center](https://devcenter.heroku.com/articles/pusher)
[Adding in-app notifications with Pusher | Heroku Dev Center](https://devcenter.heroku.com/articles/pusher-in-app-notifications#creating-our-authentication-endpoint)

herokuドキュメントに沿ってやったんですが、
Turbolinksとの絡みがあるので、いろいろ変えました。

　　
Gemfile

```

gem 'pusher'

```

HerokuのダッシュボードからPusherを追加。
リミットはあるけど無料で使える。

![Pusher  Development — App keys](http://image.workabroad.jp/upload/2015/231/workabroad.jpg)



###設定

APIキーとかを追加。Herokuのプロダクションは何にもしなくても面倒をみてくれるみたい。
Pusher.url とか Pusher.app_id とかは自分のローカル環境とHeroku上のアプリとでは違うものなので注意。

config/environments/development.rb

```ruby

  # Real time notification
  require 'pusher'
  Pusher.url = "http://7f115110ccdsxxxxxxxxx:f7b9a0f68dxxxxxxxxxx@api-eu.pusher.com/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

  <!-- 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も。
なんかいい方法ないかな。

ローカル環境

```

$ vi ~/.bash_profile
export PUSHER_API_KEY="7b01788fd3cxxxxd05470"

```

Heroku

```

$ heroku config:set YADOKALY_PUSHER_API_KEY=7b01788fdxxxxx05470

```

HTML（views/layouts/application.html.erbとかどこでも）

```html

<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

 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

  post 'pusher/auth'
  get 'pusher/push'

```



###クライアントサイド

/pusher/pushを叩くリンクをViewのどこかに入れておいてください。

```erb

<%= link_to "push", "/pusher/push", remote: true %>

```

bodyの中に書くとTurbolinksと色々あって、1回ノーティフィケーション送ったつもりが2個も3個も表示されるって状況になったので、ページ遷移するたびにPushrer instancesをカラにしてみました。

assets/javascripts/pusher.coffee

```coffee

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

$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](https://devcenter.heroku.com/articles/pusher)
[Adding in-app notifications with Pusher | Heroku Dev Center](https://devcenter.heroku.com/articles/pusher-in-app-notifications#creating-our-authentication-endpoint)
[Documentation | Pusher](http://pusher.com/docs/)
[Pusher connections stack up when using turbo links · Issue #48 · chrismccord/sync · GitHub](https://github.com/chrismccord/sync/issues/48)
[Turbolinks support for Pusher.js](https://gist.github.com/ejholmes/6135198)
[rails/turbolinks · GitHub](https://github.com/rails/turbolinks)


















 → <a href="http://workabroad.jp/posts/2127">http://workabroad.jp/posts/2127</a>