geoff
Geoff is
a declarative notation for representing graph data within concise human-readable text, designed specifically with Neo4j in mind
This gem is a Ruby DSL for
- generating geoff syntax files
- batch inserting data into Neo4j
The reason for creating this gem is to:
- easily build data sets for tests
- that are readable and maintainable
- quickly batch insert the whole data set in one transaction
Prerequisites/ Caveats
- A ruby project
- Gemfile with
gem 'geoff', '0.0.3.beta'
- neo4j jar and geoff jar files in lib/jars (not included, but required!)
- Current implementation assumes usage of the neo4j wrapper gem
- Neo4j::Rails::Model classes or a class that includes the Neo4j::NodeMixin
Usage
#Gemfile
gem 'geoff'
# Basic tree like structure for DSL
# the first line generates the class nodes used by Neo4jWrapper
# NB 'Company' and 'Person' are classes with the Neo4j::NodeMixin
Geoff(Company, Person) do
company 'Acme' do
address "13 Something Road"
outgoing :employees do
person 'Geoff'
person 'Nigel' do
name 'Nigel Small'
end
end
end
company 'Github' do
outgoing :customers do
person 'Tom'
person 'Dick'
person 'Harry'
end
end
person 'Harry' do
incoming :customers do
company 'NeoTech'
end
end
end
Configuration
#in spec helper
Neo4j::Config[:storage_path] = '/path/to/db'
#in rspec
before do
geoff = Geoff(Company) do
company 'Acme' do
address "13 Something Road"
end
end
#Silence output, and delete existing neo4j db
Geoff.import geoff, silent: false, delete: true
end
(ROOT)-[:Company]->(Company)
(ROOT)-[:Person]->(Person)
(Acme) {"_classname":"Company","address":"13 Something Road"}
(Company)-[:all]->(Acme)
(Geoff) {"_classname":"Person","name":"Geoff"}
(Person)-[:all]->(Geoff)
(Acme)-[:employees]->(Geoff)
(Nigel) {"_classname":"Person","name":"Nigel Small"}
(Person)-[:all]->(Nigel)
(Acme)-[:employees]->(Nigel)
(Github) {"_classname":"Company"}
(Company)-[:all]->(Github)
(Tom) {"_classname":"Person"}
(Person)-[:all]->(Tom)
(Github)-[:customers]->(Tom)
(Dick) {"_classname":"Person"}
(Person)-[:all]->(Dick)
(Github)-[:customers]->(Dick)
(Harry) {"_classname":"Person"}
(Person)-[:all]->(Harry)
(Github)-[:customers]->(Harry)
Individual relationship overrides
Geoff(Company, Person) do
company 'Amazon' do
outgoing do
person 'Tom', type: :customers
person 'Tom', type: :supplier
end
end
end
(ROOT)-[:Company]->(Company)
(ROOT)-[:Person]->(Person)
(Amazon) {"_classname":"Company"}
(Company)-[:all]->(Amazon)
(Tom) {"_classname":"Person"}
(Person)-[:all]->(Tom)
(Amazon)-[:customers]->(Tom)
(Tom) {"_classname":"Person"}
(Person)-[:all]->(Tom)
(Amazon)-[:supplier]->(Tom)
Link arbitrary nodes in different branches of the tree
Uses the magic 'b' method
Geoff(Company, Person) do
company 'Amazon' do
outgoing 'employees' do
b.judas = person 'Judas'
end
end
company 'Moonlighters' do
outgoing do
b.judas type: 'employees'
end
end
end
(ROOT)-[:Company]->(Company)
(ROOT)-[:Person]->(Person)
(Amazon) {"_classname":"Company"}
(Company)-[:all]->(Amazon)
(Tom) {"_classname":"Person"}
(Person)-[:all]->(Tom)
(Amazon)-[:employees]->(Tom)
(Moonlighters) {"_classname":"Company"}
(Company)-[:all]->(Moonlighters)
(Moonlighters)-[:employees]->(Tom)
Using the outer scope
@hours = SomeFancyAttributeParser.parse <<-EOF
Monday 09:00-15:00
Tuesday 13:00-19:00
Saturday 09:00-16:00
Sunday closed
EOF
Geoff(Company, target: self) do
company 'Amazon' do
opening_hours ->{ @hours }
end
end
Injecting builders
coffee_machines_builder = Geoff do
b.large_coffee_machines = outgoing do
coffee_machine('large_machine') { power 2300 }
coffee_machine('xxl_machine' ) { power 2600 }
end
end
tables_builder = Geoff do
b.tables = outgoing do
table('round_table' ) { capacity 3 }
table('square_table') { capacity 4 }
end
end
Geoff(coffee_machines_builder, tables_builder) do
branch 'acme_coffee_luton_branch' do
number_of_employees 15
outgoing do
b.small_coffee_machines type: 'uses', lease: '3 years'
b.tables type: 'has'
end
end
end
Resulting graph
(Branch)
acme_coffee_luton_branch---------------------(Table)
/ | \ has square_table
/ | \ {capacity: 4}
/ | \
/uses |uses \has
/ | \
/ | \
/ | \
(CoffeeMachine) (CoffeeMachine) (Table)
large_machine xxl_machine round_table
{power: 2300} {power: 2600} {capacity: 3}
Cloning subtrees
Geoff do
b.grinder = grinder 'fine_grinder'
branch 'acme_coffee_luton_branch' do
outgoing 'uses' do
b.machine = coffee_machine('large_machine') do
power 2300
outgoing 'connected_to' do
b.grinder clone: false
end
end
# this branch uses two identical large coffee machines
# but they share same grinder
b.machine type: 'uses', clone: true
end
end
end
Resulting graph
(Branch)
acme_coffee_luton_branch
/ \
/ \
/ \
/uses \uses
/ \
/ \
/ \
(CoffeeMachine) (CoffeeMachine)
large_machine large_machine
{power: 2300} {power: 2300}
\ /
\ /
\ /
\connected_to /connected_to
\ /
\ /
\ /
(Grinder)
fine_grinder