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](http://neo4j.org/learn/) 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 ------- ```ruby #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 ```ruby #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 ``` ```geoff (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 ```ruby Geoff(Company, Person) do company 'Amazon' do outgoing do person 'Tom', type: :customers person 'Tom', type: :supplier end end end ``` ```geoff (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 ```ruby 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 ``` ```geoff (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 ```ruby @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 ```ruby 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 ```ruby 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 ```