geoff 0.0.2.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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