Intro to Neo4j.rb - Active Model with Neo4j in Rails

TL;DR

  • Side by side Neo4j basic queries and Active Node examples
  • Product recommendation example queries
  • This post is intended for readers who want to build rails applications using Neo4j but have zero experience with Neo4j
  • repo

I started looking into Neo4j this week, and I began with the goal to get myself familiar with using Neo4j in Ruby. Neo4j queries look really intuitive, but using it with ORM takes time in order to get to know how to set it up, build relationships and query. In this post I’d like to demo some examples to show the original queries and Active Node side by side. Hopefully this will be another example for you to reference and help you kickoff your project faster.

Setup

$ brew install neo4j 
$ git clone git@github.com:Neil-Ni/neo4jrb-northwind-example.git 
$ cd neo4jrb-northwind-example
neo4jrb-northwind-example $ rake neo4j:install  
neo4jrb-northwind-example $ rake neo4j:start  
Starting Neo4j in development...  
Starting Neo4j Server...WARNING: not changing user  
process [30830]... waiting for server to be ready..... OK.  
http://localhost:7474/ is ready

Visit http://localhost:7474/ and type :play northwind graph

And you should see the default Northwind example in a card:

First walk through the steps and see how to translate relational database into graph database, and get familiar with a few Neo4j queries in the example.

Rails example

The next step is to set up the exact same data structure but in Rails. Let's drop the example database and seed the same database with rake task:

$ rake neo4j_db_drop
$ rake neo4j_db_seed
Creating (:Category) Nodes  
Creating (:Supplier) Nodes  
Creating (:Product) Nodes  
Creating (:Customer) Nodes  
Creating (:Order) Nodes  
Creating [:CONTAINS] Relationships (Ex: (:Order) - [:CONTAINS] -> (:Product))  

It's now building the same set of data with models and relationships defined in the Rails app, which means we can start up rails console and try to query with our models.

Hopefully, by now you have taken a quick look into the official Northwind example in Neo4j browser. Below are the Active Node equivalences to the queries used in the Northwind example:

Categories that each product supplier supplies

Query:

MATCH (s:Supplier)-->(:Product)-->(c:Category)  
RETURN  
    s.company_name as Company,
    collect(distinct c.name) as Categories

Active Node:

Supplier.as(:s).  
    products.category(:c).
    pluck([
        's.company_name',
        'collect(distinct c.name)'
    ])
Product suppliers that supply products in Produce category

Query:

MATCH (s:Supplier)-->(:Product)-->(c:Category {name:"Produce"})  
RETURN  
    DISTINCT s.company_name as ProduceSuppliers

Active Node:

Supplier.as(:s).products.category.where(name: 'Produce').  
    pluck([
        'DISTINCT s.company_name as ProduceSuppliers'
    ])
Total quantity of product (in Produce category) purchased by each customer

Query:

MATCH (cust:Customer)-[:PURCHASED]->(:Order)-[contain:CONTAINS]->(p:Product),  
      (p)-[:PART_OF]->(c:Category {name:"Produce"})
RETURN  
    DISTINCT cust.contact_name as CustomerName,
    SUM(contain.quantity) AS TotalProductsPurchased

Active Node:

Customer.as(:c).  
    orders.products(:p, :orders).
    category.where(name: 'Produce').
    pluck([
        'DISTINCT(c.contact_name) as CustomerName',
        'SUM(orders.quantity) as TotalProductsPurchased'
    ])

Advanced queries/Product recommendation queries

Here are other example queries that show how easy it is to perform these types of recommendation queries in graph database
(Unfortunately I don’t know how to translate these into Active Node yet :( ):

People who bought this product (with product name Chai) also bought

Query:

MATCH (p:Product)<-[:CONTAINS]-(:Order)<-[:PURCHASED]-(:Customer)-[:PURCHASED]->(:Order)-[contain:CONTAINS]->(otherProducts:Product)  
WHERE p.name = "Chai"  
RETURN  
    otherProducts.name as ProductName,
    SUM(contain.quantity) as totalPurchase
ORDER BY totalPurchase DESC  

Active Node:

Product.where(name: 'Chai').  
    orders.customer.orders.
    products(:otherProduct, :contain).
    order('totalPurchase DESC').
    pluck([
        'otherProduct.name AS productName',
        'SUM(contain.quantity) AS totalPurchase'
    )]
People who have purchased in similar categories as me (Ex: contact_name: "Simon Crowther")

Query:

MATCH (me:Customer { contact_name: "Simon Crowther" })-[:PURCHASED]->(:Order)-[:CONTAINS]->(:Product)-->(c:Category)<--(:Product)<-[contain:CONTAINS]-(:Order)<-[:PURCHASED]-(otherCustomer:Customer)  
WHERE NOT me=otherCustomer  
RETURN  
    otherCustomer.contact_name as SimilarCustomer,
    COUNT(*) as Similarity,
    SUM(contain.quantity) as TotalPurchase
ORDER BY Similarity DESC  
People who have purchased similar products as me (Ex: contact_name: "Simon Crowther")
MATCH (me:Customer { contact_name: "Simon Crowther" })-[:PURCHASED]->(:Order)-[:CONTAINS]->(:Product)<-[contain:CONTAINS]-(:Order)<-[:PURCHASED]-(otherCustomer:Customer)  
WHERE NOT me=otherCustomer  
RETURN  
    otherCustomer.contact_name,
    SUM(contain.quantity) as TotalPurchase,
    COUNT(*) as Similarity
ORDER BY Similarity DESC  
Other products that people who have purchased similar products as me also bought (Ex: contact_name: "Simon Crowther")
MATCH (me:Customer { contact_name: "Simon Crowther" })-->(:Order)-->(p:Product)<-[contain:CONTAINS]-(:Order)<--(otherCustomer:Customer),  
(otherCustomer)-->(o:Order)-->(otherProduct:Product)
WHERE NOT me=otherCustomer and NOT (me)-->(o)-->(otherProduct)  
RETURN  
    p.name,
    SUM(contain.quantity) as TotalPurchase,
    COUNT(*) as Similarity
ORDER BY Similarity DESC  

Official Neo4j.rb page

neo4j-core wiki

Advanced Data Modeling Examples

Asset Portal with Screencasts

comments powered by Disqus