RailsのActionCableでリアクティブプログラミング

コメントを投稿したら、いいねをしたら、他の人のブラウザ画面にリアルタイムに反映される
そういうWEBアプリを簡単に作れる仕組みを作りたくて今gem作ってます。
https://github.com/tompng/ar_sync AR(Active Record)Sync 名前は変わるかも)

ActionCableで自前でbroadcast頑張ればできるけど、データ同期するモデルが増えると複雑になって破綻すると思うので、
宣言的に定義しておけば全部うまくやってくれる仕組みが必要なはず。

このgemでできること

  • 欲しいJSONデータの形をクライアント側からリクエストできる
  • だけどSQLのN+1問題が起きない、または楽に回避できる
  • そのデータがRails側のmodelと同期していてリアルタイムに更新されていく
  • Vue(もしくはReact)と繋げば、リアルタイムにデータ同期されるWEBアプリの完成

多分、GraphQLやMeteorがやってることに近いかな
Railsと何らかのJSフレームワークを使ってるところに簡単に乗せれるところを目指してます。

使い方

モデルの定義

データ同期するカラムと親子関係を記述

class User < ApplicationRecord
  has_many :posts
  sync_self
  sync_data :name
  sync_has_many :posts # 逆の関係(sync_parent)も定義する
end

class Post < ApplicationRecord
  belongs_to :user
  sync_self
  sync_data :title, :body, :created_at, :updated_at, :user
  sync_parent :user, inverse_of: :posts # sync_has_many :postsの逆
end

API作成

class SyncApiController < ApplicationController
  include ARSync::ApiControllerConcern
  api(:profile) { |_params| current_user }
  api(:user) { |params| User.find params[:id] }
  api(:post) { |params| Post.find params[:id] }
end

さくっとView作成

postsまで、postsについたcommentまで、commentについたいいね数まで、など
任意の深さのデータを要求して、vueに渡すだけ

<script>
  new ARSyncData({
    currentUser: {
      api: 'profile',
      query: ['id', 'name', { posts: ['title', 'created_at'] }]
    }
    post: {
      api: 'post',
      params: { id: 3 },
      query: ['title', 'body', 'created_at']
    }
  }).load((vueData) => {
    new Vue({ el: '#foobar', data: vueData })
  })
</script>
<div id='foobar'>
  <h1>
    {{post.title}} by {{post.user.name}}
    <small>date: {{post.created_at}}</small>
  </h1>
  <p>{{post.body}}</p>
  <hr>
  <h2>my posts</h2>
  <div v-for='post in currentUser.posts'>
    <a :href="'/posts/' + post.id">{{post.title}}</a>
    <small>date: {{post.created_at}}</small>
    <!-- remote: true なformやボタンを足すだけで動く -->
    <a :href="'/posts/' + post.id" data-remote=true data-method=delete>delete</a>
  </div>
</div>

仕組み

Rails

ActiveRecordのafter_commitでhookをかけて、
そのデータが必要なクライアントにだけ、データのpatchをActionCable経由でbroadcast

JS側

初期データをAPI経由で読み込んで、ActionCableで必要なkeyだけsubscribe
送られてきたpatchをデータに適用して、最新に保つ
VueやReactがそれを勝手に画面に反映してくれる

デモページ

http://arsyncdemo.herokuapp.com/
2つのウィンドウで開くと、いいねやコメントがリアルタイムに反映される様子が見れます
デモのソースコード
https://github.com/tompng/ar_sync/tree/master/sampleapp

今後

  • それ、**でできるよ みたいなのあるのかな?
  • 関連してそうなライブラリとか色々調べる
  • どうしよう