Skip to content
Andreas Ronge edited this page Apr 26, 2012 · 3 revisions

The Neo4j::Rails::Model and Neo4j::Rails::Relationship implements the Rails ActiveModel interface and a subset of the ActiveRecord API.

Example:

class IceCream < Neo4j::Model
  property :flavour
  validates_presence_of :flavour
end

IceCream.new.valid?  # => false
IceCream.new(:flavour => "vanilla").valid?  # => true

Neo4j::Rails::Model

A Neo4j::Rails::Model wraps a Neo4j::Node. The Neo4j::Rails::Model and Neo4j::Rails::Relationship are Active Model compliant and does implement some Active Record method.

Callbacks & Observers

The following callbacks are defined: initialize, valid?, create_or_update, create, update, destroy. See the rails documentation when they are called.
There is also support for Active Model observers, see Neo4j::Rails::Observer and neo4j-observers-example

Model.new

The Neo4j::Model.new methods does not require a transaction (unlike the Neo4j::Node.new method)
since it creates a node in memory with no connection to the neo4j database. This makes it easier to create forms that don’t touch the database if validation fails by using the ActiveRecord-style two-step Model.new + Model#save creation. The node will be saved to the database when the save method is called.

Model.save

Saves the node if the validation was successful. It will create a new transaction if neccessarly.
Notice, nested nodes will also be saved and validated (see below – does raise exception !). Validation can be skipped by model.save( :validate => false )

Model.update_attributes

Updates the model with the given attributes and saves the model if the validation is successful. Will create a new transaction if neccessarly.

Declaring Properties

The Neo4j::Rails::Model#property and Neo4j::Rails::Relationship#property method accept additional configuration parameters:, :default, :length and :null and :type and converter, see Neo4j::Rails::Attributes::ClassMethods#property

Example:

class MyModel < Neo4j::Rails::Model
  # gives title "Mr" unless it is set specifically
  # ensures it is a maximum of 3 chars long
  # ensures it is never saved with a nil value
  property :title, :default => "Mr", :limit => 3, :null => false
  property :superuser, :foo, :type => :boolean  # both superuser and foo will be converted to true/false values
end

Declaring Lucene Index

You use the Neo4j::Rails::Model#property and Neo4j::Rails::Relationship#property to declare lucene index (same as for Neo4j::NodeMixin, see Neo4j::Wrapper-Lucene

Example:

class Role < Neo4j::Rails::Relationship
  property :since, :type => Fixnum, :index => :exact
end

Validation

All the normal validations work for both Neo4j::Rails::Model and Neo4j::Rails::Relationship

Example:

class Person < Neo4j::Rails::Model
  property :email, :index => :exact
  validates :email, :uniqueness => true, :allow_blank => false
  validates :password, :presence     => true,
                   :confirmation => true,
                   :length       => { :within => 6..40 }
end

In order to get uniquess validation to work you must have an exact index on the property, as shown above (index :email).

Notice If you are saving a node which has changed relationships/nodes an exception will be thrown if they are not valid !
Notice It is not enought to have a unique validation to make sure nodes and relationships are unique, see below.

Unique Entities

If you want to be 100% sure of having unique nodes or relationship it is not enough with a unique validation on the property (since of possible concurrency issues). Instead, you should declare the property with a :unique => true and use the get_or_create method, see below:

class Person < Neo4j::Rails::Model
  property :email, :index => :exact, :unique => true
  property :name
end

# Either create a new person if there is no person with this email or return a person with this email.
node = Person.get_or_create(:name => 'foo', :email => 'email')

Create with .outgoing(:reltype) <<

You do not need to declare a relationship with a has_n Neo4::Rails::Model class method.
Instead you can at anytime create/delete/update a relationship between two nodes.

n1 = Neo4j::Rails::Model.new
n2 = Neo4j::Rails::Model.new
n1.outgoing(:friends) << n2
n1.save

Notice that you can also create the something on incoming relationship.
The example above can also be written like this:

n2.incoming(:friends) << n1
n2.save

Notice you must call save in order to persist the relationship do disk.

Create with Neo4j::Rails::Relationship

The outgoing method from the example above does not return a relationship object since it allows to chain several nodes with the << operator (creating several relationship in one line).
If you want to set properties on the relationship it’s more convinient to create it with the Neo4j::Rails::Relationship

# create a relationship with one property
rel = Neo4j::Rails::Relationship.new(:type, n1, n2, :since => 1942)
# add another property on the relationship
rel[:foo] = "bar"
# Don't forget to save it
rel.save
# or create and save it in one go
rel = Neo4j::Rails::Relationship.create(:type, n1, n2, :since => 1942, :foo => 'bar')

TIP: You can of course also subclass the Neo4j::Rails::Relationship class just like for Neo4j::Rails::Model class to specify domain specific behavour (e.g. validations, callbacks, business logic etc.)

Create with a has_n/has_one accessor method

The has_n and has_one class methods can generate some convenience method for creating and traversing relationships and nodes.
Validation will only be performed on each nested node if the validates_associated is specified, see below.

class Person << Neo4j::Rails::Model
  has_n(:friends)
end

p1 = Person.new  # or Person.create
p2 = Person.new
p1.friends << p2
p1.save # => true

Relationship of same class is assumed by default. For relationships between different classes, see Mapping nodes/relationships to Ruby classes

Notice that you can combine using friends and the outgoing / incoming methods.
These methods may all return both saved and unsaved nodes.

Example:

a.friends << b
a.outgoing(:friends).first # => nil
a.save
a.friends << c
d.incoming(:friends) << a
a.outgoing(:friends).to_a # =>[b,c,d]

TIP: Use ModelClass.relationship when specifying relationship name. Instead of the example above write a.outgoing(Person.friends). The reason is that the relationship is prefixed if it is specified with a to node. Example Person.has_n(:friends).to(Person)

Create with friends.build and friends.create

Just like Active Record you can create relationship like this:

a.friends.build(property_hash)
a.friends.create(property_hash)

For all generated has_n and has_one methods see, Neo4j::Rails::HasN::ClassMethods

Create with Nested Attributes

Neo4j.rb supports accepts_nested_attributes_for which can be used to create relationship between nodes.

The following configuration option are available

  • :allow_destroy If true, destroys any members from the attributes hash with a _destroy key and a value that evaluates to true (eg. 1, ‘1’, true, or ‘true’). This option is off by default.
  • :reject_if Allows you to specify a Proc or a Symbol pointing to a method that checks whether a record should be built for a certain attribute hash. The hash is passed to the supplied Proc or the method and it should return either true or false. When no :reject_if is specified, a record will be built for all attribute hashes that do not have a destroy value that evaluates to true. Passing :allblank instead of a Proc will create a proc that will reject a record where all the attributes are blank.

When using the accepts_nested_attributes_for class method you must specify which class the relationship correspond to
by using the to method in has_one or has_n.

Example

class Member < Neo4j::Rails::Model
  has_n(:posts).to(Post)
  has_one(:avatar).to(Avator)

  accepts_nested_attributes_for :avatar, :allow_destroy => true
  accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes[:title].blank? }
  # when creating, pass in: {:avatar_attributes => {:key => 'value'} }
end

Traversing Nodes

You can access nodes with the same method that creates relationship: outgoing, incoming and the has_n and has_one accessor methods.
All these methods returns an object thats includes the Ruby Enumerable mixin (which means you can use each, collect, find etc).

Example

m = Member.find(42)
m.outgoing(:posts).find_all{|post| post.keyword == 'ruby'}
# or
m.posts.find_all{|post| post.keyword == 'ruby'}

For more advanced traversals see Neo4j::Core-Traverse

Accessing Relationships

The has_one and has_n method also generate accessors for getting the relationship objects.

m = Member.find(42)
m.posts_rels.to_a #=> an array of all type Neo4j::Rails::Relationship (or subclass of that).
m.avatar_rel #=> an object of  type Neo4j::Rails::Relationship (or subclass of that).

You can also access any relationship using the rels and rel method, see Neo4j::Core-Nodes-Properties-Relationships

Validation

Validation of relationships and nodes for the friends relationship above will always be performed.
If a related nodes or relationship is not valid then an exception will be raised.

Finding All Relationships Between Two Nodes

The following example returns all relationships between a and b.

a.rels.to_other(b)

The relationship accessor method also allows you to find the relationships between two nodes using the to_other method (just like the #rels method).
To only find the friends relationships between those nodes:

# find all relationships object between p1 and p3, using the has_n accessor
p1.friends_rels.to_other(p3)

# not using the has_n accessor
a.rels(Person.friends).to_other(b)

destroy and delete

The destroy and delete method works like the Active Record methods.

rel = p1.rels(:friends).find{|rel| rel.end_node == p3}
rel.destroy

p1.friends.find(p2).delete

You can also delete the relationship object like this.

p1.friends.delete(p2)

delete_all and destroy_all

You can destroy and delete all relationship in one go (just like Active Record).

p1.friends.destroy_all

To destroy all relationships:

p1.rels.destroy_all
p1.rels(:friends).delete_all # no callbacks, and only the friends relationships

Mapping Nodes and Relationships

You can subclass both the Neo4j::Rails::Model and Neo4j::Rails::Relationship classes and specify the relationship between those.
Remember that you can also do this without any declaration at any time (just like for declaring properties or just setting them
with the [] operator).

Let say you want to express the domain that an Actor has a Role to a Movie.
Example create three files and put them in the app/model folder.

app/models/role.rb

class Role < Neo4j::Rails::Relationship
  property :title, :character, :index => :exact # index both properties
end

app/models/actor.rb

class Actor < Neo4j::Rails::Model
  property :name, :index => :exact
  has_n(:acted_in).to(Movie).relationship(Role)
  index :name
end

app/models/movie.rb

class Movie < Neo4j::Rails::Model
  property :title, :year, :index => :exact

  # defines a method for traversing incoming acted_in relationships from Actor
  has_n(:actors).from(Actor, :acted_in)
end

Now, when you create a relationship between actor and role it will be of type Role.
Example:

an_actor = Actor.find(42)
an_actor.acted_in << a_film
an_actor.acted_in_rels.first #=> a Role object

# or
role = an_actor.acted_in.create(:title => 'The Matrix')
role.character => 'neo'
role.save

You can now also navigate the incoming direction since we declared has_n(:actors).from(Actor, :acted_in)

matrix.actors.to_a #=> all the actors in that film

Neo4j.rb will prefix relationships when they are declared with to (has_n(:friends).to).
Example:

actor = Actor.create
actor.acted_in << movie1
actor.outgoing(:acted_in) << movie2
actor.outgoing(:acted_in) # only include movie2
actor.outgoing(Actor.acted_in) # only include movie1

TIP: By not specifying which class the relationship is associated to (using the to method above) gives you more freedom. If you instead just declared Actor.has_n(:acted_in) then an actor can have an acted_in relationship to different classes. Example @an_actor.acted_in << Neo4j::Rails::Model.new() << Thingy.create.

Lucene Index & Querying

The find and all methods use the model’s Lucene index to search for either one or a set of nodes respectively. Make sure to index the properties that you’ll query on.

Example Setup

class IceCream < Neo4j::Model
  property :flavour, :index => :exact
  property :cone, :index => :exact
end
vanilla_sugar = IceCream.create(:flavour => 'vanilla', :cone => 'sugar')
vanilla_waffle = IceCream.create(:flavour => 'vanilla', :cone => 'waffle')

See Neo4j:Wrapper-Lucene and Neo4j::Core-lucene for more documentation how to declare and use a lucene index.

Find

The Neo4j::Rails::Model.find and Neo4j::Rails::Relationship.find method behaves differnt compared to Neo4j::Node.find and Neo4j::NodeMixin.find.
The find method will find the first node that matches its query or nil if one isn’t found.

Example:

IceCream.find('cone: sugar') #=> vanilla_sugar
IceCream.find('flavour: vanilla') #=> who knows?
IceCream.find(:flavour => 'vanilla') #=> same as above
IceCream.find('flavour: chocolate') #=> nil

The find method will also accept a Neo4j node id which is useful for finding a node in a rails controller.

class IceCreamController < ApplicationController
  
  def show
    @ice_cream = IceCream.find(params[:id])
    # . . .
  end

end

The Neo4j::Model also support Active Record like query syntax:

Model.find(:first, :conditions => { :name => "test" }) # works the same as find("name: \"test\"")
Model.find(:first, :conditions => { :id => 10 }) # works the same as find(10) or find("10")

It does also generate finder methods like IceCream.find_by_flavour or find_or_create_by_flavour, see below.

All

The all method will find a set of nodes or relationship. If none is find it will return an empty array.

IceCream.all #=> [vanilla_sugar, vanilla_waffle]

IceCream.all('flavour: vanilla') #=> [vanilla_sugar, vanilla_waffle]

IceCream.all('flavour: vanilla AND cone: sugar') #=> [vanilla_sugar]

IceCream.all('flavour: chocolate') #=> []
IceCream.all(:flavour => 'chocolate')
IceCream.find(:all, :condition => {:flavour => 'chocolate'}, :sort => {:name => :asc})

Tip: String parameter values with special characters, such as URI’s, should be escaped first before setting or querying to not conflict with the Lucene Query Parser Syntax: + – && || ! ( ) { } ^ " ~ * ? : \ . See Apache Lucene – Query Parser Syntax

escaped = URI.escape( "http://neo4j.rubyforge.org/guides", Regexp.new( "[^#{URI::PATTERN::UNRESERVED}]" ))
Website.find("uri: #{escaped}")

or using String#gsub

escaped = "http://neo4j.rubyforge.org/guides".gsub( /([\+\-\&\|\!\(\)\{\}\[\]\^\"\~\*\?\:\\])/ , "\\\1" )

TIP: The all method also work for subclasses for Neo4j::Rails::Model (but as expected for subclasses of Neo4j::Rails::Relationship).

Fulltext Search

An example of using the lucene full text search index:

class BlogEntry < Neo4j::Rails::Model
  property :text, :index => :fulltext
end

e = BlogEntry.create(:text => 'some long text string')

BlogEntry.find('text: long', :type => :fulltext) #=> e
BlogEntry.all('text: long', :type => :fulltext).size #=> 1

Notice that you need to specify the :fulltext configuration property for both declaring the index and searching.

Dynamic Finders

If you declare an index you neo4j.rb will generate finder methods for you.

Example:

class Person
  property :name
  index :name
end

Person.find_by_name 'andreas'  # with underscore of course, not visible

For a complete list of available methods see Neo4j::Rails::Finders

Timestamps

If a Neo4j::Rails::Model or a Neo4j::Rails::Relationship class (or subclass) has the property updated_at or created_at then
it will be timestamped. This is all that is required.

class Role < Neo4j::Rails::Relationship
  property :updated_at
  property :created_at
end

That’s all you need to do. If you want to disable this behaviour check the configuration below.

MultiTenancy

For multitenancy support check this

Neo4j::Rails::Transaction

All write operations requires a transaction. Read operations like find,load or read properties does not require transactions.
The Neo4j::Rails::Model and Neo4j::Rails::Relationship classes does automatically create transaction if needed. If you need to write several operations in one operation use Neo4j::Rails::Transaction. You will also get better performance using a single transaction instead of several small transactions.

Transaction and around_filter

This class can be used as a filter in order to wrap methods in one transactions.

Example:

class UsersController < ApplicationController
  around_filter Neo4j::Rails::Transaction, :only => [:create]

The Neo4j::Rails::Transaction class can also be used to access the current running transaction in order
to signal for a rollback.

Example:

class UsersController < ApplicationController
   around_filter Neo4j::Rails::Transaction, :only => [:create, :update]

   def update
     #.. create, update delete some nodes/relationships
     #.. something when wrong, rollback everyting
     Neo4j::Rails::Transaction.current.fail
   end

Model#transaction

Notice that you can also use the Model.transaction method to wrap a block of code in a transaction.

Example:

class UsersController < ApplicationController
  def update
    Users.transaction do |tx|
      #.. create, update delete some nodes/relationships
      #.. something when wrong, rollback everyting
      tx.fail
    end
  end

Rails Generators

You can use the standard Rails 3 generators to create your models, controllers, routes, tests and views, by passing ‘neo4j’ as the ORM for generators to use:

> rails generate scaffold User name:string born:date --orm=neo4j

You can also set up your application configuration to use neo4j as the default ORM for generating templates. Simply add the following to your application’s Rails configuration. This example also favours RSpec and specifies that fixtures are not to be created:

config.generators do |g|
  g.orm             :neo4j
  g.test_framework  :rspec, :fixture => false
end

Then you can simply call generators as normal without having to specify the ORM:

 rails generate model Admin --parent User

To undo what was just generated:

 rails destroy model Admin

To create a model with timestamps:

rails generate scaffold Post title:string --timestamps

Rails Neo4j Project Template

Example of creating an Neo4j Application from scratch, type

gem install rails
rails new myapp -m http://andreasronge.github.com/rails3.rb
cd myapp
bundle
rails generate scaffold User name:string email:string
rails s

open a webbrowser: http://localhost:3000/users

Installation and Configuration

If you don’t want to use the Neo4j Project template above you can do it yourself.

Replace ActiveRecord with Neo4j

Edit the config/application.rb
comment the line require ‘rails/all’ and add

require "action_controller/railtie"
require "action_mailer/railtie"
require "active_resource/railtie"
require "rails/test_unit/railtie"
require "sprockets/railtie" # Rails 3.1+ only
require 'neo4j'

Include Dependency to Neo4j

Add the following line to your Gemfile
gem 'neo4j', '1.0.0'
and remove all sql gems. Install the gems specified in your Gemfile bundle install

Configuration

You can set the location of the database in the config/application.rb file.
Example:

config.neo4j.storage_path = "#{config.root}/db/neo4j-#{Rails.env}"
config.neo4j.timestamps = false  # disable automatic timestamps on updated_at and created_at properties

Deployment

Trinidad needs the Java jar files in the lib folder.
There is a script for doing this:


neo4j-jars -local

Just type neo4j-jars for help

If you forget to add those jar files to your rails project you may get the following error message:


No index provider ‘lucene’ found

Backup

See Configuration & Backup

Add the following line in your config/application.rb file:

config.neo4j.online_backup_enabled = true

Then add the correct jar files to the lib folder:

neo4j-jars -backup

To perform the backup

require 'neo4j'
Neo4j.load_online_backup
Neo4j::OnlineBackup('localhost').incremental('/var/backup')

HA Cluster

Add the following line in your config/application.rb file:

config.neo4j['ha.db']=true

Then add the correct jar files to the lib folder:

cd YOUR_RAILS_ROOT
neo4j-jars -ha

Integration with other Rails Gems

devise

See the devise-neo4j gem and (old)
devise_example

carrierwave-neo4j

See http://github.com/reu/carrierwave-neo4j

paperclip

See http://github.com/l4u/neo4jrb-paperclip

will_paginate

See neo4j-will_paginate

CanCan alternative

see allowy

FAQ

I get “No index provider ‘lucene’ found”

See deployment above.

org.neo4j.kernel.impl.core.ReadOnlyDbException

You get this exception if there is already an neo4j db instance running.
Only one write instance of the database is possible. If there is already a write instance running then a read only db will be created.
Did you run the rails console before you did the first request to rails ?

Does neo4j have an Identity Map ?

Yes but it is disabled by default. Read more about it here: the identity map
and Neo4j::IdentityMap

Why can’t I update nested models: node.nested << x; node.nested.save ?

Because you have not enabled identity map, see above.
Let say you have the following class:

class Person < Neo4j::Rails::Model
  has_one(:nested)
end

node = Person.create
node.nested = Person.create
node.save

Then you can’t update the node.nested

node.nested[:some_property] = 'some value'
node.nested.nested << other_node
node.nested.save

Instead you must use a temporary variable, like this:

tmp = node.nested
tmp[:some_property] = 'some value'
tmp.nested << other_node
tmp.save

The reason is that the node.nested creates a new instance of a
wrapped node.

However, the following will work:

n = Person.create
n.nested = Person.create
n.nested[:name] = 'foo'
n.save

n.nested[:name] # => 'foo'

You do not need a temporary variable in this case since the relationship is not
persisted yet.

Clone this wiki locally