geoff 0.0.2.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,107 @@
1
+ require 'geoff/children_dsl'
2
+
3
+ class NodeDsl
4
+ attr_reader :node_name
5
+
6
+ def initialize(options, &block)
7
+ @node_name = options[:node_name] || 'root'
8
+ @klass_name = options[:klass_name] || 'ROOT'
9
+ @container = options[:container] || Container.new
10
+ @rel_type = options[:rel_type]
11
+ @outer_binding = options[:binding]
12
+ @properties = {}
13
+ @children_dsls = []
14
+
15
+ @rendered = false
16
+
17
+ write_mode { instance_eval(&block) } if block_given?
18
+ end
19
+
20
+ def to_geoff
21
+ geoff_lines.join "\n"
22
+ end
23
+
24
+ def geoff_lines
25
+ #we need this to prevent rendering same node which is rendered already
26
+ return [] if @rendered
27
+ @rendered = true
28
+
29
+ lines = [self_to_geoff]
30
+ lines << all_rule_to_geoff if use_neo4j_wrapper?
31
+
32
+ lines += @children_dsls.map(&:geoff_lines)
33
+
34
+ lines
35
+ end
36
+
37
+ def b
38
+ @container
39
+ end
40
+
41
+ def all_rule_to_geoff
42
+ "(#{@klass_name})-[:all]->(#{@node_name})"
43
+ end
44
+
45
+ def self_to_geoff
46
+ "(#{@node_name}) #{properties.to_json}"
47
+ end
48
+
49
+ #refactor out wrapper specific stuff later
50
+ def use_neo4j_wrapper?
51
+ true
52
+ end
53
+
54
+ def properties
55
+ if use_neo4j_wrapper?
56
+ { '_classname' => @klass_name }.merge @properties
57
+ else
58
+ @properties
59
+ end
60
+ end
61
+
62
+ #prevent very confusing method_missing calls during debugging by
63
+ #only writing to the object when expected
64
+ def write_mode
65
+ @write_mode = true
66
+ yield
67
+ @write_mode = false
68
+ end
69
+
70
+ # e.g.
71
+ #sandwich "BLT", type: :on_menu do
72
+ # filling 'bacon'
73
+ # filling 'lettuce'
74
+ # filling 'tomato'
75
+ #end
76
+ def method_missing m, *args, &blk
77
+
78
+ if args.empty?
79
+ @properties[m]
80
+ else
81
+ return super unless @write_mode
82
+ val = if args.first.is_a? Proc
83
+ eval("@#{m}", @outer_binding)
84
+ else
85
+ args.first
86
+ end
87
+ @properties[m] = val
88
+ end
89
+ end
90
+
91
+ def to_s
92
+ to_geoff
93
+ end
94
+
95
+ private
96
+
97
+ def children type=nil, &block
98
+ options = {
99
+ parent_node_name: @node_name,
100
+ type: type,
101
+ container: @container,
102
+ binding: @outer_binding
103
+ }
104
+
105
+ @children_dsls << ChildrenDsl.new(options, &block)
106
+ end
107
+ end
@@ -0,0 +1,3 @@
1
+ module Geoff
2
+ VERSION = "0.0.2.beta"
3
+ end
data/lib/geoff.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "json"
2
+ require "geoff/version"
3
+ require 'geoff/node_dsl'
4
+ require 'geoff/neo4j_wrapper_dsl'
5
+ require 'geoff/importer'
6
+
7
+ def Geoff *class_list, options, &block
8
+ Neo4jWrapperDsl.new(*class_list, options, &block)
9
+ end
10
+
11
+ class Geoff::Error < StandardError; end
12
+ class Geoff::MissingNodeDefinition < Geoff::Error ; end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+ require 'geoff/children_dsl'
3
+
4
+ describe ChildrenDsl do
5
+ describe 'with no special nodes' do
6
+ let(:children) do
7
+ ChildrenDsl.new({parent_node_name: 'starbucks'}) do
8
+ branch 'starbucks_branch_1', type: 'owns' do
9
+ delay_time 15
10
+ end
11
+
12
+ branch 'starbucks_branch_2', type: 'owns' do
13
+ delay_time 30
14
+ end
15
+ end
16
+ end
17
+
18
+ let(:expected_geoff) do
19
+ strip_whitespace <<-EOS
20
+ (starbucks_branch_1) {"_classname":"Branch","delay_time":15}
21
+ (Branch)-[:all]->(starbucks_branch_1)
22
+ (starbucks)-[:owns]->(starbucks_branch_1)
23
+ (starbucks_branch_2) {"_classname":"Branch","delay_time":30}
24
+ (Branch)-[:all]->(starbucks_branch_2)
25
+ (starbucks)-[:owns]->(starbucks_branch_2)
26
+ EOS
27
+ end
28
+
29
+ specify do
30
+ geoff = children.to_geoff
31
+
32
+ geoff.should == expected_geoff
33
+ end
34
+ end
35
+
36
+ context "with missing rel type" do
37
+ let(:missing){ChildrenDsl.new(parent_node_name: 'starbucks') {area 'Luton' } }
38
+ let(:top) {ChildrenDsl.new(parent_node_name: 'starbucks', type: :x ){area 'Luton' } }
39
+ let(:on_node){ChildrenDsl.new(parent_node_name: 'starbucks') {area 'Luton', type: :x }}
40
+
41
+ specify { ->{missing }.should raise_error ArgumentError}
42
+ specify { ->{ top }.should_not raise_error ArgumentError }
43
+ specify { ->{ on_node}.should_not raise_error ArgumentError }
44
+ end
45
+
46
+ describe 'with special node' do
47
+ let(:children) do
48
+ ChildrenDsl.new({parent_node_name: 'starbucks'}) do
49
+ b.luton = area 'Luton', type: 'headquarters_location' do
50
+ name 'LU1'
51
+ end
52
+ end
53
+ end
54
+
55
+ specify do
56
+ luton = children.b.luton
57
+
58
+ luton.node_name.should == 'Luton'
59
+ luton.name.should == 'LU1'
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,147 @@
1
+ require 'spec_helper'
2
+ require 'geoff/node_dsl'
3
+ require 'geoff'
4
+
5
+ describe NodeDsl do
6
+ describe 'with properties and no children' do
7
+ let(:node) do
8
+ NodeDsl.new(node_name: 'starbucks_branch', klass_name: 'Branch') do
9
+ delay_time 15
10
+ end
11
+ end
12
+
13
+ let(:expected_geoff) do
14
+ strip_whitespace <<-EOS
15
+ (starbucks_branch) {"_classname":"Branch","delay_time":15}
16
+ (Branch)-[:all]->(starbucks_branch)
17
+ EOS
18
+ end
19
+
20
+ specify do
21
+ node.to_geoff.should == expected_geoff
22
+ end
23
+ end
24
+
25
+ describe 'with children' do
26
+ let(:node) do
27
+
28
+ NodeDsl.new(node_name: 'starbucks', klass_name: 'Cafe') do
29
+ children do
30
+ branch 'starbucks_branch_1', type: 'owns', some: 'property' do
31
+ closing_buffer 30
32
+ delay_time 15
33
+ postcode 'LU1 1CN'
34
+ schedule 'DEFAULT_SCHEDULE'
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ let(:expected_geoff) do
42
+ strip_whitespace <<-EOS
43
+ (starbucks) {"_classname":"Cafe"}
44
+ (Cafe)-[:all]->(starbucks)
45
+ (starbucks_branch_1) {"_classname":"Branch","closing_buffer":30,"delay_time":15,"postcode":"LU1 1CN","schedule":"DEFAULT_SCHEDULE"}
46
+ (Branch)-[:all]->(starbucks_branch_1)
47
+ (starbucks)-[:owns]->(starbucks_branch_1) {"some":"property"}
48
+ EOS
49
+ end
50
+
51
+ specify do
52
+ node.to_geoff.should == expected_geoff
53
+ end
54
+ end
55
+
56
+ describe "#with a lambda to access the outer scope" do
57
+ let(:expected_geoff) do
58
+ strip_whitespace <<-EOS
59
+ (starbucks) {"_classname":"Cafe"}
60
+ (Cafe)-[:all]->(starbucks)
61
+ (starbucks_branch_1) {"_classname":"Branch","delay_time":15,"hours":"3"}
62
+ (Branch)-[:all]->(starbucks_branch_1)
63
+ (starbucks)-[:owns]->(starbucks_branch_1) {"some":"property"}
64
+ EOS
65
+ end
66
+
67
+ specify do
68
+ @hours = "3"
69
+
70
+ node = NodeDsl.new(node_name: 'starbucks', klass_name: 'Cafe', binding: binding) do
71
+ children do
72
+ branch 'starbucks_branch_1', type: 'owns', some: 'property' do
73
+ delay_time 15
74
+ hours ->{@hours}
75
+ end
76
+ end
77
+ end
78
+
79
+ node.to_s.should == expected_geoff
80
+ end
81
+ end
82
+
83
+ describe 'with non-tree graphs' do
84
+ let(:node) do
85
+
86
+ NodeDsl.new(node_name: 'starbucks', klass_name: 'Cafe') do
87
+ children do
88
+ b.luton = area 'luton', type: 'headquarters_location' do
89
+ name 'LU1'
90
+ end
91
+
92
+ branch 'starbucks_branch_1', type: 'owns', some: 'property' do
93
+ delay_time 15
94
+
95
+ children do
96
+ b.luton type: 'location'
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ let(:expected_geoff) do
104
+ strip_whitespace <<-EOS
105
+ (starbucks) {"_classname":"Cafe"}
106
+ (Cafe)-[:all]->(starbucks)
107
+ (luton) {"_classname":"Area","name":"LU1"}
108
+ (Area)-[:all]->(luton)
109
+ (starbucks)-[:headquarters_location]->(luton)
110
+ (starbucks_branch_1) {"_classname":"Branch","delay_time":15}
111
+ (Branch)-[:all]->(starbucks_branch_1)
112
+ (starbucks_branch_1)-[:location]->(luton)
113
+ (starbucks)-[:owns]->(starbucks_branch_1) {"some":"property"}
114
+ EOS
115
+ end
116
+
117
+ specify do
118
+ geoff = node.to_geoff
119
+ geoff.should == expected_geoff
120
+ end
121
+
122
+ context "with relation type at the children level" do
123
+ let(:expected_geoff) do
124
+ strip_whitespace <<-EOS
125
+ (starbucks) {"_classname":"Cafe"}
126
+ (Cafe)-[:all]->(starbucks)
127
+ (luton) {"_classname":"Area","name":"LU1"}
128
+ (Area)-[:all]->(luton)
129
+ (starbucks)-[:location]->(luton)
130
+ EOS
131
+ end
132
+
133
+
134
+ specify do
135
+ node = NodeDsl.new(node_name: 'starbucks', klass_name: 'Cafe') do
136
+ children "location" do
137
+ area 'luton' do
138
+ name 'LU1'
139
+ end
140
+ end
141
+ end
142
+ node.to_geoff.should == expected_geoff
143
+ end
144
+ end
145
+
146
+ end
147
+ end
@@ -0,0 +1,72 @@
1
+ require File.expand_path('spec/spec_helper')
2
+ require 'neo4j'
3
+ require 'geoff'
4
+
5
+ describe Geoff do
6
+ class CoverageArea
7
+ include Neo4j::NodeMixin
8
+ end
9
+ class Cafe
10
+ include Neo4j::NodeMixin
11
+ end
12
+
13
+ class Carrier
14
+ include Neo4j::NodeMixin
15
+ end
16
+
17
+ class Branch
18
+ include Neo4j::NodeMixin
19
+ end
20
+ describe 'base nodes' do
21
+
22
+ let(:builder) do
23
+ Geoff(Cafe, Carrier, Branch, CoverageArea)
24
+ end
25
+
26
+ let(:expected_geoff) do
27
+ strip_whitespace <<-EOS
28
+ (ROOT)-[:Cafe]->(Cafe)
29
+ (ROOT)-[:Carrier]->(Carrier)
30
+ (ROOT)-[:Branch]->(Branch)
31
+ (ROOT)-[:CoverageArea]->(CoverageArea)
32
+ EOS
33
+ end
34
+
35
+ specify do
36
+ builder.to_s.should == expected_geoff
37
+ end
38
+ end
39
+
40
+ describe 'Geoff sugar' do
41
+ let(:node) do
42
+ Geoff(Branch) do
43
+ branch 'starbucks_branch' do
44
+ delay_time 15
45
+ end
46
+ end
47
+ end
48
+
49
+ let(:expected_geoff) do
50
+ strip_whitespace <<-EOS
51
+ (ROOT)-[:Branch]->(Branch)
52
+ (starbucks_branch) {"_classname":"Branch","delay_time":15}
53
+ (Branch)-[:all]->(starbucks_branch)
54
+ EOS
55
+ end
56
+
57
+ specify do
58
+ node.to_geoff.should == expected_geoff
59
+ end
60
+
61
+ specify do
62
+ pending 'not implemented'
63
+ ->{
64
+ Geoff do
65
+ branch 'starbucks_branch' do
66
+ delay_time 15
67
+ end
68
+ end
69
+ }.should raise_error Geoff::MissingClassDefinition, "branch"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,86 @@
1
+ require File.expand_path('spec/spec_helper')
2
+ require 'neo4j'
3
+ require './lib/geoff/neo4j_wrapper_dsl'
4
+
5
+ describe Neo4jWrapperDsl do
6
+ let(:expected_geoff) do
7
+ strip_whitespace <<-EOS
8
+ (ROOT)-[:Sandwich]->(Sandwich)
9
+ (ROOT)-[:Cheese]->(Cheese)
10
+ (ROOT)-[:Egg]->(Egg)
11
+ EOS
12
+ end
13
+
14
+ class Sandwich
15
+ include Neo4j::NodeMixin
16
+ end
17
+
18
+ class Cafe
19
+ include Neo4j::NodeMixin
20
+ end
21
+
22
+ class Branch
23
+ include Neo4j::NodeMixin
24
+ end
25
+
26
+
27
+ class Cheese
28
+ include Neo4j::NodeMixin
29
+ end
30
+
31
+ class Egg
32
+ include Neo4j::NodeMixin
33
+ end
34
+
35
+
36
+ describe "#to_s" do
37
+ specify do
38
+ dsl = Neo4jWrapperDsl.new Sandwich, Cheese, Egg
39
+ dsl.to_s.should == dsl.to_geoff
40
+ end
41
+ end
42
+
43
+ describe "#to_geoff" do
44
+ it "outputs root node geoff syntax" do
45
+ dsl = Neo4jWrapperDsl.new(Sandwich, Cheese, Egg)
46
+ dsl.to_geoff.should == expected_geoff
47
+ end
48
+
49
+ specify do
50
+ ->{Neo4jWrapperDsl.new 2 } .should raise_error ArgumentError
51
+ ->{Neo4jWrapperDsl.new '' } .should raise_error ArgumentError
52
+ ->{Neo4jWrapperDsl.new ['']}.should raise_error ArgumentError
53
+ ->{Neo4jWrapperDsl.new [1]} .should raise_error ArgumentError
54
+
55
+ ->{Neo4jWrapperDsl.new(Egg)}.should_not raise_error ArgumentError
56
+ end
57
+
58
+
59
+ context "with a block" do
60
+ let(:expected) {
61
+ strip_whitespace <<-EOS
62
+ (ROOT)-[:Cafe]->(Cafe)
63
+ (ROOT)-[:Branch]->(Branch)
64
+ (Starbucks) {"_classname":"Cafe"}
65
+ (Cafe)-[:all]->(Starbucks)
66
+ (starbucks_branch) {"_classname":"Branch"}
67
+ (Branch)-[:all]->(starbucks_branch)
68
+ (Starbucks)-[:owns]->(starbucks_branch)
69
+ EOS
70
+ }
71
+
72
+ specify "with parent node is root node, but no relationship,
73
+ don't create relationship to root node" do
74
+ Neo4jWrapperDsl.new(Cafe, Branch) do
75
+ cafe 'Starbucks' do
76
+ children 'owns' do
77
+ #create relationship to parent node of type owns (comes from children DSL)
78
+ branch 'starbucks_branch'
79
+ end
80
+ end
81
+
82
+ end.to_geoff.should == expected
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,28 @@
1
+ require './lib/geoff'
2
+ require './lib/geoff/neo4j_wrapper_validator'
3
+
4
+ describe Neo4jWrapperValidator do
5
+ let(:validator) { Neo4jWrapperValidator.new }
6
+
7
+ {
8
+ "(Egg)-[:relates]->(1)" => {existing_nodes: %w(Branch Starbucks Cafe), node_name: "Egg", valid: false},
9
+ "((Branch)-[:relates]->(1)" => {existing_nodes: %w(Branch Cafe Starbucks), node_name: "Branch", valid: false},
10
+ "(Starbucks)-[:relates]->(1)" => {existing_nodes: %w(Branch Cafe Starbucks), node_name: "Starbucks", valid: true},
11
+ "((_Branch)-[:relates]->(1)" => {existing_nodes: %w(Branch), node_name: "(_Branch", valid: false},
12
+ "((Bra nch)-[:relates]->(1)" => {existing_nodes: %w(Branch Cafe), node_name: "(Bra nch", valid: false},
13
+ "((Branch)-[:relates]->(1)" => {existing_nodes: %w(Branch Cafe), node_name: "(Branch", valid: false},
14
+ "(Branch)-[:relates]->(1)" => {existing_nodes: [], node_name: "Branch", valid: false}}.each do |input, details|
15
+ specify do
16
+ valid = details[:valid]
17
+ existing_nodes = details[:existing_nodes]
18
+ node_name = details[:node_name]
19
+ container = mock 'container', node_list: existing_nodes
20
+
21
+ if valid
22
+ validator.call(container, input).should == true
23
+ else
24
+ ->{validator.call(container, input)}.should raise_error Geoff::MissingNodeDefinition, node_name
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+
2
+ Dir["spec/support/**/*.rb"].each {|f| require_relative File.join('..', f)}
3
+
4
+ RSpec.configure do |config|
5
+ end
@@ -0,0 +1,51 @@
1
+ require 'json'
2
+
3
+ def parse_geoff_node(expr)
4
+ GeoffNodeMatcher.new(expr)
5
+ end
6
+
7
+ def parse_geoff_relation(expr)
8
+ GeoffRelationshipMatcher.new(expr)
9
+ end
10
+
11
+ def strip_whitespace s
12
+ s.gsub(/^\s*/, '').chomp
13
+ end
14
+
15
+ class GeoffNodeMatcher
16
+
17
+ @@regex = /\((\w+)\) (.*)/
18
+
19
+ def initialize(expression)
20
+ @@regex.match expression
21
+
22
+ @node_name = $1
23
+ @attributes = JSON.parse($2, symbolize_names: true)
24
+ rescue
25
+ @attributes = {}
26
+ end
27
+
28
+ def named?(name)
29
+ @node_name == name
30
+ end
31
+
32
+ def has_attribute?(name, value)
33
+ @attributes[name.to_sym] == value
34
+ end
35
+ end
36
+
37
+ class GeoffRelationshipMatcher
38
+ @@regex = /\((.*)\)-\[\:(.*)\]->\((.*)\)/
39
+
40
+ def initialize(expression)
41
+ @@regex.match expression
42
+
43
+ @source_name = $1
44
+ @relationship_name = $2
45
+ @dest_name = $3
46
+ end
47
+
48
+ def named?(name)
49
+ @relationship_name == name
50
+ end
51
+ end