geoff

Geoff is

a declarative notation for representing graph data within concise human-readable text, designed specifically with Neo4j in mind

http://geoff.nigelsmall.net/

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