Lightweight but flexible gem that allows reflect some of data from relational database into neo4j.
This allows to perform faster and easier search of your models ids
or it can be first step of migrating application data to neo4j.
Uses Neography (wrapper of Neo4j Rest API).
Gem was inspired by Neoid
Add this line to your application's Gemfile:
gem 'neomirror'
And then execute:
$ bundle
Or install it yourself as:
$ gem install neomirror
For more configuration options please read about Neography Configuration and initialization
Neography.configure do |config|
config.protocol = "http://"
config.server = "localhost"
config.port = 7474
config.username = nil
config.password = nil
end
Neomirror.connection = Neography::Rest.new
class User < ActiveRecord::Base
include Neomirror::Node
mirror_neo_node label: :User do # option :label is optional
property :name
property :name_length, ->(record) { record.name.length }
end
end
user = User.create(name: 'Dougal')
# Find or create neo node.
user.neo_node # => #<Neography::Node id=...>
user.node # Alias of #neo_node
user.find_neo_node # => #<Neography::Node id=...>
Primary key is saved automatically for nodes as id
attribute. Also unique constraint is created. Creating a unique constraint also creates a unique index (which is faster than a regular index).
For ActiveRecord
methods #create_neo_node
, #update_neo_node
, #destroy_neo_node
are called on corresponding callbacks (after_create
, after_update
, after_destroy
).
class Membership < ActiveRecord::Base
include Neomirror::Relationship
belongs_to :premises
belongs_to :group
mirror_neo_relationship start_node: :premises, end_node: :group, type: :MEMBER_OF
end
membership = Membership.first
# Find or create neo relationship.
membership.neo_relationship # => #<Neography::Relationship> or nil unless both nodes present
membership.neo_rel # Alias of #neo_relationship
For ActiveRecord
methods #create_neo_relationships
, #update_neo_relationships
, #destroy_neo_relationships
are called on corresponding callbacks (after_create
, after_update
, after_destroy
).
class Staff < ActiveRecord::Base
include Neomirror::Relationship
belongs_to :user
belongs_to :premises
belongs_to :group
mirror_neo_relationship start_node: :user, end_node: :premises, type: :STAFF_OF
mirror_neo_relationship start_node: :user, end_node: :group, type: :STAFF_OF
end
staff = Staff.first
staff.neo_relationship(end_node: :premises) # => #<Neography::Relationship> or nil
staff.neo_relationship(end_node: :group) # => #<Neography::Relationship> or nil
Premises.find_each(&:create_neo_node)
Group.find_each(&:create_neo_node)
Membership.preload(:premises, :group).find_each(&:create_neo_relationships)
User.find_each(&:create_neo_node)
Staff.preload(:premises, :group).find_each(&:create_neo_relationships)
Note that #create_neo_node
method will raise exception for already existed node and #create_neo_relationships
will create duplicated relationships for existed relationships.
Also can use #neo_node
and #neo_relationship
methods which find or create.
Premises.find_each(&:neo_node)
Group.find_each(&:neo_node)
Membership.preload(:premises, :group).find_each(&:neo_relationship)
User.find_each(&:neo_node)
Staff.preload(:premises, :group).find_each do |staff|
staff.neo_relationship(end_node: :premises)
staff.neo_relationship(end_node: :group)
end
Neomirror.neo.execute_query("START n=node(*) OPTIONAL MATCH n-[r]-() DELETE n,r")
Sometimes there is choise how to reflect relationship. Model which is representation of relationship can be mapped as edge with properties or as bunch of edges. Both design decisions are possible with neomirror.
Reflect model as relationship (edge) with properties.
class Staff < ActiveRecord::Base
include Neomirror::Relationship
belongs_to :user
belongs_to :premises
mirror_neo_relationship start_node: :user, end_node: :premises, type: :STAFF_OF do
property :roles
end
end
Reflect model as bunch of optional relationships (edges) existence of which depends on the respective condition. On model create edge created only if predicate evaluates as true. On model update edge will be deleted if predicate evaluates as false.
class Staff < ActiveRecord::Base
include Neomirror::Relationship
belongs_to :user
belongs_to :premises
mirror_neo_relationship start_node: :user, end_node: :premises, type: :MANAGER_OF,
if: ->(r) { r.roles.include?('manager') }
mirror_neo_relationship start_node: :user, end_node: :premises, type: :VISITOR_OF,
if: ->(r) { r.roles.include?('visitor') }
end
Even possible reflect model as node (vertex) and relationship(s) (edge).
class Group < ActiveRecord::Base
include Neomirror::Node
include Neomirror::Relationship
belongs_to :parent, class_name: 'Group', foreign_key: :parent_id
mirror_neo_node
mirror_neo_relationship start_node: :self, end_node: :parent, type: :CHILD_OF
end
It is possible to use it outside of ActiveRecord (there is no dependency). Just use methods create_neo_node
, update_neo_node
and destroy_neo_node
in your callbaks for nodes and create_neo_relationships
, update_neo_relationships
and destroy_neo_relationships
for relationships.
Also specify primary key attribute if it is differ from id
and class don't respond_to? :primary_key
method.
class Postcode
attr_accessor :code
include Neomirror::Node
self.neo_primary_key = :code
mirror_neo_node
end
p = Postcode.new
p.code = 'ABC'
p.create_neo_node # => #<Neography::Node id="ABC">
Any callback (for both neo node and neo relationship) can be skipped if #skip_neo_callbacks
is set to true.
# skip create_neo_node callback
user = User.new
user.skip_neo_callbacks = true
user.save
user.persisted? # => true
user.find_neo_node # => nil
# skip update_neo_node callback
user = User.last
user.skip_neo_callbacks = true
user.name # => Ted
user.neo_node.name # => Ted
user.update_attributes(name: 'Dougal')
user.name # => Dougal
user.neo_node.name # => Ted
- Fork it ( http://github.com/shhavel/neomirror/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request