-
Notifications
You must be signed in to change notification settings - Fork 276
Neo4j::Rails
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
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
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
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')
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.
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]
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)
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
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
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
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 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 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)
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)
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
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.
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.
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.
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).
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.
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
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
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
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
If you don’t want to use the Neo4j Project template above you can do it yourself.
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'
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
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
- Installing JRuby – see RVM
- Tomcat/Trinidad
- Glassfish App Server
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
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')
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
See the devise-neo4j gem and (old)
devise_example
See https://github.com/reu/carrierwave-neo4j
See https://github.com/l4u/neo4jrb-paperclip
see allowy
See deployment above.
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 ?
Yes but it is disabled by default. Read more about it here: the identity map
and Neo4j::IdentityMap
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.
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