はじめに
Rubyからneo4jにアクセスする方法を調べているとneographyを使った方法がわりとヒットします。
これらを読みつつちょっと使ってみたのですが、あまり性に合わなかったので、他の方法を探してみました。その結果、neo4jrb/neo4jを使った方がいろいろスッキリしたので基本的な使い方についてまとめてみます。
neo4jrb/neo4j自体はいわゆるグラフDBのNeo4jのActive Modelラッパーだそうです。このエントリーでは、neo4jrb/neo4jが内部で使用しているneo4j/neo4j-coreを直接使ってみました。現時点でneo4j-coreはv3.0です。
一次情報はドキュメントを見てください。
前提条件
以下の条件のもとで環境の準備を行なっていきます。
- LinuxもしくはMac OSX
- Ruby環境(今回は2.1.0を使っています)
Gemの準備
gemをインストールするには、以下のような内容のGemfileを作成してbundle install
します。
source 'https://rubygems.org'
gem 'neo4j'
gem 'rake'
Neo4jのインストール
インストール方法はいくつかありますが、今回はneo4jrb/neo4jに含まれるRakefileを利用した方法でインストールします。
以下の内容を含むRakefileを作成します。
load 'neo4j/tasks/neo4j_server.rake'
次にNeo4jをインストールして起動します。Neo4jのバージョンについては、こちらを確認します。なお、この方法はプロジェクト内にDBを構築するので開発環境向けです。
$ rake neo4j:install[community-2.1.6]
$ rake neo4j:start
運用環境向けなど、その他のインストールおよび起動方法についてはこちらをご覧ください。
準備が整ったので、以降DBにアクセスしていきます。
Session
DBのセッションをオープンします。
require "neo4j-core"
session = Neo4j::Session.open(:server_db)
Transaction
トランザクションを利用してDBに対する複数の操作を管理することができます。
l1 = "北斗神拳"
l2 = "伝承者"
Neo4j::Transaction.run do
nr = Neo4j::Node.create({name: "リュウケン"}, l1, l2)
nk = Neo4j::Node.create({name: "コウリュウ"}, l1)
# 複数の処理
# :
# :
end
Node
ノードを作成してプロパティとラベルを設定します。
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
ノード間にリレーションをはります。
# プロパティを後から設定
rel = nr.create_rel(:student, n1, {order: 1})
rel[:inherit] = false
# 複数のキーを持つ連想配列でプロパティを設定
nr.create_rel(:student, n4, {order: 4, inherit: true})
LabelとIndex
ラベルにインデクスを設定します。
l1 = "北斗神拳"
l2 = "伝承者"
labels = [l1, l2]
i1 = Neo4j::Label.create(l1)
i1.create_index(:name)
i2 = Neo4j::Label.create(l2)
i2.create_index(:name)
ラベルでノードを検索します。
# 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
生のCypherが使える他、DSLによりクエリを発行することができます。
# 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にあげておきました。
データはこんな感じになります。全Nodeに北斗神拳Labelを設定しているのですが、伝承者Labelを設定しているNodeは北斗神拳Labelが設定されていないように見えるのがちょっとアレですが。
北斗神拳は一子相伝。
ただこれが言いたかった。
なお、このデータはWikipediaの情報を元に作成しています。