BLOG

Using Neo4j with Ruby on Rails

What is Neo4j? Neo4j is an open-source NoSQL graph database implemented in Java and Scala. It is one of the market leading graph databases.

First of all, what is Neo4j?

To sum this up, Neo4j is an open-source NoSQL graph database implemented in Java and Scala. It’s development started on 2003, but it got available as an open source database only in 2007. You can see it on GitHub. Neo4j is one of the market leading graph databases, you can read more about it on their homepage and Download Neo4j.

What are the use cases of Graph databases and what we will be building?

A powerful feature of using a graph database, is that you can create your own in-graph data structures — for example a linked list, basic friend finding based on social neighborhood or anything else that has complicated relationships.

As a huge football fan if I wanted to create an application for a Football league and do it using Neo4j, since this database is perfectly suitable for this kind of data representation. Our football league will have Leagues, Teams and Players. Leagues will have a level and will have many teams. Logically teams will have players.

Setting everything up within a Rails project.

Lets start from scratch and create new Rails project: rails new football_league
add 'neo4j', '~> 4.1.0 to our gem-file and don’t forget to do bundle install

Since Neo4J has implicit schema, we don’t care about migrations so we can just delete db/migrate folder.

Next we will need to take care of our data layer. The goal here is to replace everything related to ActiveRecord with Neo4J ruby library. We need to make sure that all ActiveRecord libraries are excluded and all Neo4J libraries are included. Also when we generate new model, we need to make sure that it will be Neo4J model.

Go to config/application.rb and lets configure it:

require File.expand_path('../boot', __FILE__)

require "rails"

%w(
  neo4j
  action_controller
  action_mailer
  sprockets
).each do |framework|
  begin
    require "#{framework}/railtie"
  rescue LoadError
  end
end

Bundler.require(*Rails.groups)

module Neo4jForFun  
  class Application < Rails::Application
    config.generators do |g|
      g.orm :neo4j
    end

    config.neo4j.session_options = { basic_auth: { username: 'your_username', password: 'your_password'} } 
    config.neo4j.session_type = :server_db 
    config.neo4j.session_path = 'http://localhost:7474'
  end
end

We replaced require rails/all with

%w(
  neo4j
  action_controller
  action_mailer
  sprockets
).each do |framework|
  begin
    require "#{framework}/railtie"
  rescue LoadError
  end
end

As I wrote we don’t need to require all anymore, because we don’t use migrations and ActiveRecord.

Also you can see in our application.rb file that it is using neo4j configuration methods:

1) session_type we have two ways to install Neo4j server_db and embedded_db

The gem supports both server and embedded modes.

Server: Connecting to an external Neo4j database instance with the REST API
Embedded: Running Neo4j in the Ruby process

The difference is that Server is supported by both MRI and JRuby, but Embedded is supported only under JRuby

The advantage of embedded mode is direct access to the database via the Neo4j Java APIs, which gets you a lot more speed, but is more complex. One disadvantage is that your ruby process is now your server process, so if you want to deploy/do maintenance, it becomes trickier.

The advantage of the server mode is having the nice separation of concerns. Connection via cypher queries is relatively simple.

2) session_path is the location of our database. We hook it up to http://localhost:7474 if we are on development/test.

We are almost there, before running our rails server lets do some last changes to our rails app.
Comment out following lines:

config/environments/development.rb
# config.active_record.migration_error = :page_load
config/environments/production.rb
# config.active_record.dump_schema_after_migration = false

and it’s done now we can run our rails s.

Congratulations, we have successfully configured our Rails application to use Neo4j as database.

How to declare relationships?

In Neo4j, every Model object is node so all has_many/has_one method calls begin with declaration of direction. They can be: :in, :out and :both

Lets start with creating League model
rails g model League name:string This generated for us:

class League  
  include Neo4j::ActiveNode
  property :name, type: String

end

As you can see we don’t inherit from ActiveRecord::Base as usual, but we include Neo4j::ActiveNode.

Lets create some leagues via console and check what will we get.

➜  football_league git:(master) ✗ rails c
Loading development environment (Rails 4.1.9)  
2.2.1 :001 > League  
 => League(name: String) 
2.2.1 :002 > League.create(name: "League 1")  
 => #<League name: "League 1"> 
2.2.1 :003 > League.create(name: "League 2")  
 => #<League name: "League 2"> 
2.2.1 :004 > League.create(name: "League 3")  
 => #<League name: "League 3">

Now we have created three leagues. Lets see how do they look in Neo4j. Open up http://localhost:7474/browser/ and there will be Node label called League Click on that and you should see:

Using Neo4j with Ruby on Rails

Now we have three leagues, but we don’t know which one is highest league and which one is the lowest, so lets add another field to our model called rank with type of Integer.

property :rank, type: Integer

Simply by adding the property class method on our League model, we have added new attribute rank
Let’s update rank, so that we know which league is higher

2.2.1 :011 > League  
 => League(name: String, rank: Integer) 
2.2.1 :012 > League.where(name: "League 1").first.update_attribute(:rank, 1)  
 => true 
2.2.1 :013 > League.where(name: "League 2").first.update_attribute(:rank, 2)  
 => true 
2.2.1 :014 > League.where(name: "League 3").first.update_attribute(:rank, 3)  
 => true

Let’s create Team model and add relations to our Leagues.

rails g model team name:string

To your League model add: has_many :in, :teams, origin: :league and to Team model add `hasone :out, :league, type: :playin

We have declared relationships that each League can have many Teams and each Team has_one league. To see how this looks in our database viewer, lets add some teams.

%w(
  neo4j
  action_controller
  action_mailer
  sprockets
).each do |framework|
  begin
    require "#{framework}/railtie"
  rescue LoadError
  end
end

Now in http://localhost:7474/browser/ we should see new Node label called Team and Relationship type called play_in lets click on that.

Using Neo4j with Ruby on Rails
Here you can see that each League now has four Tems.

Lets take a closer look at one of the Leagues:

Using Neo4j with Ruby on Rails
And if you click on a node you will see detailed information below.

Using Neo4j with Ruby on Rails

Lets check how does that look in our console:
application.rb
So which one do you prefer, console or graphic presentation? I think the answer is obvious.

Now when we know how to create simple relations between models(nodes), lets create Player model and add some players to each Team.

rails g model player first_name:string last_name:string
session_type

And don’t forget to add has_many :in, :players, origin: :team to year Team model
I have already added 11 Players for each team the same way as we added Teams to League, let’s check how does it look:

Using Neo4j with Ruby on Rails
And a closer look up for single team:

Using Neo4j with Ruby on Rails
Looks pretty good!

And finally, I want to create a relationship that goes both ways. Let’s imagine that team can follow another team in order to see their progress or steal their players (I know, not cool, but it’s life).
We can do it in the following ways:
server_db
To see how this will look, let’s do a bidirectional relationship:
embedded_db

Using Neo4j with Ruby on Rails
And now for the final picture, let’s see how it looks all together:

Using Neo4j with Ruby on Rails

Conclusions

So, as you saw from this post, Neo4j is really great for relational data representation and it’s really fast. In some ways I think these relations are easier to write and understand than ActiveRecord relations.

I will cover more stuff for Neo4j with Rails in next posts and I will continue experimenting with this project, check it on GitHub football_league

I would like to thank Brian Underwood (One of Neo4j Ruby gem core developers) for taking time and reviewing this article.

Hope you enjoyed.

Previous
Next