Using a Graph Database with Ruby, Part II: Integration

Thiago Jackiw
Share

Yes, it is real, it is finally here! The long waited follow-up to the introductory article is here, just in time for the new year!

In the first article, we learned about graph databases, their differences and advantages over traditional databases, and about Neo4j. In this article, we are going to install Neo4j, integrate and evaluate the gems listed in the first part of this series.

The scenario that we are going to be working with is the continuation of the simple idea in the first article, a social networking example that is capable of producing traversal queries such as “given the fact that Bob is my friend, give me all friends that are friend’s of friend’s of friend’s of Bob”.

Installing Neo4j

Installing the Neo4j server is pretty straightforward. You can download the standalone server here, or install it using Homebrew. For this article and for the sake of simplicity, we are going to use Homebrew:

$ brew update && brew install neo4j

Once the installation is done, the following commands are available:

Start the server:

$ neo4j start

Stop the server:

$ neo4j stop

Open webadmin, which allows you to manipulate the data using a web interface:

$ open http://localhost:7474/webadmin/

Using Neo4j with Ruby

Now that the server is installed, the next step is going to be the integration with Neo4j. As mentioned in the introductory article, the gems that we are going to be evaluating are: Neo4j.rb, Neoid, and Neography.

Neo4j.rb

What is Neo4j.rb, according to the author:

You can think of Neo4j as a high-performance graph engine with all the features of a mature and robust database. The programmer works with an object-oriented, flexible network structure rather than with strict and static tables — yet enjoys all the benefits of a fully transactional, enterprise-strength database.

This gem is really extensive and I encourage you to explore all of its features. For this article I will be focusing only in one aspect: nodes and relationships.

Prerequisites

This gem requires JRuby. If you are using RVM, you can easily install it with:

$ rvm install jruby

Next, switch to JRuby by executing:

$ rvm use jruby

Installing Neo4j.rb

You can add neo4j to your Gemfile:

$ gem 'neo4j'

And run Bundler:

$ bundle

Or install manually with:

$ gem install neo4j

Integration

Before we can create our nodes and our relationships, we need to have our code be part of a transaction. This is required because Neo4j has ACID transactions.

Since our example is relatively simple, we are going to encapsulate our code to run inside a Neo4j::Transaction block:

require 'rubygems'
require 'bundler/setup'
require 'neo4j'

Neo4j::Transaction.run do
  # our code here
end

The next thing that we are going to do is create a few nodes that will represent our users:

require 'rubygems'
require 'bundler/setup'
require 'neo4j'

Neo4j::Transaction.run do
  me   = Neo4j::Node.new(:name => 'Me',   :age => 31)
  bob  = Neo4j::Node.new(:name => 'Bob',  :age => 29)
  mark = Neo4j::Node.new(:name => 'Mark', :age => 34)
  mary = Neo4j::Node.new(:name => 'Mary', :age => 32)
  john = Neo4j::Node.new(:name => 'John', :age => 33)
  andy = Neo4j::Node.new(:name => 'Andy', :age => 31)
end

Next, we create the friendship relationship between the users:

require 'rubygems'
require 'bundler/setup'
require 'neo4j'

Neo4j::Transaction.run do
  me   = Neo4j::Node.new(:name => 'Me',   :age => 31)
  bob  = Neo4j::Node.new(:name => 'Bob',  :age => 29)
  mark = Neo4j::Node.new(:name => 'Mark', :age => 34)
  mary = Neo4j::Node.new(:name => 'Mary', :age => 32)
  john = Neo4j::Node.new(:name => 'John', :age => 33)
  andy = Neo4j::Node.new(:name => 'Andy', :age => 31)

  me.both(:friends)   << bob
  bob.both(:friends)  << mark
  mark.both(:friends) << mary
  mary.both(:friends) << john
  john.both(:friends) << andy
end

This is how the friendship relationship between the users (nodes) is currently represented:

The last step for this example is to traverse the nodes and find all friendship relationship from Me to Andy.

The complete example looks like this:

require 'rubygems'
require 'bundler/setup'
require 'neo4j'

Neo4j::Transaction.run do
  me   = Neo4j::Node.new(:name => 'Me',   :age => 31)
  bob  = Neo4j::Node.new(:name => 'Bob',  :age => 29)
  mark = Neo4j::Node.new(:name => 'Mark', :age => 34)
  mary = Neo4j::Node.new(:name => 'Mary', :age => 32)
  john = Neo4j::Node.new(:name => 'John', :age => 33)
  andy = Neo4j::Node.new(:name => 'Andy', :age => 31)

  me.both(:friends)   << bob
  bob.both(:friends)  << mark
  mark.both(:friends) << mary
  mary.both(:friends) << john
  john.both(:friends) << andy   puts me.outgoing(:friends).depth(5).map{|node| node[:name]}.join(" => ")
end

Running the example

Let’s run our example and see if we get the result that we are expecting.

$ ruby neo4j_example.rb

And as expected, we get the following output:

Me => Bob => Mark => Mary => John => Andy

Neography

What is Neography, according to the author:

Neography is a thin Ruby wrapper to the Neo4j Rest API

This gem is a great alternative if you do not want to use JRuby with your application but still want to take advantage of Neo4j. It has its limitations though, and the author recommends using Neo4j.rb in order to have access to the full power of Neo4j.

Installing Neography

You can add neography to your Gemfile:

gem 'neography'

And run Bundler:

$ bundle

Or install manually with:

$ gem install neography

Integration

Let’s start by creating a few User nodes:

me   = Neography::Node.create(name: 'Me',   age: 31)
bob  = Neography::Node.create(name: 'Bob',  age: 29)
mark = Neography::Node.create(name: 'Mark', age: 34)
mary = Neography::Node.create(name: 'Mary', age: 32)
john = Neography::Node.create(name: 'John', age: 33)
andy = Neography::Node.create(name: 'Andy', age: 31)

Now, let’s create the friendship relationship between the nodes:

me.both(:friends) << bob
bob.both(:friends) << mark
mark.both(:friends) << mary
mary.both(:friends) << john
john.both(:friends) << andy

Visually, this is how the friendship relationship is represented:

Next, we traverse the nodes to find all friendship relationship from Me to Andy:

me.all_simple_paths_to(andy).incoming(:friends).depth(5).nodes.each do |node|
  puts node.map{|n| n.name }.join(' => ')
end

And the complete example looks like this:

require 'rubygems'
require 'neography'

me   = Neography::Node.create(name: 'Me',   age: 31)
bob  = Neography::Node.create(name: 'Bob',  age: 29)
mark = Neography::Node.create(name: 'Mark', age: 34)
mary = Neography::Node.create(name: 'Mary', age: 32)
john = Neography::Node.create(name: 'John', age: 33)
andy = Neography::Node.create(name: 'Andy', age: 31)

me.both(:friends) << bob
bob.both(:friends) << mark
mark.both(:friends) << mary
mary.both(:friends) << john
john.both(:friends) << andy me.all_simple_paths_to(andy).incoming(:friends).depth(5).nodes.each do |node|   puts node.map{|n| n.name }.join(' => ')
end

Starting the server

In order to execute the completed example we first need to initialize the Neo4j server. The first option is run the server that we installed earlier using Homebrew:

$ neo4j start

Another option is to use Neography’s rake tasks to install/start/stop/restart the server. To do so just add the following line to your Rakefile:

require 'neography/tasks'

And the following tasks will be available to you:

rake neo4j:install[edition,version]  # Install Neo4j
rake neo4j:reset_yes_i_am_sure       # Reset the Neo4j Server
rake neo4j:restart                   # Restart the Neo4j Server
rake neo4j:start                     # Start the Neo4j Server
rake neo4j:stop                      # Stop the Neo4j Server

Running the example

When we run the example:

$ ruby neography_example.rb

We get the following output:

Me => Bob => Mark => Mary => John => Andy

This shows all traversed nodes from Me to Andy, just as we expected.

Neoid

What is Neoid, according to the author:

Make your ActiveRecords stored and searchable on Neo4j graph database, in order to make fast graph queries that MySQL would crawl while doing them. Neoid to Neo4j is like Sunspot to Solr. You get the benefits of Neo4j speed while keeping your schema on your plain old RDBMS.

Prerequisites

Since Neoid works with ActiveRecord, let’s start by creating a simple Rails app:

$ rails new neoid_example -S -J

Installing Neoid

You can add neoid to your Gemfile:

gem 'neoid', git: 'git://github.com/elado/neoid.git'

And run Bundler:

$ bundle

Starting the server

Because Neoid is powered by Neography, we have the same options as Neography’s server options. We can either run the standalone server, or include the rake tasks provided by Neography:

Starting the standalone server:

$ neo4j start

To use Neography’s rake tasks to install/start/stop/restart the server, just add the following line to your Rakefile:

require 'neography/tasks'

And the following tasks will be available to you:

rake neo4j:install[edition,version]  # Install Neo4j
rake neo4j:reset_yes_i_am_sure       # Reset the Neo4j Server
rake neo4j:restart                   # Restart the Neo4j Server
rake neo4j:start                     # Start the Neo4j Server
rake neo4j:stop                      # Stop the Neo4j Server

Integration

Now that we have a basic rails app, the next step is to create two models: User and Friendship. The first will hold a few basic attributes, such as name and age. The latter will hold the friendship relationship between the users.

Generating the user model:

$ rails g model user

Generating the friendship model:

$ rails g model friendship

Next, we need to update the migration for both models:

# User migration

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name
      t.string :age
      t.timestamps
    end
  end
end
# Friendship migration

class CreateFriendships < ActiveRecord::Migration
  def change
    create_table :friendships do |t|
      t.integer :user_id
      t.integer :friend_id
      t.timestamps
    end
  end
end

Now, we need to add a custom initializer that contains the configuration needed for Neoid. The author recommends creating the initializer in config/initializers/01_neo4j.rb:

require('neography') unless defined?(Neography)

ENV["NEO4J_URL"] ||= "http://localhost:7474"

uri = URI.parse(ENV["NEO4J_URL"])

neo = Neography::Rest.new(uri.to_s)

Neography.configure do |c|
  c.server = uri.host
  c.port = uri.port

  if uri.user && uri.password
    c.authentication = 'basic'
    c.username = uri.user
    c.password = uri.password
  end
end

Neoid.db = neo

The next step will be to update the User model to include Neoid and set it as Node, and one has_many association:

class User < ActiveRecord::Base
  include Neoid::Node

  attr_accessible :name, :age

  has_many :friends, class_name: Friendship

  neoidable do |c|
      c.field :name
    end
end

Now, we need to update our Friendship model to include Neoid and set it as a Relationship, and a couple of belong_to associations:

class Friendship < ActiveRecord::Base
  include Neoid::Relationship

  attr_accessible :friend

  belongs_to :user
    belongs_to :friend, class_name: User

  neoidable do |c|
    c.relationship start_node: :user, end_node: :friend, type: :friends
  end

  class << self
    def create_both(user, friend)
      user.friends.create(friend: friend)
      friend.friends.create(friend: user)
    end
  end

end

For the final step of this setup we just need to run the migrations:

$ rake db:migrate

Now that the setup is done, we need to add some data. Let’s open the rails console and add a few users and a few friendships.

Here we are creating six users.

me   = User.create(name: 'Me',   age: 31)
bob  = User.create(name: 'Bob',  age: 29)
mark = User.create(name: 'Mark', age: 34)
mary = User.create(name: 'Mary', age: 32)
john = User.create(name: 'John', age: 33)
andy = User.create(name: 'Andy', age: 31)

Next, we need to create the friendship relationship between the users:

Friendship.create_both(me, bob)
Friendship.create_both(bob, mark)
Friendship.create_both(mark, mary)
Friendship.create_both(mary, john)
Friendship.create_both(john, andy)

Using Neo4j’s web interface, we can see how the relationships are visually represented:

Running the example

With the data in place, we now need to traverse and find all friends between Me and Andy. To do this, let’s update our User model and add the following instance method:

def all_friends_to(user)
  neo_node.all_simple_paths_to(user.neo_node).incoming(:friends).depth(5).nodes.each do |node|
    puts node.map{|n| n.name }.join(' => ')
  end
end

Next, restart the rails console and execute the following:

me   = User.first
andy = User.last
me.all_friends_to(andy)
=> "Me => Bob => Mark => Mary => John => Andy"

As we can see, this shows all traversed nodes from Me to Andy, just as expected.

Conclusion

This article demonstrated how to install Neo4j and the basic idea of how to integrate it with a Ruby/Rails application using the different solutions available. Even though the examples given here barely scratched the surface of Neo4j, it should hopefully give you enough knowledge and curiosity to start integrating it on your own projects.

I hope you enjoyed reading this article as much as I enjoyed writing it. Happy New Year!

Resources

CSS Master, 3rd Edition