Ruby で Neo4j Server を使う

Neo4j Wiki から

Neo4j Server は REST API と webadmin アプリケーションを 1 つにまとめ、Neo4j を使ったソリューションのインストールと実行を容易にしたものです。Neo4j Server では、リモートから Java 以外の言語を使ってサーバーにアクセスすることができます。REST をサポートする任意の言語が使用できるので、もちろん Ruby も使用できます。

REST API が利用するのは HTTP と JSON なので、多くの言語とプラットフォームから REST API を使うことができます。ただし、再利用可能ないくつかのパターンをあらかじめ見ておくと便利です。この短いガイドでは、REST API を介して簡単なグラフを作成して操作する方法や、作成したグラフに対して問い合わせを行う方法を示します。以下で示すサンプルでは、Jersey クライアントコンポーネントを使用しています。Jersey クライアントコンポーネントは、Maven 経由で簡単にダウンロードできます。以下のサンプルのフルソースコードは Neo4j Subversion のリポジトリでブラウズできます。

目次

[edit] インストール

詳しいインストールの手順は、「Neo4j Server スタートガイド」にあります。ここでは、先を急ぎたいユーザーのためにインストールの手順を簡単に示します。

  • http://neo4j.org/download にアクセスし、Neo4j Server の最新の安定版バージョンをダウンロードします。
  • bin/neo4j start を実行して (または Windows の場合は Neo4j をダブルクリックして) Neo4j Server を起動します。

[edit] Ruby REST クライアント

Ruby の世界では一般にそうであるように、複数の選択肢があります。ここではそのうち 3 つを示します。

  • Adam Wiggins と Julien Kirch によるプレーンな REST クライアント RestClient
  • Max De Marzi による Neo4j に特化した軽量 REST クライアント Neography
  • プレーンな REST クライアントである Restfulie クライアント

ここでは、それぞれのクライアントでいくつか基本的なサンプルを実行します。REST API の完全なリファレンスについては、http://components.neo4j.org/neo4j-rest/ を参照してください。

[edit] RestClient

RestClient は、ruby gem を使って次のコマンドを実行することでインストールできます。

gem install rest-client

言うまでもなく、RestClient は任意の Ruby アプリケーションまたは Rails アプリケーションに追加して使用できますが、もともとスクリプト言語なので、ここでは「Neo4j Server スタートガイド」で示した cURL の使用例に似た、コマンドラインからのアプローチを使ってみましょう。具体的には、'interactive ruby' インタープリタの IRB を使います。コマンド irb 実行して irb を起動すると、プロンプトが表示されます。

irb(main):001:0>

RestClient ライブラリにアクセスし、Neo4j Server URL に接続を試みます。

irb(main):001:0> require 'rubygems'
=> true
irb(main):002:0> require 'rest_client'
=> true
irb(main):003:0> RestClient.get 'http://localhost:7474/db/data'
=> "{\n  \"index\" : \"http://localhost:7474/db/data/index\",\n  \"node\" : \"http://localhost:7474/db/data/node\",\n  \"reference_node\" : \"http://localhost:7474/db/data/node/0\"\n}"

見やすくするために、ベース URL を変数で定義し、結果を出力するのに inspect の代わりに to_s を使うことにしましょう。

irb(main):004:0> $base = 'http://localhost:7474/db/data'
=> "http://localhost:7474/db/data"
irb(main):005:0> puts RestClient.get $base
{
  "index" : "http://localhost:7474/db/data/index",
  "node" : "http://localhost:7474/db/data/node",
  "reference_node" : "http://localhost:7474/db/data/node/0"
}
=> nil

[edit] ノードの作成

上の出力には、以後の操作で使用できる URL が含まれています。その中に、最後が node で終わっている URL が 1 つありますが、この URL を使うと、ノードの一覧表示、作成、更新、削除を行うことができます。では、プロパティを指定して新しいノードを作成してみましょう。まず、JSON gem をインクルードする必要があります。次に、RestClient.post コマンドを実行して、新しいノードのプロパティを JSON 文字列として渡します。

irb(main):006:0> require 'json'
=> true
irb(main):007:0> puts RestClient.post "#{$base}/node", { :name => 'Neo' }.to_json, :content_type => :json, :accept => :json
{
  "outgoing_relationships" : "http://localhost:7474/db/data/node/1/relationships/out",
  "data" : {
    "name" : "Neo"
  },
  "traverse" : "http://localhost:7474/db/data/node/1/traverse/{returnType}",
  "all_typed_relationships" : "http://localhost:7474/db/data/node/1/relationships/all/{-list|&|types}",
  "property" : "http://localhost:7474/db/data/node/1/properties/{key}",
  "all_relationships" : "http://localhost:7474/db/data/node/1/relationships/all",
  "self" : "http://localhost:7474/db/data/node/1",
  "properties" : "http://localhost:7474/db/data/node/1/properties",
  "outgoing_typed_relationships" : "http://localhost:7474/db/data/node/1/relationships/out/{-list|&|types}",
  "incoming_relationships" : "http://localhost:7474/db/data/node/1/relationships/in",
  "incoming_typed_relationships" : "http://localhost:7474/db/data/node/1/relationships/in/{-list|&|types}",
  "create_relationship" : "http://localhost:7474/db/data/node/1/relationships"
}
=> nil

ご覧のとおりです。どんどん先へ進めそうですね。出力には、さまざまな操作に使用できる URL がたくさん含まれています。コマンドの実行結果はかなり分量も多いので、今後はすべてを示すことはしません。実行するコマンドに注目しましょう。さて、次はプロパティを指定してリレーションを作成してみましょう。

[edit] リレーションの作成

上の出力の中からリレーションの作成に適した項目を見つけて利用すれば、リレーションを作成できます。サンプルでは、最後の行にそのものずばりを意味する "create_relationship" という行が出力されています。この行をもとに、さっそく次のコマンドを実行してみましょう。

irb(main):008:0> puts RestClient.post "#{$base}/node/0/relationships", { :to => "#{$base}/node/1", :type => :knows }.to_json, :content_type => :json, :accept => :json
{
  "start" : "http://localhost:7474/db/data/node/0",
  "data" : {
  },
  "property" : "http://localhost:7474/db/data/relationship/0/properties/{key}",
  "self" : "http://localhost:7474/db/data/relationship/0",
  "properties" : "http://localhost:7474/db/data/relationship/0/properties",
  "type" : "knows",
  "end" : "http://localhost:7474/db/data/node/1"
}
=> nil

Neo4j Server スタートガイド」をすでに読んでいる場合は、cURL を使って実行した操作とまったく同じ操作を今度は Ruby を使って行っていることに気付くでしょう。引数も構文も驚くほどよく似ています。ここでは、コマンドで渡すプロパティには Ruby のハッシュを使用し、to_json メソッドで JSON に変換していますが、もちろん代わりに純粋な JSON テキストを記述することもできます。しかし、Ruby のハッシュは、Ruby らしい使い方をするための小さな一歩とでも言うべきものです。次に何か作るとしたら、Neo4j Server のノード、リレーション、プロパティを管理する簡単なコマンドが用意された便利な Ruby のラッパーライブラリでしょうか。実は、Max De Marzi が作成した Neography gem は、まさにそのようなラッパーライブラリです。

[edit] Neography

これまで実行してきたサンプルを今度は Neography gem を使って実行してみましょう。シェルに戻って Neography gem をインストールします。

gem install neography

上に示した RestClient サンプルの場合と同様、今回も Ruby IRB コンソールを使って対話的にサンプルを実行します。同じコマンドは、任意の Ruby スクリプトまたは Rails アプリケーションでも使用できます。使用例をたくさん見たい場合は、neography のドキュメントを参照してください。

コマンド irb を実行して irb を起動し、neography ライブラリを追加してサーバーへの接続をテストします。

irb(main):001:0> require 'rubygems'
=> true
irb(main):002:0> require 'neography'
No Extensions Found: /home/craig/.neography
=> true
irb(main):003:0> neo = Neography::Rest.new
=> #<Neography::Rest:0xb7517748 @log_file="neography.log", @server="localhost", @port=7474, @protocol="http://", @log_enabled=false>
irb(main):004:0> neo.get_root
=> {"reference_node"=>"http://localhost:7474/db/data/node/0", "node"=>"http://localhost:7474/db/data/node", "index"=>"http://localhost:7474/db/data/index"}

サーバー URL を定義する必要がなかったことに注目してください。これは、Neography のデフォルト設定が、デフォルトのローカル Neo4j Server の設定と同じになっているためです。実働環境で Neography 接続を作成するときは、必ず適切なオプションを指定してください。なお、RestClient の場合と異なり、コマンドを入力するたびにサーバー設定を繰り返し指定する必要することがない点に注意してください。その代わりに、上のサンプルでは neo という名前の一種の仮想接続を保持し、以後のコマンドを同じサーバーに送信しています。では、RestClient のサンプルを再度実行し、次に、出力を少し見やすくしてみましょう。

irb(main):005:0> puts neo.get_root.to_json
{"reference_node":"http://localhost:7474/db/data/node/0","node":"http://localhost:7474/db/data/node","index":"http://localhost:7474/db/data/index"}
=> nil
irb(main):006:0> puts JSON.pretty_generate neo.get_root
{
  "reference_node": "http://localhost:7474/db/data/node/0",
  "node": "http://localhost:7474/db/data/node",
  "index": "http://localhost:7474/db/data/index"
}
=> nil

Neography とその背後で使われている HTTParty が出力する JSON コードは、RestClient が出力する JSON コードよりも人間にとって読みにくいので、上の実行例では JSON.pretty_generate メソッドを使って、行ごとにインデントを付けて出力を読みやすく表示しています。実際のアプリケーションでは、このような点を気にする必要はないでしょう。

[edit] ノードの作成

では、先へ進んで新しいノードを作成し、ノードにリレーションを作成してみましょう。

irb(main):007:0> node = neo.create_node :name => 'Neo'
irb(main):008:0> puts JSON.pretty_generate node
{
  "self": "http://localhost:7474/db/data/node/2",
  "property": "http://localhost:7474/db/data/node/2/properties/{key}",
  "data": {
    "name": "Neo"
  },
  "incoming_typed_relationships": "http://localhost:7474/db/data/node/2/relationships/in/{-list|&|types}",
  "outgoing_typed_relationships": "http://localhost:7474/db/data/node/2/relationships/out/{-list|&|types}",
  "incoming_relationships": "http://localhost:7474/db/data/node/2/relationships/in",
  "create_relationship": "http://localhost:7474/db/data/node/2/relationships",
  "all_relationships": "http://localhost:7474/db/data/node/2/relationships/all",
  "traverse": "http://localhost:7474/db/data/node/2/traverse/{returnType}",
  "properties": "http://localhost:7474/db/data/node/2/properties",
  "all_typed_relationships": "http://localhost:7474/db/data/node/2/relationships/all/{-list|&|types}",
  "outgoing_relationships": "http://localhost:7474/db/data/node/2/relationships/out"
}
=> nil

興味深いのは、create_node によって返されたオブジェクトが、通常の Java API の場合のような Node インスタンスや NodeProxy ではなく、neo4j.rb Ruby バインディングにおける NodeMixin を含む Ruby インスタンスでもない点です。返されるのは、(JSON から Ruby Hash に変換された) REST の結果を表す Ruby Hash インスタンスです。すでにふれたように、Neography はピュアな REST クライアントを覆う薄いラッパーです。Neography は操作を簡単に行えるようにするための便利なメソッドを提供していますが、あくまで REST クライアントです。便利な機能としては、node オブジェクトを以後のメソッドの引数として使用できることなどがあります。たとえば、以下の 3 つのメソッド呼び出しは、すべて同じ結果を返します。

実際に試してみましょう。

irb(main):009:0> neo.get_node(node).hash
=> 950656862
irb(main):010:0> neo.get_node({"self"=>"http://localhost:7474/db/data/node/2"}).hash
=> 950656862
irb(main):011:0> neo.get_node(2).hash
=> 950656862

各コマンドからの大量の出力でスペースを浪費するのを避けるため、文字列ハッシュで比較しています。実際に結果はすべて同じになりました。get_node が実際に知る必要があるのはノード ID だけで、これはフル Ruby Hash、または 'self' をキーとする短いハッシュ、あるいは ID 整数それ自体から取得することができます。

[edit] リレーションの作成

では最後に、RestClient で実行したのと同様に、プロパティを指定してリレーションを作成してみましょう。

irb(main):012:0> rel = neo.create_relationship(:knows, neo.get_node(0), node)
=> {"self"=>"http://localhost:7474/db/data/relationship/1", "property"=>"http://localhost:7474/db/data/relationship/1/properties/{key}", "data"=>{}, "end"=>"http://localhost:7474/db/data/node/2", "type"=>"knows", "start"=>"http://localhost:7474/db/data/node/0", "properties"=>"http://localhost:7474/db/data/relationship/1/properties"}
irb(main):013:0> puts JSON.pretty_generate rel
{
  "self": "http://localhost:7474/db/data/relationship/1",
  "property": "http://localhost:7474/db/data/relationship/1/properties/{key}",
  "data": {
  },
  "end": "http://localhost:7474/db/data/node/2",
  "type": "knows",
  "start": "http://localhost:7474/db/data/node/0",
  "properties": "http://localhost:7474/db/data/relationship/1/properties"
}
=> nil

というわけで、簡単なサンプルを実行しました。操作の内容は「Neo4j Server スタートガイド」で cURL を使って行ったものと同じですが、2 つの異なる Ruby REST クライアントを使用しました。

[edit] その他のサンプル

Neography を使ったサンプルには、ほかにも次のようなものがあります。

  • すでに示したノードとリレーションを作成する簡単なサンプル:
require 'rubygems'
require 'neography'

@neo = Neography::Rest.new()
max = @neo.create_node("age" => 31, "name" => "Max")
puts max
peter = @neo.create_node("name" => "Peter")
puts peter
@neo.create_relationship("friends", max, peter)

[edit] トラバース

これまでの部分を書き終えた筆者がふと疑問に思ったのは、トラバースはどうなのかということでした (おそらく読者も同じ疑問を持ったのではないでしょうか) 。REST API はトラバースをサポートしているので、当然、Neography でもトラバースを実行できます。

irb(main):014:0> neo.traverse(neo.get_node(0), "nodes", {:order => :breadth_first}).each {|n| puts n['self']}
http://localhost:7474/db/data/node/2
http://localhost:7474/db/data/node/1

上の実行例で each ループを使っているのは、すべてのノードについて完全な JSON テキストを出力することを IRB に抑止させるためですが、それでもどんなふうに動作しているかは理解できるでしょう。traverse メソッドは、指定されたタイプ (サンプルでは "nodes") のすべてのオブジェクトを含む Ruby Array を返します。3 番目の引数は、トラバーサルでサポートされている以下のすべてのオプションを指定するための省略可能な Hash です。

  • order: "breadth first" または "depth first" のいずれかのトラバーサルの順序です。
  • uniqueness: (オプションについては、API ドキュメントの Uniqueness を参照してください)。
  • relationships: トラバースするリレーションのタイプの Hash の配列で、各要素は "type" と "direction" を含んでいます。
  • prune evaluator: 特定のブランチをたどるトラバースをいつ停止するかを評価するコードです。
  • return filter: 返すオブジェクトです。

[edit] Restfulie

Restfulie はほかの ruby gem 同様、次のようにしてインストールできます。

gem install restfulie

Restfulie は、Rails Web アプリケーションを含む任意の Ruby アプリケーションで使用できます。以下のサンプルでは、irb でいくつかコマンドを実行します。

まず、Restfulie gem を require する必要があります。

irb(main):001:0> require 'restfulie'
=> true

これで、Neo4j と Restfulie を操作できるようになります。サーバーがレスポンスを返すかどうかみてみましょう。

irb(main):003:0> Restfulie.at('http://localhost:7474/db/data/').get.resource

=>  {"relationship_index"=>"http://localhost:7474/db/data/index/relationship", "node"=>"http://localhost:7474/db/data/node", "relationship_types"=>"http://localhost:7474/db/data/relationship/types", "extensions_info"=>"http://localhost:7474/db/data/ext", "node_index"=>"http://localhost:7474/db/data/index/node", "reference_node"=>"http://localhost:7474/db/data/node/0", "extensions"=>{}} 

サーバーからはレスポンスが帰ってくるので、ノードをいくつか作成してみましょう。ノードを作成するのに必要なことは、シンプルな POST リクエストをノード URI に送信することだけです。

irb(main):003:0> Restfulie.at('http://localhost:7474/db/data/node').as('application/json').post({:name=>'Neo'}).resource

=> {"outgoing_relationships"=>"http://localhost:7474/db/data/node/1/relationships/out", "data"=>{"name"=>"Neo"}, "traverse"=>"http://localhost:7474/db/data/node/1/traverse/{returnType}", "all_typed_relationships"=>"http://localhost:7474/db/data/node/1/relationships/all/{-list|&|types}", "property"=>"http://localhost:7474/db/data/node/1/properties/{key}", "self"=>"http://localhost:7474/db/data/node/1", "properties"=>"http://localhost:7474/db/data/node/1/properties", "outgoing_typed_relationships"=>"http://localhost:7474/db/data/node/1/relationships/out/{-list|&|types}", "incoming_relationships"=>"http://localhost:7474/db/data/node/1/relationships/in", "extensions"=>{}, "create_relationship"=>"http://localhost:7474/db/data/node/1/relationships", "all_relationships"=>"http://localhost:7474/db/data/node/1/relationships/all", "incoming_typed_relationships"=>"http://localhost:7474/db/data/node/1/relationships/in/{-list|&|types}"}

結果で返されるのは、新しいリソースに対して実行できる操作のリストです。このリストを利用して、新しいリレーションを作成したり、ノードのプロパティを表示したりすることができます。ここでは、リファレンスノードと新しく作成したノードとの間にリレーションを作成してみましょう。必要な操作は、リファレンスノードのリレーション URI に対し、リレーション先のノードを指定して POST リクエストを送信することだけです。

irb(main):003:0> Restfulie.at('http://localhost:7474/db/data/node/0/relationships').as('application/json').post({:to=>'http://localhost:7474/db/data/node/1', :type=>:knows}).resource

=> {"start"=>"http://localhost:7474/db/data/node/0", "data"=>{}, "self"=>"http://localhost:7474/db/data/relationship/0", "property"=>"http://localhost:7474/db/data/relationship/0/properties/{key}", "properties"=>"http://localhost:7474/db/data/relationship/0/properties", "type"=>"knows", "extensions"=>{}, "end"=>"http://localhost:7474/db/data/node/1"} 

以上でサンプルは終わりです。サンプルで学んだことを活かせば、API と利用可能な URI を使って非常に複雑なグラフも作成できることがわかったと思います。

[edit] 次は

[edit] オブジェクトモデル

neo4j.rb gem に慣れているユーザーは、完全なオブジェクトモデルを求めているかもしれません (詳細については、「スタートガイド (Ruby)」を参照してください)。そのようなオブジェクトモデルは、ここで取り上げた REST クライアントには (まだ) 存在していません。ただし、neo4j メーリングリストでは、まさにこの点に関して、特に Neography と Neo4j.rb の開発者の間で議論が行われています。具体的には、Neography に被せる小さなラッパーを作成して Neography を Neo4j.rb 内部で使用できるようにし、組み込みデータベースとリモート REST データベースの両方にアクセスできるようにするというアイデアが出されています。この方法は少々トリッキーです。というのは、REST API は組み込み API とまったく同じ機能を提供しているわけではないからです。しかし、このアイデアが今後も発展していくのか、発展するならばどういう方向へ向かっていくのか、興味は尽きません。

もちろん、これらのツールはすべてオープンソースですから、開発に興味を持ったら、ぜひ読者自身が牽引役となって活動してください。

[edit] その他の言語

ここではいくつかの REST クライアントを取り上げましたが、サーバーにアクセスするには、ここで取り上げたもの以外にも REST をサポートする任意の言語を使用できます。現在、次のようなガイドが用意されています。

Neo4j のサイト
ツールボックス