Develop with pleasure!

福岡でCloudとかBlockchainとか。

neo4j with ruby

BItcoinのブロックチェーンのデータをグラフDBに突っ込もうと思い、neo4jを使ってみる。

neo4jをrubyで扱うのにはneo4jってgemが便利そう。

github.com

neographyってneo4jのREST APIをラップしたgemもあるけど、neo4jではノードの構造をモデルとして扱えるのが魅力的。

セットアップ

gemのインストール

今回はRailsじゃなくてgemのライブラリで使用するので、gemspecファイルにgemを定義する。

spec.add_runtime_dependency 'neo4j', '~> 5.0.0'

Rakefileにタスクを追加

Rakefileに以下のコードを追加する。

load 'neo4j/tasks/neo4j_server.rake'
load 'neo4j/tasks/migration.rake'

neo4jのインストール

rakeタスク neo4j:install[edition,environment] でneo4jがインストールできる。
↓では、neo4jのcommunityエディションの最新版がtest環境としてインストールされる。

$ bundle exec rake neo4j:install[community-latest,test]

プロジェクトルート/db直下にneo4jが指定した環境ごとにインストールされる。

neo4jの起動

rake neo4j:start[environment]を実行すれば対象のneo4jが起動する

$ bundle exec rake neo4j:start[test]
Starting Neo4j test...
WARNING: Max 4096 open files allowed, minimum of 40 000 recommended. See the Neo4j manual.
Starting Neo4j Server...WARNING: not changing user
process [3030]... waiting for server to be ready....... OK.
http://localhost:7474/ is ready.

http://localhost:7474/ にアクセスするとneo4jが起動してるのが確認できる
f:id:techmedia-think:20160224110015p:plain

neo4jの停止

↓のrakeタスクで停止

$ bundle exec rake neo4j:stop[test]
Stopping Neo4j test...
Stopping Neo4j Server [3030]....%

Rubyでneo4jを扱う

neo4jへの接続

↓で先ほど起動したローカルのneo4jに接続できる。

session = Neo4j::Session.open(:server_db)

リモートのneo4jに接続する場合は、↓のようにオプションを渡す。

session = Neo4j::Session.open(:server_db, 'http://localhost:7474', {basic_auth: {username: 'foo', password: 'bar'}})

JRubyだと、:server_dbの他に:embedded_dbを指定でき、同じJVMで動作してるneo4jに接続できるらしい。

Neo4j::ActiveNode

ActiveNodeはRailsActiveRecordみたいにneo4jのノードを扱うことができるモジュール。
利用する際は、↓のようにNeo4j::ActiveNodeをインクルードする。

class Block
  include Neo4j::ActiveNode
end
Property

モデルにneo4jのノードのプロパティをpropertyメソッドを使って宣言する。

class Block
  include Neo4j::ActiveNode

  property :hash, index: :exact, constraint: :unique
  property :height, type: Integer
  property :version
  property :merkle_root
  property :time, type: Time
  property :nonce, type: Integer
  property :bits
  property :difficulty
  property :chain_work
  property :previous_block_hash
  property :next_block_hash

end

インデックスを登録したい場合は、propertyメソッドにindexオプションを指定する。

各プロパティの型はactive_attrベースで以下の型をサポートしてる。

  • Boolean
  • DateTime
  • Date
  • Float
  • Integer
  • String
  • Time
    v3とv4では実態はDateTimeみたい。

constraint: :unique オプションでユニーク制約を付加できるけど、ActiveRecordと同じで完全なユニーク制約が保証される訳ではない。

コールバック

ActiveRecordと同様、以下のフックポイントがある。

  • initialize
  • validation
  • find
  • save
  • create
  • update
  • destroy
created_atとupdated_at

これもActiveRecordと同様、created_atとupdated_atというプロパティを宣言しとけば、作成と更新のタイミングで自動的に時刻がセットされる。

validation

Active Modelのvalidationをサポート

モデルの関連

モデルの関連付けも↓のように定義できる。

class Block
  include Neo4j::ActiveNode

  has_many :in, :transactions, origin: :block
end

class Transaction
  include Neo4j::ActiveNode
  
  has_one :out, :block, type: :block
end

Neo4j::ActiveNodeをインクルードしたことで、クラスメソッドhas_manyhas_oneが作られる。それぞれ、:in:out:bothのシンボルを引数にとり、以下のオプションが指定可能。

  • :type
    association nameをベースにしたデフォルトのrelationship typeをオーバーライドするオプション
  • :model_class
    関連先のノードのモデルを指定する際のオプション。ActiveRelを使ってる場合はActiveRelクラスが使われる
  • :rel_class
    全ての関連の設定を担当するActiveRelモデルを指定するオプション。(:typeや:originと一緒に使うことはできない)
  • :origin
    2つのモデル間両方で関連を定義することなく、1つのモデル上で関連を定義するためのオプション?
  • :before:after
    関連が作成された際のコールバック
  • :unique
    ノード間の関連を作る際にCypherでCREATE UNIQUEを実行するオプション
  • :dependent
    ActiveRecordのdependentと同様、ノードが削除された際の振る舞いを定義するオプション

Neo4j::ActiveRel

ActiveRelを使うとActiveNodeオブジェクト間の関連を定義できる。ActiveRelは必須要素ではないけど、複雑な関連を定義する際に便利な仕組み。

トランザクション

以下のようにトランザクションロールバックもできるもよう。

Neo4j::Transaction.run do |tx|
   Neo4j::Node.create
   Block.create
   tx.mark_failed
end

もしくは

begin
  tx = Neo4j::Transaction.new
  # ノードへ登録処理など
rescue 任意の例外
  tx.mark_failed
ensure
  tx.close
end

DBのマイグレーション

↓のようなrakeコマンドを実行することでマイグレーションが可能。

$ bundle exec rake neo4j:migrate[task_name,subtask]

時刻するには予めtask_nameと同じカスタムタスクを/db/neo4j-migrate/直下に作っておく必要がある。マイグレーションタスクの作り方の詳細は↓
Neo4j v3 Migrations · neo4jrb/neo4j Wiki · GitHub

データの検索

ActiveRecordに似たfinderやクエリメソッドが提供されている↓
Search and Match · neo4jrb/neo4j Wiki · GitHub

DBのクリア

テスト実行時など、neo4jのデータをクリアしたい場合は↓の方法でデータのクリアが可能。

  • クエリを使った削除
    以下のクエリを使って削除する。DBが完全にクリアな状態になるわけではないが高速。
Neo4j::Session.current._query('MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r')
  • 完全削除
    Rakefilerequire 'neo4j/tasks/neo4j_server'を記述しておき、↓のrakeタスクを実行する。
$ bundle exec rake neo4j:reset_yes_i_am_sure[test]

database_cleanerもneo4jに対応しているので、spec_helper.rbで↓ように設定すればテスト前後でトランザクション使ったデータのお掃除が可能になる。

RSpec.configure do |config|

  DatabaseCleaner[:neo4j, connection: {type: :server_db, path: 'http://localhost:7475'}].strategy = :transaction

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end

end

ActiveRecordライクに使えるのでRails使って開発したことがあればとっつきやすそう。