RethinkDB の Realtime Feed を試してみる

普段は MongoDB を触っているのですが，RethinkDB も良さそうだなと思ってちょこちょこと触っています．
RethinkDB の公式ガイドにある [Ten-minute Guide](https://www.rethinkdb.com/docs/guide/ruby/) をやってみようかと．

# ひとまず Create / Update / Delete
例えば，`players` テーブルの変更をすべて受け取るリアルタイムフィードは以下の様になる．

```ruby
require 'rethinkdb'
include RethinkDB::Shortcuts

r.connect(host: 'localhost', port: 28015).repl
# ここまで下準備

cursor = r.table("players").changes.run
cursor.each{|document| p document}
```

これを走らせたうえで，別コネクションから `players` テーブルに更新や挿入を走らせる．

```ruby
# 上記と別のコネクション
irb(main):024:0> r.table('players').insert([{name: 'Takehiro Ishikawa', number: 7, team: "DB"}]).run()
```

すると，最初の `cursor.each` したシェルには，以下の様に出力される．(読みやすいように整形しています．)

```
{
  "new_val"=>{
    "id"=>"d5e78984-d91e-4220-a7e5-bced167fbdbc", 
    "name"=>"Takehiro Ishikawa",
    "number"=>7,
    "team"=>"DB"
  },
  "old_val"=>nil
}
```

更新をしてみる．

```ruby
irb(main):024:0> r.table('players').filter{|p| p['name'].eq('Takehiro Ishikawa')}.update(previous_number: 52).run()
```

すると，`new_val` と `old_val` のそれぞれが入っていることが分かる．
どのような差分が生じたのかは自分で JSON 操作 API などを使って確認する必要があるようですね．

```
{
  "new_val"=>{
    "id"=>"d5e78984-d91e-4220-a7e5-bced167fbdbc", 
    "name"=>"Takehiro Ishikawa",
    "number"=>7,
    "previous_number"=>52,
    "team"=>"DB"
  },
  "old_val"=>{
    "id"=>"d5e78984-d91e-4220-a7e5-bced167fbdbc",
    "name"=>"Takehiro Ishikawa",
    "number"=>7,
    "team"=>"DB"
  }
}
```

続いて，削除．

```ruby
irb(main):025:0> r.table('players').filter{|p| p['name'].eq('Takehiro Ishikawa')}.delete().run()
```

すると，予想通り，`new_val` に紐づくのは `nil`, `old_val` には削除する前のドキュメント．

```
{
  "new_val"=>nil,
  "old_val"=>{
    "id"=>"d5e78984-d91e-4220-a7e5-bced167fbdbc",
    "name"=>"Takehiro Ishikawa",
    "number"=>7,
    "previous_number"=>52,
    "team"=>"DB"
  }
}
```

# cursor の性質いろいろ

## 購読の中断

Realtime Feed を読み込んでいる irb にて Ctrl+C をタイプして，フィード購読を中断する．
このあと，もう一度 `cursor` に対して Feed 購読を開始しようとすると怒られる．

```
RethinkDB::RqlRuntimeError: Can only iterate over a cursor once.
	from /home/vagrant/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/rethinkdb-2.2.0.0/lib/net.rb:389:in `each'
	from (irb):367
	from /home/vagrant/.rbenv/versions/2.2.3/bin/irb:11:in `<main>'
```

## Filter する
Realtime Feed を購読する対象を `filter` で絞ることが出来る．

例えば，以下のカーソル宣言で，`team` が `"DB"` であるドキュメントだけを抽出する．

```ruby
irb(main):376:0* players = r.table('players').filter(team: "DB").changes.run
irb(main):382:0* baystars.each{|player| p player}
```

購読を開始したうえで，`team` が `"DB"` のものも，そうでないものも insert する．

```ruby
irb(main):027:0> r.table('players').insert({name: "Kajitani Takayuki", number: 3, team: "DB"}).run() # (1)

irb(main):032:0* r.table('players').insert({name: "Okajima Hideki", team: "SB", number: 37}).run() # (2)

irb(main):033:0> r.table('players').insert({name: "Kinjo Tatsuhiko", number: 1, team: "DB"}).run() # (3)
```

すると，Realtime Feed を購読しているシェルには以下のように表示される．


```
((1) のクエリを実行すると以下の出力)
{
  "new_val"=>{
    "id"=>"b6b2bd75-5736-4cb8-9991-9eaaa1386159",
    "name"=>"Kajitani Takayuki",
    "number"=>3, "team"=>"DB"
  },
  "old_val"=>nil
}
(ここの上下の間に (2) のクエリを実行)
((3) のクエリを実行すると以下の出力)
{
  "new_val"=>{
    "id"=>"9eddbb22-f8af-44a8-818e-2acf676838b7",
    "name"=>"Kinjo Tatsuhiko",
    "number"=>1,
    "team"=>"DB"
  },
  "old_val"=>nil
}
```
(2) のクエリを実行しても，挿入の結果は Realtime Feed の結果には反映されない．

更新するとどうなるか．`team` が `"DB"` だった金城を `"G"` に所属させる．

```ruby
irb(main):043:0> r.table('players').get('9eddbb22-f8af-44a8-818e-2acf676838b7').update(team: "G").run()
```

購読は以下のようになる．

```
{
  "new_val"=>nil,
  "old_val"=>{
    "id"=>"9eddbb22-f8af-44a8-818e-2acf676838b7",
    "name"=>"Kinjo Tatsuhiko",
    "number"=>1,
    "team"=>"DB"}
  }
```

むむぅ，金城が巨人に写った後のデータは含まれていない様子．
これは，フィルタの対象が `player` テーブルではなく，`team` が `"DB"` で絞った集合に対してリアルタイムフィードを購読しているからだと考えられる．つまり，上記の結果は `team` が `"DB"` な集合から上記のデータが除去されたことを表している．
もちろん，Update した後のデータのみが filter に当てはまる場合は，`new_val` のみに値が入っている状態となる．

`new_val` もしくは `old_val` のどちらかにおいて `team` が `"DB"` である，つまりベイスターズから出て行く選手なら出て行った先の情報も，入ってくる選手なら元のデータも取得したいときは，table 全体の Realtime Feed を購読し，その結果のストリームに対してフィルタを適用すれば良い．

```ruby
# 見やすいように整形しています．
irb(main):399:0> baystars = 
		r.table('players') 
			.changes
			.filter{ # Realtime Feed で帰ってくるドキュメントの配列にフィルタ
				|d| d['new_val']['team'].eq("DB") |
				d['old_val']['team'].eq("DB")}
			.run
irb(main):400:0> baystars.each{|d| p d}
```