Skip to content

Neo4j::Rails Persistence

Andreas Ronge edited this page Apr 26, 2012 · 6 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

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 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

If you declare which node class a has_n or has_one is then you can use the build and create method on the relationship accessor. Example

Just like Active Record you can create relationship like this:

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

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

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

Notice You must declare which node class the relationship points to with to, as shown above.

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

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.

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

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. 


h3. 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.

```ruby
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
Clone this wiki locally