geoff 0.0.2.beta

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in geoff.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,110 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ geoff (0.2.4.beta)
5
+ activesupport (~> 3.2.3)
6
+ json
7
+ neo4j
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ actionpack (3.2.6)
13
+ activemodel (= 3.2.6)
14
+ activesupport (= 3.2.6)
15
+ builder (~> 3.0.0)
16
+ erubis (~> 2.7.0)
17
+ journey (~> 1.0.1)
18
+ rack (~> 1.4.0)
19
+ rack-cache (~> 1.2)
20
+ rack-test (~> 0.6.1)
21
+ sprockets (~> 2.1.3)
22
+ activemodel (3.2.6)
23
+ activesupport (= 3.2.6)
24
+ builder (~> 3.0.0)
25
+ activesupport (3.2.6)
26
+ i18n (~> 0.6)
27
+ multi_json (~> 1.0)
28
+ builder (3.0.0)
29
+ coderay (1.0.7)
30
+ columnize (0.3.6)
31
+ diff-lcs (1.1.3)
32
+ erubis (2.7.0)
33
+ hike (1.2.1)
34
+ i18n (0.6.0)
35
+ journey (1.0.4)
36
+ json (1.7.4-java)
37
+ linecache (0.46)
38
+ rbx-require-relative (> 0.0.4)
39
+ method_source (0.8)
40
+ multi_json (1.3.6)
41
+ neo4j (2.0.1-java)
42
+ activemodel (>= 3.0.0, < 3.3)
43
+ neo4j-wrapper (= 2.0.1)
44
+ orm_adapter (>= 0.0.3)
45
+ railties (>= 3.0.0, < 3.3)
46
+ neo4j-community (1.7.1-java)
47
+ neo4j-core (2.0.1-java)
48
+ neo4j-community (>= 1.7.0)
49
+ neo4j-wrapper (2.0.1-java)
50
+ neo4j-core (= 2.0.1)
51
+ orm_adapter (0.3.0)
52
+ pry (0.9.10)
53
+ coderay (~> 1.0.5)
54
+ method_source (~> 0.8)
55
+ slop (~> 3.3.1)
56
+ pry (0.9.10-java)
57
+ coderay (~> 1.0.5)
58
+ method_source (~> 0.8)
59
+ slop (~> 3.3.1)
60
+ spoon (~> 0.0)
61
+ rack (1.4.1)
62
+ rack-cache (1.2)
63
+ rack (>= 0.4)
64
+ rack-ssl (1.3.2)
65
+ rack
66
+ rack-test (0.6.1)
67
+ rack (>= 1.0)
68
+ railties (3.2.6)
69
+ actionpack (= 3.2.6)
70
+ activesupport (= 3.2.6)
71
+ rack-ssl (~> 1.3.2)
72
+ rake (>= 0.8.7)
73
+ rdoc (~> 3.4)
74
+ thor (>= 0.14.6, < 2.0)
75
+ rake (0.9.2.2)
76
+ rbx-require-relative (0.0.9)
77
+ rdoc (3.12)
78
+ json (~> 1.4)
79
+ rspec (2.11.0)
80
+ rspec-core (~> 2.11.0)
81
+ rspec-expectations (~> 2.11.0)
82
+ rspec-mocks (~> 2.11.0)
83
+ rspec-core (2.11.1)
84
+ rspec-expectations (2.11.2)
85
+ diff-lcs (~> 1.1.3)
86
+ rspec-mocks (2.11.1)
87
+ ruby-debug (0.10.4)
88
+ columnize (>= 0.1)
89
+ ruby-debug-base (~> 0.10.4.0)
90
+ ruby-debug-base (0.10.4)
91
+ linecache (>= 0.3)
92
+ ruby-debug-base (0.10.4-java)
93
+ slop (3.3.2)
94
+ spoon (0.0.1)
95
+ sprockets (2.1.3)
96
+ hike (~> 1.2)
97
+ rack (~> 1.0)
98
+ tilt (~> 1.1, != 1.3.0)
99
+ thor (0.15.4)
100
+ tilt (1.3.3)
101
+
102
+ PLATFORMS
103
+ java
104
+ ruby
105
+
106
+ DEPENDENCIES
107
+ geoff!
108
+ pry
109
+ rspec (~> 2.11.0)
110
+ ruby-debug
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 David Rouchy
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,141 @@
1
+ geoff
2
+ =====
3
+
4
+ Ruby Geoff DSL
5
+
6
+
7
+ Preview
8
+ =======
9
+
10
+
11
+ ```ruby
12
+
13
+ #Basic tree like structure for DSL
14
+ #the first line generates the class nodes used by Neo4jWrapper
15
+ Geoff(Company, Person) do
16
+ company 'Acme' do
17
+ address "13 Something Road"
18
+
19
+ children :employees do
20
+ person 'Geoff' do
21
+ name 'Geoff'
22
+ end
23
+
24
+ person 'Nigel' do
25
+ name 'Nigel Small'
26
+ end
27
+ end
28
+ end
29
+
30
+ company 'Github' do
31
+ children :customers do
32
+ person 'Tom'
33
+ person 'Dick'
34
+ person 'Harry'
35
+ end
36
+ end
37
+ end
38
+
39
+ ```
40
+
41
+ ```
42
+ (ROOT)-[:Company]->(Company)
43
+ (ROOT)-[:Person]->(Person)
44
+ (Acme) {"_classname":"Company","address":"13 Something Road"}
45
+ (Company)-[:all]->(Acme)
46
+ (Geoff) {"_classname":"Person","name":"Geoff"}
47
+ (Person)-[:all]->(Geoff)
48
+ (Acme)-[:employees]->(Geoff)
49
+ (Nigel) {"_classname":"Person","name":"Nigel Small"}
50
+ (Person)-[:all]->(Nigel)
51
+ (Acme)-[:employees]->(Nigel)
52
+ (Github) {"_classname":"Company"}
53
+ (Company)-[:all]->(Github)
54
+ (Tom) {"_classname":"Person"}
55
+ (Person)-[:all]->(Tom)
56
+ (Github)-[:customers]->(Tom)
57
+ (Dick) {"_classname":"Person"}
58
+ (Person)-[:all]->(Dick)
59
+ (Github)-[:customers]->(Dick)
60
+ (Harry) {"_classname":"Person"}
61
+ (Person)-[:all]->(Harry)
62
+ (Github)-[:customers]->(Harry)
63
+ ```
64
+
65
+ #Individual relationship overrides
66
+
67
+ ```ruby
68
+ Geoff(Company, Person) do
69
+ company 'Amazon' do
70
+ children do
71
+ person 'Tom', type: :customers
72
+ person 'Tom', type: :supplier
73
+ end
74
+ end
75
+ end
76
+ ```
77
+
78
+ ```
79
+ (ROOT)-[:Company]->(Company)
80
+ (ROOT)-[:Person]->(Person)
81
+ (Amazon) {"_classname":"Company"}
82
+ (Company)-[:all]->(Amazon)
83
+ (Tom) {"_classname":"Person"}
84
+ (Person)-[:all]->(Tom)
85
+ (Amazon)-[:customers]->(Tom)
86
+ (Tom) {"_classname":"Person"}
87
+ (Person)-[:all]->(Tom)
88
+ (Amazon)-[:supplier]->(Tom)
89
+ ```
90
+
91
+
92
+ #Link arbitrary nodes in different branches of the tree
93
+ ##Uses the magic 'b' method
94
+
95
+ ```ruby
96
+ Geoff(Company, Person) do
97
+ company 'Amazon' do
98
+ children 'employees' do
99
+ b.judas = person 'Judas'
100
+ end
101
+ end
102
+
103
+ company 'Moonlighters' do
104
+ children do
105
+ b.judas type: 'employees'
106
+ end
107
+ end
108
+ end
109
+ ```
110
+
111
+ ```
112
+ (ROOT)-[:Company]->(Company)
113
+ (ROOT)-[:Person]->(Person)
114
+ (Amazon) {"_classname":"Company"}
115
+ (Company)-[:all]->(Amazon)
116
+ (Tom) {"_classname":"Person"}
117
+ (Person)-[:all]->(Tom)
118
+ (Amazon)-[:employees]->(Tom)
119
+ (Moonlighters) {"_classname":"Company"}
120
+ (Company)-[:all]->(Moonlighters)
121
+ (Moonlighters)-[:employees]->(Tom)
122
+ ```
123
+
124
+
125
+ #Using the outer bindings scope
126
+ ```ruby
127
+ @hours = SomeFancyAttributeParser.parse <<-EOF
128
+ Monday 09:00-15:00
129
+ Tuesday 13:00-19:00
130
+ Saturday 09:00-16:00
131
+ Sunday closed
132
+ EOF
133
+
134
+ Geoff(Company, binding: binding) do
135
+ company 'Amazon' do
136
+ opening_hours ->{ @hours }
137
+ end
138
+ end
139
+ ```
140
+
141
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/geoff.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/geoff/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["David Rouchy", "Mark Burns", "Phyo Wai Win",
6
+ "Kajetan Bojko", "Volker Pacher"]
7
+
8
+ gem.email = ["davidr@shutl.co.uk", "phyo@shutl.co.uk",
9
+ "volkerpacher@gmail.com", "kai@shutl.co.uk",
10
+ "markthedeveloper@gmail.com"]
11
+
12
+ gem.description = %q{Geoff syntax builder and Neo4j batch importer}
13
+ gem.summary = %q{DSL to allow easy generating of data for Neo4j}
14
+ gem.homepage = ""
15
+
16
+ gem.files = `git ls-files`.split($\)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.name = "geoff"
20
+ gem.require_paths = ["lib"]
21
+ gem.version = Geoff::VERSION
22
+
23
+ gem.add_dependency 'neo4j'
24
+ gem.add_dependency 'activesupport', '~> 3.2.3'
25
+ gem.add_dependency 'json'
26
+
27
+ gem.add_development_dependency 'rspec', '~>2.11.0'
28
+ gem.add_development_dependency 'ruby-debug'
29
+ gem.add_development_dependency 'pry'
30
+ end
@@ -0,0 +1,110 @@
1
+ require 'geoff/node_dsl'
2
+ require 'geoff/container'
3
+ require 'active_support/core_ext/string'
4
+
5
+ class ChildrenDsl
6
+ def initialize options, &block
7
+ @parent_node_name = options[:parent_node_name]
8
+ @parent_rel_type = options[:type]
9
+ @outer_binding = options[:binding]
10
+ @outer_geoff = options[:geoff]
11
+
12
+ @write_mode = false
13
+
14
+ @node_dsls = {}
15
+ @container = options[:container] || Container.new
16
+ write_mode { instance_eval(&block) }
17
+ end
18
+
19
+ def to_geoff
20
+ "#{@outer_geoff}\n#{geoff_lines.join("\n")}".strip
21
+ end
22
+
23
+ def geoff_lines
24
+ lines = []
25
+
26
+ @node_dsls.each do |node_dsl, properties|
27
+ lines += node_dsl.geoff_lines
28
+ lines << rel_geoff(node_dsl, properties) unless top_level_node?
29
+ end
30
+
31
+ lines
32
+ end
33
+
34
+ def rel_geoff node_dsl, properties
35
+ properties = properties.dup
36
+ type = properties.delete :type
37
+
38
+ "(#{@parent_node_name})-[:#{type}]->(#{node_dsl.node_name})".tap do |r|
39
+ r << " #{properties.to_json}" if properties.any?
40
+ end
41
+ end
42
+
43
+ def b
44
+ @container.set_recipient_of_node_dsl self
45
+ @container
46
+ end
47
+
48
+ def add_node_dsl node_dsl, rel_properties
49
+ @node_dsls[node_dsl] = rel_properties
50
+ end
51
+
52
+ private
53
+
54
+ #prevent very confusing method_missing calls during debugging by
55
+ #only writing to the object when expected
56
+ def write_mode
57
+ @write_mode = true
58
+ yield
59
+ @write_mode = false
60
+ end
61
+
62
+ # e.g.
63
+ #company "ACME" do
64
+ # children "works_at" do
65
+ # employee 'Bob'
66
+ # employee 'Lenny'
67
+ # employee 'Tom'
68
+ # end
69
+ #end
70
+ #
71
+ def method_missing m, *args, &blk
72
+ return super unless @write_mode
73
+ relationship, options = parse_method_missing m, *args
74
+
75
+ options.merge!(binding: @outer_binding)
76
+ NodeDsl.new(options, &blk).tap do |n|
77
+ @node_dsls[n] = relationship
78
+ end
79
+ end
80
+
81
+ def parse_method_missing m, *args
82
+ node_name = args.first
83
+ relationship = args[1] || {}
84
+ relationship[:type] ||= @parent_rel_type
85
+
86
+ if type_missing? relationship
87
+ raise ArgumentError, "Missing relationship type for node #{node_name}"
88
+ end
89
+
90
+ return [
91
+ relationship,
92
+
93
+ {
94
+ node_name: node_name,
95
+ klass_name: m.to_s.camelize,
96
+ rel_type: relationship[:type],
97
+ container: @container
98
+ }
99
+ ]
100
+
101
+ end
102
+
103
+ def type_missing? r
104
+ !top_level_node? and !r[:type]
105
+ end
106
+
107
+ def top_level_node?
108
+ @parent_node_name == "ROOT"
109
+ end
110
+ end
@@ -0,0 +1,21 @@
1
+ class Container
2
+ def initialize validator=nil
3
+ @node_dsls = {}
4
+ @validator = validator || ->(_, _){}
5
+ end
6
+
7
+ def method_missing m, *args, &blk
8
+ if m.to_s.last == "=" # assignment
9
+ @node_dsls[m.to_s[0..-2]] = args.first
10
+ else
11
+ node_dsl = @node_dsls[m.to_s]
12
+ rel_properties = args.first
13
+ @recipient.add_node_dsl node_dsl, rel_properties
14
+ node_dsl
15
+ end
16
+ end
17
+
18
+ def set_recipient_of_node_dsl children_dsl
19
+ @recipient = children_dsl
20
+ end
21
+ end
@@ -0,0 +1,155 @@
1
+ require 'java'
2
+ require 'fileutils'
3
+ Dir["lib/jars/*"].each {|file| require file }
4
+
5
+ module Geoff
6
+ class MissingIndexedProperty < StandardError; end
7
+
8
+ class Importer
9
+ class << self
10
+
11
+ def import_files *files, options
12
+ concatenated = files.map{|f|File.read f}.join "\n"
13
+
14
+ import concatenated, options
15
+ end
16
+
17
+ def import_file(file, options = {})
18
+ rules = File.read(file)
19
+ import(rules, options)
20
+ end
21
+
22
+ def import(geoff, options = {})
23
+ db_path = options[:test] ? 'db/test' : 'db/development'
24
+
25
+ Neo4j::Config[:storage_path] = db_path
26
+
27
+ geoff = geoff.to_geoff unless geoff.is_a? String
28
+
29
+ geoff = geoff.split "\n" if geoff.is_a? String
30
+
31
+ new(geoff, options).go
32
+ end
33
+ end
34
+
35
+ def initialize(rules, options)
36
+ @rules = rules
37
+ @options = options
38
+ log @options.inspect
39
+ end
40
+
41
+ def go
42
+ raise 'Invalid rules' unless validate_rules(@rules)
43
+ delete_database if drop?
44
+
45
+ log 'importing the database'
46
+
47
+ import_geoff_rules @rules
48
+ rebuild_indexes
49
+
50
+ true
51
+ end
52
+
53
+ private
54
+
55
+ def delete_database
56
+ if testmode?
57
+ Neo4j::Transaction.run do
58
+ Neo4j.db.each_node do |n|
59
+ n.del unless n.neo_id == 0
60
+ end
61
+ end
62
+ else
63
+ Neo4j.db.shutdown
64
+ FileUtils.rm_rf Neo4j::Config[:storage_path]
65
+ log "restarting the database #{Neo4j::Config[:storage_path]}"
66
+ Neo4j.db.start
67
+ end
68
+ end
69
+
70
+ def import_geoff_rules(rules)
71
+ sub_graph = org.neo4j.geoff.Subgraph.new(rules)
72
+ node_map = { '0' => root_node }
73
+ org.neo4j.geoff.Geoff.insertIntoNeo4j(sub_graph, Neo4j.db.graph, node_map );
74
+ end
75
+
76
+ def rebuild_indexes
77
+ log 're-building indexes'
78
+ types = root_node.relationships.map { |r| Kernel.const_get(r.get_type.to_s) }
79
+
80
+ Neo4j::Transaction.run do
81
+ types.each do |type|
82
+ re_index(type)
83
+ end
84
+ end
85
+ end
86
+
87
+ def re_index(type)
88
+ log "-> re-indexing #{type}"
89
+ props = type.instance_variable_get(:@_decl_props)
90
+ indexed_props = props.find_all { |_, p| p.has_key?(:index) }
91
+
92
+ indexed_props.each do |index|
93
+ all = type.all
94
+
95
+ all.each do |node|
96
+ @current_node = node
97
+ @current_index = index.first
98
+ node.add_index index.first
99
+ end
100
+ end
101
+
102
+ rescue NativeException => e
103
+ log "-" * 80
104
+ message = "Exception during re-indexing #{type} with #{@current_node} and index #{@current_index}"
105
+ log message
106
+ log "-" * 80
107
+ log e.message
108
+ log "-" * 80
109
+ #raise Geoff::MissingIndexedProperty, message
110
+ end
111
+
112
+ def tx
113
+ Neo4j::Transaction.run { yield if block_given? }
114
+ end
115
+
116
+ def root_node
117
+ Neo4j.ref_node
118
+ end
119
+
120
+ def log(message)
121
+ $stdout.puts message unless silent?
122
+ end
123
+
124
+ def drop?
125
+ option_value?(:drop, false)
126
+ end
127
+
128
+ def silent?
129
+ option_value? :silent
130
+ end
131
+
132
+ def testmode?
133
+ option_value? :test
134
+ end
135
+
136
+ def option_value?(key, default = false)
137
+ @options[key].nil? ? default : @options[key]
138
+ end
139
+
140
+ def validate_rules(rules)
141
+ invalid = Array(rules).reject{ |r| validate_rule(r) }
142
+ invalid.each { |r| log "Rule '#{r}' is invalid" }
143
+ invalid.empty?
144
+ end
145
+
146
+ def validate_rule(rule)
147
+ org.neo4j.geoff.Subgraph.new(Array(rule))
148
+ true
149
+ rescue
150
+ false
151
+ end
152
+
153
+ end
154
+
155
+ end
@@ -0,0 +1,46 @@
1
+ require 'geoff/children_dsl'
2
+
3
+ class Neo4jWrapperDsl
4
+ def initialize *classes, &block
5
+ options = classes.last.is_a?(Hash) ? classes.pop : {}
6
+
7
+ validate classes
8
+
9
+ options.merge!({
10
+ parent_node_name: 'ROOT',
11
+ type: nil,
12
+ container: Container.new
13
+ })
14
+
15
+ @children_dsl = ChildrenDsl.new(options, &block) if block_given?
16
+ end
17
+
18
+ def to_geoff
19
+ geoff = "#{add_classes}\n"
20
+ geoff += @children_dsl.to_geoff if @children_dsl
21
+ geoff.chomp
22
+ end
23
+
24
+ def to_s
25
+ to_geoff
26
+ end
27
+
28
+ private
29
+
30
+ def add_classes
31
+ @classes.map{|c| root_to_class_as_geoff c }.join("\n")
32
+ end
33
+
34
+ def root_to_class_as_geoff c
35
+ "(ROOT)-[:#{c}]->(#{c})"
36
+ end
37
+
38
+ def validate classes
39
+ @classes = classes || []
40
+
41
+ @classes.each do |c|
42
+ m = "Class #{c} should include Neo4j::NodeMixin"
43
+ raise ArgumentError, m unless c.is_a?(Class) and c.included_modules.include? Neo4j::NodeMixin
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,17 @@
1
+ class Neo4jWrapperValidator
2
+ NODE_NAME_REGEX = /^\(([^\)]+)\)/
3
+
4
+ def call container, node
5
+ node_name = node.match(NODE_NAME_REGEX)[1]
6
+
7
+ unless class_present? container, node_name
8
+ raise Geoff::MissingNodeDefinition, node_name
9
+ end
10
+
11
+ true
12
+ end
13
+
14
+ def class_present? container, node_name
15
+ container.node_list.include? node_name
16
+ end
17
+ end