目的
もともとウチの社内勉強会でokappy先輩がRubyとグラフデータベースNeo4jでソーシャルデータをいじくるというものを実施していて、
これはGreenでも使ってみたいという事で導入してみました。
調べてみると参考にできる記事があったので、参照リンクでつけています。
参照)
rubyからグラフデータベースneo4jを利用する
グラフDBのNeo4jを1日触ってみた
使いはじめる
まずはneo4jのインストール
$ brew install neo4j
$ neo4j start
そしたらbrowserの管理画面にアクセスし、
ID: neo4j, PW: neo4j を入れてログインする。
アクセス出来ればOKです。
早速nodeを作ろう
グラフDBのneo4j
neo4jの問い合わせ言語として用意されているcypher
neo4jにはRESTライクなApiが用意されており、そちらをRubyから扱いやすくしてくれているneography
勿論Railsのアプリケーションなのでneographyを使って処理を行います。
まずはnodeを登録。
class Neo4jModel
def self.generate
# 初期設定
@neo = Neography::Rest.new({
authentication: 'basic',
username: "neo4j",
password: "green"
})
# ノードを作成する
user_counts = User.last.id
for num in 1..user_counts
node = @neo.create_node("user_id" => num) #ノードを登録
@neo.add_label(node, "User") #ラベルを登録
end
end
end
とりあえずこれでnodeを作りまくる!
イメージこんな感じになればいい感じ。
nodeは作れましたが...neographyでmatchの処理をするにはindexを張る必要があります。
orz...最初にもっと読み込んでからやればよかった。。。という事でやり直し。
再びチャレンジ
# リレーションとノードの削除 cypherで実行
$start n=node(*) match n-[r]-() delete r;
$start n=node(*) delete n;
neographyを用いてindexを貼るにはNeography::Nodeオブジェクトを扱う必要があります。
ということで、先程のものを書き換える。
class Neo4jModel
def self.constract_any_nodes_and_relations
@neo = Neography::Rest.new({
authentication: 'basic',
username: "neo4j",
password: "green"
})
User.select('id, birthday').find_each do |user|
# 以下でuserのnode構築
user_node = Neography::Node.create({ user_id: user.id, age: user.age })
@neo.add_label(user_node, "User")
user_node.add_to_index("user_index", "user_id", user.id)
# リレーション構築(応募)
user.applies.each do |apply|
job_id = apply.job_id
job_node = first_or_create_for_job_offer_node(job_id)
user_node.outgoing(:apply) << job_node
if job_apply.passed?
# 通過している場合にはjobからもリレーションを構築
job_node.outgoing(:pass) << user_node
end
end
# リレーション構築(気になる)
user.favorites.each do |favorite|
job_id = favorite.job_id
job_node = first_or_create_for_job_offer_node(job_id)
if favorite.only_user?
user_node.outgoing(:favorite) << job_node
elsif favorite.only_client?
user_node.incoming(:favorite) << job_node
else
user_node.both(:favorite) << job_node
end
end
# リレーション構築(閲覧)
user.footprints.each do |footprint|
job_id = footprint.job_id
job_node = first_or_create_for_job_offer_node(job_id)
user_node.outgoing(:footprint) << job_node
end
end
end
private
def self.first_or_create_for_job_node(job_id)
job_node = Neography::Node.find("job_index", "job_id", job_id)
if job_node.blank?
# node構築
job_node = Neography::Node.create(job_id: job_id)
@neo.add_label(job_node, "Job")
job_node.add_to_index("job_index", "job_id", job_id)
end
job_node
end
end
これでOKです。
LIMITを設定しないとブラウザが落ちてしまうレベルになりました。
いい感じです。
レコメンドを出してみる
ではこれらのrelationを用いてレコメンドを出してみたいと思います。
まずは、自分が気になるした求人に、気になるしている人が気になるした他の求人。
※日本語解りづらくてすみませんw
user_node = Neography::Node.find("user_index", "user_id", "393767").first
# 自分が気になるした求人
user_node.outgoing(:favorite)
.depth(1)
.uniqueness(:nodeglobal)
.map {|n| n.job_id }
これだとどう追ってもノイズが入る・・・
「気になる!」という同じ動作を同じrelationにしたので、違うものにする必要がありそうw
再びチャレンジ②
# リレーションとノードの削除 cypherで実行
$start n=node(*) match n-[r]-() delete r;
$start n=node(*) delete n;
class Neo4jModel
def self.constract_any_nodes_and_relations
@neo = Neography::Rest.new({
authentication: 'basic',
username: "neo4j",
password: "green"
})
User.select('id, birthday').find_each do |user|
# 以下でuserのnode構築
user_node = Neography::Node.create({ user_id: user.id, age: user.age })
@neo.add_label(user_node, "User")
user_node.add_to_index("user_index", "user_id", user.id)
# リレーション構築(応募)
user.applies.each do |apply|
job_id = apply.job_id
job_node = first_or_create_for_job_offer_node(job_id)
user_node.outgoing(:apply) << job_node
if job_apply.passed?
# 通過している場合にはjobからもリレーションを構築
job_node.outgoing(:pass) << user_node
end
end
# リレーション構築(気になる)
user.favorites.each do |favorite|
job_id = favorite.job_id
job_node = first_or_create_for_job_offer_node(job_id)
if favorite.only_user?
user_node.outgoing(:user_favorite) << job_node
elsif favorite.only_client?
user_node.incoming(:client_favorite) << job_node
else
user_node.outgoing(:user_favorite) << job_node
user_node.incoming(:client_favorite) << job_node
end
end
# リレーション構築(閲覧)
user.footprints.each do |footprint|
job_id = footprint.job_id
job_node = first_or_create_for_job_offer_node(job_id)
user_node.outgoing(:footprint) << job_node
end
end
end
private
def self.first_or_create_for_job_node(job_id)
job_node = Neography::Node.find("job_index", "job_id", job_id)
if job_node.blank?
# node構築
job_node = Neography::Node.create(job_id: job_id)
@neo.add_label(job_node, "Job")
job_node.add_to_index("job_index", "job_id", job_id)
end
job_node
end
end
レコメンドを出してみる(Part2)
user_node = Neography::Node.find("user_index", "user_id", "298712")
# 気になるベースのレコメンド
user_node.outgoing(:user_favorite)
.incoming(:user_favorite)
.depth(3)
.uniqueness(:nodeglobal)
.filter("position.length() == 3;")
.map {|n| n.job_id }
こう設計すればincoming/outgoingのuser_favoriteを追えば取りたいnodeを取れそう。
幅優先探索/深さ優先探索とは?
こちらは探索の方法なのでGraphDBを使う上で知っておく必要があります。
通勤・通学中に理解する深さ優先探索と幅優先探索【アルゴリズム】
その他のメソッド
uniquness
- nodeglobal
- nodepath
- noderecent
- relationshipglobal
- relationshippath
- relationship recent
nodeglobalはnodeをuniqに、relationshipglobalはrelationshipをuniqに...までは分かるけど、
後はどういうuniqunessなのか不明w
ググっても出てこないので、誰か分かれば教えて下さい。
filter
これはmysqlにおけるwhereのようなものですね。
例えば、2階層目だけ取りたい場合は
filter("position.length() == 2;")
と指定すると意図通り取れます。
https://github.com/maxdemarzi/neography