Rubyからneo4jrb/neo4jを使ってneo4jにアクセスする

# はじめに

Rubyからneo4jにアクセスする方法を調べていると[neography](https://github.com/maxdemarzi/neography)を使った方法がわりとヒットします。

- [rubyからグラフデータベースneo4jを利用する](http://qiita.com/k-shogo/items/fce39fd5fe6c474586a7)
- [グラフDBのNeo4jを1日触ってみた](http://engineer.wantedly.com/2014/01/02/neo4j-introduction.html)
- [Ruby で Neo4j Server を使う](http://oss.infoscience.co.jp/neo4j/wiki.neo4j.org/content/Using_the_Neo4j_Server_with_Ruby.html)

これらを読みつつちょっと使ってみたのですが、あまり性に合わなかったので、他の方法を探してみました。その結果、[neo4jrb/neo4j](https://github.com/neo4jrb/neo4j)を使った方がいろいろスッキリしたので基本的な使い方についてまとめてみます。

[neo4jrb/neo4j](https://github.com/neo4jrb/neo4j)自体はいわゆるグラフDBのNeo4jのActive Modelラッパーだそうです。このエントリーでは、[neo4jrb/neo4j](https://github.com/neo4jrb/neo4j)が内部で使用している[neo4j/neo4j-core](https://github.com/neo4jrb/neo4j-core/wiki)を直接使ってみました。現時点でneo4j-coreはv3.0です。

一次情報は[ドキュメント](https://github.com/neo4jrb/neo4j-core/wiki)を見てください。

# 前提条件

以下の条件のもとで環境の準備を行なっていきます。

* LinuxもしくはMac OSX
* Ruby環境(今回は2.1.0を使っています)

# Gemの準備

gemをインストールするには、以下のような内容のGemfileを作成して```bundle install```します。

```rb=Gemfile
source 'https://rubygems.org'

gem 'neo4j'
gem 'rake'
```

# Neo4jのインストール

インストール方法はいくつかありますが、今回は[neo4jrb/neo4j](https://github.com/neo4jrb/neo4j)に含まれるRakefileを利用した方法でインストールします。

以下の内容を含むRakefileを作成します。

```rb=Rakefile
load 'neo4j/tasks/neo4j_server.rake'
```

次にNeo4jをインストールして起動します。Neo4jのバージョンについては、[こちら](http://neo4j.com/download/)を確認します。なお、この方法はプロジェクト内にDBを構築するので開発環境向けです。

```bash
$ rake neo4j:install[community-2.1.6]
$ rake neo4j:start
```

運用環境向けなど、その他のインストールおよび起動方法については[こちら](http://neo4j.com/download/)をご覧ください。

準備が整ったので、以降DBにアクセスしていきます。

# [Session](https://github.com/neo4jrb/neo4j-core/wiki/Session)

DBのセッションをオープンします。

```rb
require "neo4j-core"
session = Neo4j::Session.open(:server_db)
```

# [Transaction](https://github.com/neo4jrb/neo4j-core/wiki/Transaction)

トランザクションを利用してDBに対する複数の操作を管理することができます。

```rb
l1 = "北斗神拳"
l2 = "伝承者"
Neo4j::Transaction.run do
  nr = Neo4j::Node.create({name: "リュウケン"}, l1, l2)
  nk = Neo4j::Node.create({name: "コウリュウ"}, l1)
  # 複数の処理
  # :
  # :
end
```

# [Node](https://github.com/neo4jrb/neo4j-core/wiki/Node)

ノードを作成してプロパティとラベルを設定します。

```rb
l1 = "北斗神拳"
l2 = "伝承者"
labels = [l1, l2]
# プロパティを一つ設定。ラベルを複数設定。
nr = Neo4j::Node.create({name: "リュウケン"}, l1, l2)
# プロパティを後から設定
nk = Neo4j::Node.create({name: "コウリュウ"}, l1)
n1 = Neo4j::Node.create({name: "ラオウ"}, l1)
n1[:hero] = false
# 複数のキーを持つ連想配列でプロパティを設定。ラベルは一つだけ設定。
props_n2 = {
  name: "トキ",
  hero: false,
}
n2 = Neo4j::Node.create(props_n2, l1)
n3 = Neo4j::Node.create({name: "ジャギ", hero: false}, l1)
# ラベルを配列展開して設定
n4 = Neo4j::Node.create({name: "ケンシロウ"}, *labels)
n4[:hero] = true
```

# [Relationship](https://github.com/neo4jrb/neo4j-core/wiki/Relationship)

ノード間にリレーションをはります。

```rb
# プロパティを後から設定
rel = nr.create_rel(:student, n1, {order: 1})
rel[:inherit] = false
# 複数のキーを持つ連想配列でプロパティを設定
nr.create_rel(:student, n4, {order: 4, inherit: true})
```

# [LabelとIndex](https://github.com/neo4jrb/neo4j-core/wiki/Label-Index)

ラベルにインデクスを設定します。

```rb
l1 = "北斗神拳"
l2 = "伝承者"
labels = [l1, l2]
i1 = Neo4j::Label.create(l1)
i1.create_index(:name)
i2 = Neo4j::Label.create(l2)
i2.create_index(:name)
```

ラベルでノードを検索します。

```rb
# Node idでNodeを検索
Neo4j::Node.load(n4.neo_id)

# LabelとnameでNodeを検索
Neo4j::Label.find_nodes(l1, :name, "ジャギ")

# Labelを指定して全Node検索
Neo4j::Label.find_all_nodes(l2)

# 指定したNodeに接続する全Relationshipを検索
nodes = Neo4j::Label.find_nodes(l2, :name, "リュウケン")
nr = nodes.first
nodes = nr.rels
nodes.each{|n|
  puts "neo_id:#{n.neo_id} props:#{n.props}"
}

# outgoingなRelationshipを検索
nodes = Neo4j::Label.find_nodes(l2, :name, "リュウケン")
nr = nodes.first
rels = nr.rels(dir: :outgoing)
rels.each{|r|
  puts "neo_id:#{r.neo_id} rel_type:#{r.rel_type} props:#{r.props}"
}

# outgoingなRelationshipの先のNodeを検索
nodes = Neo4j::Label.find_nodes(l2, :name, "リュウケン")
nr = nodes.first
nodes = nr.nodes(dir: :outgoing)
nodes.each{|n|
  puts "neo_id:#{n.neo_id} labels:#{n.labels} props:#{n.props}"
}
```

# [Queries](https://github.com/neo4jrb/neo4j-core/wiki/Queries)

生のCypherが使える他、DSLによりクエリを発行することができます。

```rb
# Relationshipにinherit=trueをもつパスを検索
inherit_path = Neo4j::Session.query("MATCH (s)-[r {inherit:true}]->(g) RETURN s, r, g")
inherit_path.each{|path|
  path.each{|n|
    puts "neo_id:#{n.neo_id} props:#{n.props}"
  }
}

# パラメータを用いた検索
inherit_path = Neo4j::Session.query("MATCH (s)-[r {inherit:{value}}]->(g) RETURN s, r, g", value: true)
inherit_path.each{|path|
  path.each{|n|
    puts "neo_id:#{n.neo_id} props:#{n.props}"
  }
}

# Label=伝承者のNodeを検索(return)
query = Neo4j::Session.query.match(n: l2)
res = query.return(:n)
res.each{|path|
  path.each{|n|
    puts "neo_id:#{n.neo_id} labels:#{n.labels} props:#{n.props}"
  }
}

# Label=伝承者のNodeのプロパティ(name)を指定して列挙
query = Neo4j::Session.query.match(n: l2)
res = query.return(n: [:name])
res.each{|path|
  path.each{|n|
    puts "name:#{n}"
  }
}

# name=リュウケンのNodeを検索(pluck)
arr = query.pluck(:n)
arr.each{|n|
  puts "neo_id:#{n.neo_id} props:#{n.props}"
}
```

# さいごに

サンプルコードを[GitHub](https://github.com/awwa/neo4j-example)にあげておきました。

データはこんな感じになります。全Nodeに北斗神拳Labelを設定しているのですが、伝承者Labelを設定しているNodeは北斗神拳Labelが設定されていないように見えるのがちょっとアレですが。

![neo4j-example.png](https://qiita-image-store.s3.amazonaws.com/0/48939/9b0b2862-491b-1a89-037b-ac67d56d6274.png)

北斗神拳は一子相伝。
ただこれが言いたかった。

![inherit.png](https://qiita-image-store.s3.amazonaws.com/0/48939/b205fdb4-8964-b872-fb5b-74bf1a6f6460.png)

なお、このデータは[Wikipedia](http://ja.wikipedia.org/wiki/%E5%8C%97%E6%96%97%E7%A5%9E%E6%8B%B3#.E6.AD.B4.E5.8F.B2)の情報を元に作成しています。