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


## 目的

もともとウチの社内勉強会でokappy先輩が[RubyとグラフデータベースNeo4jでソーシャルデータをいじくる](http://qiita.com/okappy/items/7fc03e5f4a262d1e9f05)というものを実施していて、
これはGreenでも使ってみたいという事で導入してみました。

調べてみると参考にできる記事があったので、参照リンクでつけています。

参照)
[rubyからグラフデータベースneo4jを利用する](http://qiita.com/k-shogo/items/fce39fd5fe6c474586a7)
[グラフDBのNeo4jを1日触ってみた](http://engineer.wantedly.com/2014/01/02/neo4j-introduction.html)

## 使いはじめる

まずはneo4jのインストール

```
$ brew install neo4j
```

```
$ neo4j start
```

そしたら[browserの管理画面](http://localhost:7474/browser/)にアクセスし、
ID: neo4j, PW: neo4j を入れてログインする。

アクセス出来ればOKです。

## 早速nodeを作ろう

グラフDBのneo4j
neo4jの問い合わせ言語として用意されているcypher
neo4jにはRESTライクなApiが用意されており、そちらをRubyから扱いやすくしてくれている[neography](https://github.com/maxdemarzi/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](https://qiita-image-store.s3.amazonaws.com/0/5365/5fc62b37-7203-620f-e52a-d20377253e55.png "スクリーンショット 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](https://qiita-image-store.s3.amazonaws.com/0/5365/fa207760-43c5-7755-2daf-09e4527bc28f.png "スクリーンショット 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](https://qiita-image-store.s3.amazonaws.com/0/5365/30c6054b-7911-ead7-5e83-ae7bb71e927d.png "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](https://qiita-image-store.s3.amazonaws.com/0/5365/b1c024b3-c276-e61c-37b3-fd5f80592161.png "ここが取れる.png")

## 幅優先探索/深さ優先探索とは？

こちらは探索の方法なのでGraphDBを使う上で知っておく必要があります。
[通勤・通学中に理解する深さ優先探索と幅優先探索【アルゴリズム】](http://ottati.hatenablog.com/entry/2014/01/31/092528)

## その他のメソッド

### uniquness

- nodeglobal
- nodepath
- noderecent
- relationshipglobal
- relationshippath
- relationship recent


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

[source](http://www.rubydoc.info/github/maxdemarzi/neography/Neography%2FRest%2FHelpers%3Aparse_uniqueness)


### filter

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

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

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