Ruby on Railsにリアルタイム機能を簡単に導入する方法

Posted: / Tags: Ruby_on_Rails JSON_Web_Token



Ruby on RailsのWebサービスを開発していて、ちょっとリアルタイムなチャットサポートを追加したいと思ったことはないでしょうか?

Googleで「Ruby on Rails リアルタイム」と検索すると(2015年7月現在)、以下のような記事がトップにヒットします。

少し面倒くさそうに見える上、保存も行うとなるとまた設計が必要になってきます。

そこで今回は、Ruby on Railsで開発されている既存のアカウントシステムを利用して、Milkcocoaを使ったリアルタイムな機能を簡単に導入する方法を説明します。

どういう設計になるか

実際にどういう設計になるのか説明します。

ユーザーごとに異なるビューを表示するのですが、既存のWebサービスの設計はそのままで、表示するURLもユーザーごとに変えません。

では、ユーザーごとに何を変えるのでしょうか。

それは、Milkcocoaのデータストアです。Railsのログイン情報によって、データの取得・保存先のデータストアを変更することで、ユーザーごとにプライベートなビューを表示することができます。

具体的には、JSON Web Tokenを用いて、Railsのユーザ情報をMilkcocoaで利用します。

「Railsでのユーザ情報を、Milkcocoaで利用できる」とはどういうことでしょう。

Milkcocoaでは以下のようなセキュリティルールを記述できるのですが、例えば、この例ではmessage/syuheiというデータストアを操作できるのは、nameが"syuhei"であるユーザだけです。

セキュリティルール
message/[username] {
    permit : all;
    rule : account.name == username;
}

Ruby on Railsで作ったアカウントシステムで、"syuhei"という名前のユーザがいる場合に、それをMilkcocoaで利用できるというのがポイントです。

実装方法の概要

実際にどうやって実装するかを説明します。

今回は、Railsの学習教材で一番有名?なMichael Hartl氏のRuby on Rails チュートリアルの例を使って、実装します。

第8章サンプルアプリを改造して、フォローと投稿ができる、Twitterのようなものを作っています。

MilkcocoaとRuby on Railsを連携することで、リアルタイムになるだけでなく、以下のようなメリットがあります。

  • Milkcocoaを利用することで、フロントエンドエンジニアだけで開発、保守ができる。
  • リアルタイム通信部分のスケールアップをMilkcocoaに任せられる。
  • 既存のプロジェクトのアカウントシステムのまま、Milkcocoaを導入できる。
  • アカウント情報等のサービスにとって重要な情報のみを自前で持つことことができる(今回のサンプルだと、ユーザー名・パスワード・フォローしている・フォローされているといった情報。メッセージはMilkcocoaに保存される)。

以下のリポジトリにソースコードを公開しています。

以下がデモになります。

前準備(Ruby on Rails、JSON Web Tokenの導入)

では始めましょう。RubyやRuby on Railsの導入については以下の記事を参照下さい。

一応Rubyの環境を記述しておきます。

$ ruby -v
ruby 2.2.1p85 (2015-02-26 revision 49769) [x86_64-darwin13]

JSON Web Tokenを使えるようにするため、Gemでjwtパッケージをインストールします。Gemfileに以下を追加して、bundle installしましょう。

Gemfile
...
gem 'jwt'
...

途中でCan't find the PostgreSQL client library (libpq)でつまずいたので、こちらの記事を参考にして解決しました。

$ ARCHFLAGS="-arch x86_64" bundle install
$ cp config/database.yml.example config/database.yml
$ rake db:migrate
$ raile server

ソースコードの説明

それでは、コードの説明に入ります。

書く必要があるのは、「JSON Web Tokenでトークンを作成するコントローラ側のコード」と「チャット表示するビュー側のコード(JavaScript)」です。

JSON Web Tokenでトークンを作成するコントローラ側のコード

まずはログイン処理用のトークンを作成するコードを書きます。

Railsでのユーザ情報をトークン化します。今回は簡単にするため、Rails側ではpasswordはチェックせず、好きなユーザ名でログインできるようにしています。

app/controllers/sessions_controller.rb
require 'jwt'

  def get_token
    exp = Time.now.to_i + 4 * 3600
    print(current_user)
    payload = {:id => current_user.id, :exp => exp }
    hmac_secret = 'Milkcocoaのシークレットキー'
    token = JWT.encode payload, hmac_secret, 'HS256'
    render :json => {:token => token }
  end

たったこれだけで、RailsとMilkcocoaが連携できます。

ここで重要なのは'Milkcocoaのシークレットキー'のところに、Milkcocoaの管理画面にあるシークレットトークンを貼付けることです。管理画面のシークレットトークンはbase64エンコードされたもの(Auth0の場合)と、そうでないもの(AuthRocketやその他の場合)があるので、そうでないものをコピーしてください。

チャット表示するビュー側のコード

次にビューです。

トークン化したユーザ情報をmilkcocoa.authWithTokenでMilkcocoaに渡しています。これでRails側でログインしたユーザと同じユーザ名でMilkcocoaのアプリにログインしたことになります。ログインしている状態で以下を実行すると、ユーザ情報が取得できます。

milkcocoa.user(function(err, user) {console.log(user)});

ここではapp_id.mlkcca.comの部分を、自分のMilkcocoaアプリ用に書き換えてください。

app/views/sample/index.html.erb
$(function() {
  var milkcocoa = new MilkCocoa("app_id.mlkcca.com");
  milkcocoa.user(function(err, user) {
    if(user) {
      start(user);
    }else{
      loginToMilkcocoa();
    }
  });

  //コントローラで作成したトークンを取ってきて、Milkcocoaでログインする。
  function loginToMilkcocoa() {
    $.get("/get_token", {}, function(data){
      milkcocoa.authWithToken(data.token, function(err, user) {
        start(user);
      });
    }, 'json');
  }

  // フォローしているユーザーを取得
  function get_followings() {
    $.get("/followings", {}, function(data){
      if(data && data.followings)
        data.followings.forEach(showFeed);
    }, 'json');
  }

  function start(current_user) {
    console.log(current_user);
    // データストアの作成
    var ds = milkcocoa.dataStore("micropost").child(current_user.id);
    get_followings();
    $("#post-btn").click(function(e) {
      var content = $("#micropost_content").val();
      console.log(content);
      if(content) {
        // データストアに保存
        ds.push({
          content : content
        });
        $("#micropost_content").val("");
      }
    });
  }

  function showFeed(user) {
    var user_id = user.id;
    var ds = milkcocoa.dataStore("micropost").child(user_id);

    $("#followings").append('<div class="microposts"><div class="user">'+user.name+'</div><div id="feed-'+user_id+'"></div></div>');

    // データストアのデータを取得
    ds.stream().next(function(err, posts) {
      posts.forEach(function(p) {
        $('#feed-'+user_id).append('<div class="content">'+p.value.content+'</div>');
      });

    });

    // pushを監視
    ds.on('push', function(pushed) {
        $('#feed-'+user_id).append('<div class="content">'+pushed.value.content+'</div>');
    });

  }
})

Milkcocoaの管理画面で、セキュリティルールを追加します。

セキュリティルール
micropost/[userid] {
    permit : push;
    rule : account.id == userid;
}
micropost/[userid] {
    permit : query, on(push)
    rule : true;
}

これで完成です。rails serverで確認してみてください。

もう一度デモを貼っておきます。 https://milkcocoa-rails-example.herokuapp.com/

応用すればWebサイトのお問い合わせチャットができます

上記のセキュリティルールでは、その人のフィードにはその人しか書き込めませんが、閲覧側は誰でも閲覧することが可能です。 簡単な応用として、Webサイトのお問い合わせチャットのようなものを考えてみましょう。

IDが1のsyuheiさんが全てのユーザメッセージを閲覧でき、その他のユーザは自分のフィードにしか書き込みができないようにするには、どのようなセキュリティルールにしたら良いでしょう?

以下が答えになります。

セキュリティルール
micropost/[userid] {
    permit : push;
    rule : account.id == userid;
}

micropost/[userid] {
    permit : query, on(push)
    rule : account.id == userid;
}

micropost/[userid] {
    permit : query, on(push)
    rule : account.id == "1";
}

セキュリティルールを上記のように書くと、syuheiさんは、以下のようにURLのhashから閲覧したいユーザーのチャットを選択できるようにできます。

var other_username = location.hash.substr(1);//escapeすべき
var ds = milkcocoa.dataStore("message").child(other_username);

今回の記事は、主にRuby on Railsを使ってWebサービスを作ってる方向けですが、まだ触っていない方もこの機会に触ってみてはどうでしょうか。


記事内で次の画像を加工・使用しています: "Ruby on Rails logo" by KSEltar - Own work. Licensed under CC BY-SA 3.0 via Wikimedia Commons.