-
Notifications
You must be signed in to change notification settings - Fork 276
Neo4j::Rails Persistence
The Neo4j::Rails::Model
and Neo4j::Rails::Relationship
implements the Rails ActiveModel interface and a subset of the ActiveRecord API.
Example:
class IceCream < Neo4j::Rails::Model
property :flavour
validates_presence_of :flavour
end
IceCream.new.valid? # => false
IceCream.new(:flavour => "vanilla").valid? # => true
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.
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
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.
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 )
Updates the model with the given attributes and saves the model if the validation is successful. Will create a new transaction if neccessarly.
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
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.
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')
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.)
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]
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)
class Person << Neo4j::Rails::Model
property :name, type: String
has_n(:friends) # relationship_type = "friends" in Neo4j
has_n(:enemies).to(Person) # relationship_type = "Person.enemies" in Neo4j
end
Not accounting for this difference in the generated relationship types can cause you problems later when writing your own Cypher queries. You can check yourself by calling the .query
method from the relationship accessor generated by Neo4j::Rails::Model.
# p,a,b,c,d,e,f are Person models with name=`letter`
p.friends
# => []
p.enemies
# => []
p.friends.query
# => "START v1=node(12610) MATCH (v1)<-[:`friends`]-(v2) RETURN v2"
p.enemies.query
# => "START v1=node(12610) MATCH (v1)<-[:`Person#enemies`]-(v2) RETURN v2"
p.friends << a
p.outgoing(:friends) << b
p.outgoing(Person.friends) << c
p.enemies << d
p.outgoing(:enemies) << e # <== HERE BE DRAGONS!
p.outgoing(Person.enemies) << f
p.save
p.friends.map(&:name)
# => ["a","b","c"]
p.enemies.map(&:name)
# => ["d","f"] # <== UH OH! `e` IS MISSING!
# we can find them with Cypher, but this only demonstrates how you can shoot yourself in the foot.
Neo4j.query(p){|person| person > Person.enemies > node(:enemy).ret }.to_a.map(&:name)
# => ["d","f"]
Neo4j.query(p){|person| person > :enemies > node(:enemy).ret }.to_a.map(&:name)
# => ["e"]
# This all works because:
Person.friends
# => :friends
Person.enemies
# => :"Person#enemies"
These problems are easy to avoid once you know about the relationship naming rules. Using Model.rel_name
will save you any trouble later if you have to go back and make your has_*
declarations have to
clauses. You can use Model.rel_name
directly in your CypherDSL queries and also be sure you never make any mistakes.
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.
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(Avatar)
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 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.
The destroy and delete method works like the Active Record methods.
rel = p1.rels(:outgoing, :friends).find{|rel| rel.end_node == p3}
rel.destroy # destroys the friend relationship manually
p1.friends.delete(p2) # destroys the friend relationship
p1.friends.find(p2).delete # deletes p2 and the friend relationship
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(:both, :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.
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.
For multitenancy support check this
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.
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
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
WARNING: Much of the information in this wiki is out of date. We are in the process of moving things to readthedocs
- Project Introduction
- Neo4j::ActiveNode
- Neo4j::ActiveRel
- Search and Scope
- Validation, Uniqueness, and Case Sensitivity
- Indexing VS Legacy Indexing
- Optimized Methods
- Inheritance
- Core: Nodes & Rels
- Introduction
- Persistence
- Find : Lucene
- Relationships
- Third Party Gems & extensions
- Scaffolding & Generators
- HA Cluster