LoginSignup
22
23

More than 5 years have passed since last update.

neo4jを仕事で使っているrailsアプリに組み込んで見る

Posted at

目的

もともとウチの社内勉強会で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を作りまくる!

スクリーンショット 2015-12-21 0.09.48.png

イメージこんな感じになればいい感じ。
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です。

スクリーンショット 2015-12-21 0.58.45.png

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 }

node.png

これだとどう追ってもノイズが入る・・・
「気になる!」という同じ動作を同じ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を取れそう。
ここが取れる.png

幅優先探索/深さ優先探索とは?

こちらは探索の方法なのでGraphDBを使う上で知っておく必要があります。
通勤・通学中に理解する深さ優先探索と幅優先探索【アルゴリズム】

その他のメソッド

uniquness

  • nodeglobal
  • nodepath
  • noderecent
  • relationshipglobal
  • relationshippath
  • relationship recent

nodeglobalはnodeをuniqに、relationshipglobalはrelationshipをuniqに...までは分かるけど、
後はどういうuniqunessなのか不明w
ググっても出てこないので、誰か分かれば教えて下さい。

source

filter

これはmysqlにおけるwhereのようなものですね。
例えば、2階層目だけ取りたい場合は

filter("position.length() == 2;")

と指定すると意図通り取れます。
https://github.com/maxdemarzi/neography

22
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
23