BItcoinのブロックチェーンのデータをグラフDBに突っ込もうと思い、neo4jを使ってみる。
neo4jをrubyで扱うのにはneo4jってgemが便利そう。
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が起動してるのが確認できる
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はRailsのActiveRecordみたいに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と同じで完全なユニーク制約が保証される訳ではない。
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_manyとhas_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')
- 完全削除
Rakefileにrequire '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使って開発したことがあればとっつきやすそう。