n4j 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rvmrc +2 -0
- data/Gemfile +6 -0
- data/README.rdoc +5 -0
- data/Rakefile +137 -0
- data/config/n4j.yml +7 -0
- data/lib/n4j.rb +46 -0
- data/lib/n4j/attributes.rb +48 -0
- data/lib/n4j/auto_relate.rb +41 -0
- data/lib/n4j/cypher.rb +45 -0
- data/lib/n4j/entity.rb +117 -0
- data/lib/n4j/field.rb +26 -0
- data/lib/n4j/node.rb +91 -0
- data/lib/n4j/populate.rb +22 -0
- data/lib/n4j/relationship.rb +47 -0
- data/lib/n4j/representation.rb +63 -0
- data/lib/n4j/request.rb +39 -0
- data/lib/n4j/traversal.rb +62 -0
- data/lib/n4j/version.rb +3 -0
- data/n4j.gemspec +26 -0
- data/neo4j_plugins/test-delete-db-extension-1.5.jar +0 -0
- data/tasks/.gitkeep +0 -0
- data/tasks/n4j.rake +125 -0
- data/templates/README.txt +16 -0
- data/templates/logging.properties +73 -0
- data/templates/neo4j-server-1.5.properties +53 -0
- data/templates/neo4j-server-1.6.properties +78 -0
- data/templates/neo4j-wrapper.conf +37 -0
- data/templates/neo4j.properties +9 -0
- data/templates/windows-wrapper-logging.properties +73 -0
- data/test/basics_test.rb +46 -0
- data/test/field_test.rb +19 -0
- data/test/test_helper.rb +12 -0
- metadata +121 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require 'rake/testtask'
|
4
|
+
Rake::TestTask.new(:test) do |test|
|
5
|
+
test.libs << 'lib' << 'test'
|
6
|
+
test.test_files = FileList['test/*_test.rb']
|
7
|
+
test.verbose = true
|
8
|
+
# test.warning = true
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
namespace :n4j do
|
13
|
+
desc "Create n4j.yml"
|
14
|
+
task :create_n4j_config do
|
15
|
+
require 'psych'
|
16
|
+
conf_file = Psych.dump({'development' => {'port' => 7480, 'secure_port' => 7481},
|
17
|
+
'test' => {'port' => 7481, 'secure_port' => 7483}})
|
18
|
+
FileUtils.mkdir_p 'config'
|
19
|
+
File.open('config/n4j.yml', 'w') {|f| f.write(conf_file) }
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Clear development database."
|
23
|
+
task :clear_development do
|
24
|
+
port = YAML.parse_file('config/n4j.yml').to_ruby['development']['port']
|
25
|
+
result = `curl --request DELETE http://localhost:#{port}/cleandb/all-gone`
|
26
|
+
puts result
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "Status of Neo4j servers"
|
30
|
+
task :status do
|
31
|
+
number_of_neo4j_instances = `ps aux | grep ne[o]4j | wc -l`
|
32
|
+
number_of_neo4j_instances.strip!
|
33
|
+
puts "#{number_of_neo4j_instances} total instances of Neo4j running, system wide."
|
34
|
+
unless number_of_neo4j_instances.to_i < 1
|
35
|
+
YAML.parse_file("config/n4j.yml").to_ruby.each_pair do |environment, conf|
|
36
|
+
pid_filename = "tmp/pids/n4j_#{environment}_pid"
|
37
|
+
if File.exist?(pid_filename)
|
38
|
+
pid = IO.read "tmp/pids/n4j_#{environment}_pid"
|
39
|
+
pid.chomp!
|
40
|
+
result = `ps p#{pid} | grep #{pid} | wc -l`
|
41
|
+
result.strip!
|
42
|
+
puts "#{result} instances running for #{environment} (pid: #{pid})."
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
desc 'Start servers'
|
49
|
+
task :start do |task_name|
|
50
|
+
require 'erb'
|
51
|
+
|
52
|
+
YAML.parse_file('config/n4j.yml').to_ruby.each_pair do |environment, conf|
|
53
|
+
@environment = environment
|
54
|
+
@port = conf['port']
|
55
|
+
@secure_port = conf['secure_port']
|
56
|
+
@server_version = 1.6
|
57
|
+
|
58
|
+
neo4j_bin_link = `which neo4j`
|
59
|
+
neo4j_bin_link.chomp!
|
60
|
+
neo4j_bin = Pathname.new(neo4j_bin_link).realpath
|
61
|
+
|
62
|
+
java_command = `which java`
|
63
|
+
java_command.chomp!
|
64
|
+
|
65
|
+
libexec = Pathname.new(neo4j_bin_link).realpath.dirname.dirname
|
66
|
+
|
67
|
+
libdir = "#{libexec}/lib"
|
68
|
+
syslib = "#{libexec}/system/lib"
|
69
|
+
plugin_dir = "#{libexec}/plugins"
|
70
|
+
n4j_plugins = 'neo4j_plugins'
|
71
|
+
neo4j_home = "tmp/n4j/#{@environment}"
|
72
|
+
pid_file = "tmp/pids/n4j_#{@environment}_pid"
|
73
|
+
|
74
|
+
FileUtils.mkdir_p neo4j_home
|
75
|
+
FileUtils.mkdir_p "#{neo4j_home}/data/log"
|
76
|
+
FileUtils.mkdir_p 'tmp/pids'
|
77
|
+
|
78
|
+
class_path = [libdir, syslib, plugin_dir, n4j_plugins].collect {|dir| Dir.glob(dir + '/*.jar')}.flatten.join(':')
|
79
|
+
java_opts = "-server -XX:+DisableExplicitGC -Dorg.neo4j.server.properties=conf/neo4j-server.properties -Djava.util.logging.config.file=conf/logging.properties -Xms3m -Xmx64m"
|
80
|
+
|
81
|
+
# system "cp lib/n4j/templates/neo4j.properties tmp/n4j/#{@environment}/"
|
82
|
+
# system "cp lib/n4j/templates/logging.properties tmp/n4j/#{@environment}/"
|
83
|
+
FileUtils.cp 'templates/neo4j.properties', "tmp/n4j/#{@environment}/neo4j.properties"
|
84
|
+
FileUtils.cp 'templates/logging.properties', "tmp/n4j/#{@environment}/logging.properties"
|
85
|
+
|
86
|
+
FileUtils.touch "#{neo4j_home}/data/log/console.log"
|
87
|
+
|
88
|
+
neo4j_server_properties = ERB.new(IO.read("templates/neo4j-server-#{@server_version}.properties")).result
|
89
|
+
neo4j_server_properties_path = "#{neo4j_home}/neo4j-server.properties"
|
90
|
+
File.open(neo4j_server_properties_path, 'w') {|f| f.write(neo4j_server_properties) }
|
91
|
+
|
92
|
+
neo4j_wrapper_conf = ERB.new(IO.read('templates/neo4j-wrapper.conf')).result
|
93
|
+
neo4j_wrapper_conf_path = "#{neo4j_home}/neo4j-wrapper.conf"
|
94
|
+
File.open(neo4j_wrapper_conf_path, 'w') {|f| f.write(neo4j_wrapper_conf) }
|
95
|
+
|
96
|
+
launch_neo4j_command = "#{java_command}
|
97
|
+
-cp #{class_path}
|
98
|
+
-server
|
99
|
+
-XX:+DisableExplicitGC
|
100
|
+
-Dorg.neo4j.server.properties=#{neo4j_server_properties_path}
|
101
|
+
-Djava.util.logging.config.file=#{neo4j_home}/logging.properties
|
102
|
+
-Xms3m
|
103
|
+
-Xmx64m
|
104
|
+
-Dlog4j.configuration=file:#{neo4j_home}/log4j.properties
|
105
|
+
-Dorg.neo4j.server.properties=#{neo4j_server_properties_path}
|
106
|
+
-Djava.util.logging.config.file=#{neo4j_home}/logging.properties
|
107
|
+
-Dneo4j.home=#{neo4j_home}
|
108
|
+
-Dneo4j.instance=#{libexec} org.neo4j.server.Bootstrapper
|
109
|
+
>> #{neo4j_home}/data/log/console.log 2>&1 & echo $! > \"#{pid_file}\"
|
110
|
+
"
|
111
|
+
|
112
|
+
system launch_neo4j_command.gsub(/\n/,' ').gsub(/\s+/, ' ')
|
113
|
+
puts "#{environment.capitalize} Neo4j launched."
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
desc "Stop server"
|
118
|
+
task :stop do
|
119
|
+
YAML.parse_file('config/n4j.yml').to_ruby.each_pair do |environment, conf|
|
120
|
+
pid_filename = "tmp/pids/n4j_#{environment}_pid"
|
121
|
+
if File.exist?(pid_filename)
|
122
|
+
pid = IO.read pid_filename
|
123
|
+
pid.chomp!
|
124
|
+
result = `ps p#{pid} | grep #{pid} | wc -l`
|
125
|
+
if result.to_i > 0
|
126
|
+
Process.kill("HUP", pid.to_i)
|
127
|
+
puts "Killed #{environment} Neo4j."
|
128
|
+
else
|
129
|
+
puts "#{environment.capitalize} Neo4j wasn't running."
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
|
137
|
+
end
|
data/config/n4j.yml
ADDED
data/lib/n4j.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require "n4j/version"
|
2
|
+
require 'active_support/all'
|
3
|
+
require 'active_model'
|
4
|
+
|
5
|
+
module N4j
|
6
|
+
autoload :Attributes, 'n4j/attributes'
|
7
|
+
autoload :Request, 'n4j/request'
|
8
|
+
autoload :Entity, 'n4j/entity'
|
9
|
+
autoload :Node, 'n4j/node'
|
10
|
+
autoload :Relationship, 'n4j/relationship'
|
11
|
+
autoload :Field, 'n4j/field'
|
12
|
+
autoload :Traversal, 'n4j/traversal'
|
13
|
+
autoload :Cypher, 'n4j/cypher'
|
14
|
+
|
15
|
+
include Request
|
16
|
+
|
17
|
+
module Dummy
|
18
|
+
extend ActiveSupport::Concern
|
19
|
+
included do
|
20
|
+
end
|
21
|
+
module ClassMethods
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class GenericNode
|
28
|
+
include N4j::Entity
|
29
|
+
include N4j::Node
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
class RootNode < GenericNode
|
34
|
+
skip_callback :initialize, :after, :relate_to_root
|
35
|
+
end
|
36
|
+
|
37
|
+
class GenericRelationship
|
38
|
+
include N4j::Entity
|
39
|
+
include N4j::Relationship
|
40
|
+
|
41
|
+
def self.find_by_node_path(path)
|
42
|
+
result = N4j.batch([{:to => path ,:method => 'GET'}])
|
43
|
+
result.first['body'].collect {|r| GenericRelationship.new(r) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module N4j::Attributes
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
include ActiveModel::AttributeMethods
|
5
|
+
include ActiveModel::Dirty
|
6
|
+
|
7
|
+
included do
|
8
|
+
attribute_method_suffix('=')
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def attributes
|
13
|
+
@attributes ||= Set.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def attribute(name)
|
17
|
+
attributes << name.to_s
|
18
|
+
define_attribute_methods [name.to_s]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def attribute(key)
|
23
|
+
instance_variable_get("@#{key}")
|
24
|
+
end
|
25
|
+
|
26
|
+
def attribute=(key,value)
|
27
|
+
# puts "Setting attribute #{key}!"
|
28
|
+
send("#{key}_will_change!") unless value == send(key)
|
29
|
+
instance_variable_set("@#{key}", value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def attributes
|
33
|
+
self.class.attributes.inject({}) do |hsh, attr|
|
34
|
+
hsh[attr] = send(attr)
|
35
|
+
hsh
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def load_attributes(new_attributes)
|
40
|
+
self.place_in_batch = nil
|
41
|
+
self.class.attributes.each do |key|
|
42
|
+
unless new_attributes[key].blank?
|
43
|
+
value = new_attributes[key].dup
|
44
|
+
instance_variable_set("@#{key}", value)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module N4j::Node::AutoRelate
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
included do
|
4
|
+
define_singleton_method "#{model_name.param_key}_root" do
|
5
|
+
model_root
|
6
|
+
end
|
7
|
+
after_initialize :relate_to_root
|
8
|
+
end
|
9
|
+
module ClassMethods
|
10
|
+
def model_root
|
11
|
+
# Turned off cache here until I can figure out how to keep this from messing with tests.
|
12
|
+
# root = instance_variable_get("@#{model_root_name}")
|
13
|
+
# return root if root
|
14
|
+
|
15
|
+
root = follow_path_from_root || create_root_node
|
16
|
+
instance_variable_set("@#{model_root_name}", root)
|
17
|
+
end
|
18
|
+
|
19
|
+
def model_root_name
|
20
|
+
variable_name = model_name.param_key
|
21
|
+
model_root_name = "#{variable_name}_root"
|
22
|
+
end
|
23
|
+
|
24
|
+
def follow_path_from_root
|
25
|
+
service_root.relationships('outgoing_relationships', model_root_name).first.try(:end)
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_root_node
|
29
|
+
node = RootNode.new({:name => model_name.human})
|
30
|
+
node.new_relationships << GenericRelationship.new(:start => service_root, :end => node, :type => "#{model_root_name}")
|
31
|
+
node.save
|
32
|
+
node
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def relate_to_root
|
37
|
+
unless persisted?
|
38
|
+
new_relationships << GenericRelationship.new(:start => self.class.model_root, :end => self, :type => 'is_a')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/n4j/cypher.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
class N4j::Cypher
|
2
|
+
PARAMETERS = [:start, :match, :where, :return]
|
3
|
+
attr_accessor *PARAMETERS
|
4
|
+
|
5
|
+
def initialize(opts ={})
|
6
|
+
opts.each_pair do |k,v|
|
7
|
+
send "#{k}=", v if respond_to?("#{k}=")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def next(type = nil) # hops?
|
12
|
+
start_var = start.sub(/start\s+/,'').sub(/\s*=.+/,'')
|
13
|
+
self.match = "match (#{start_var})-->(b)"
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def start=(entity)
|
18
|
+
@start = if entity.kind_of?(String)
|
19
|
+
entity
|
20
|
+
else
|
21
|
+
"start a = #{entity.entity_type}(#{entity.to_key.first})"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def return
|
26
|
+
@return || (match && "return #{match.split(/\W+/).last}")
|
27
|
+
end
|
28
|
+
|
29
|
+
def go
|
30
|
+
self.class.query(to_query)
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_query
|
34
|
+
raise "start must contain 'start' (currently: '#{start}')" if start && !start.index('start')
|
35
|
+
raise "match must contain 'match' (currently: '#{match}')" if match && !match.index('match')
|
36
|
+
raise "where must contain 'where' (currently: '#{where}')" if where && !where.index('where')
|
37
|
+
raise "return must contain 'return' (currently: '#{self.return}')" if self.return && !self.return.index('return')
|
38
|
+
|
39
|
+
"#{start} #{match} #{where} #{self.return}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.query(query)
|
43
|
+
N4j.batch([{:to => '/ext/CypherPlugin/graphdb/execute_query', :method => 'POST', :body => {'query' => query}}]).first['body']
|
44
|
+
end
|
45
|
+
end
|
data/lib/n4j/entity.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
module N4j
|
2
|
+
module Entity
|
3
|
+
|
4
|
+
autoload :Representation, 'n4j/representation'
|
5
|
+
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
extend ActiveModel::Naming
|
10
|
+
include ActiveModel::Conversion
|
11
|
+
include N4j::Attributes
|
12
|
+
include N4j::Field
|
13
|
+
|
14
|
+
extend ActiveModel::Callbacks
|
15
|
+
define_model_callbacks :initialize
|
16
|
+
|
17
|
+
include Representation
|
18
|
+
|
19
|
+
attr_accessor :from_neo4j
|
20
|
+
attr_accessor :destroyed
|
21
|
+
attribute :data
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(hsh)
|
28
|
+
run_callbacks :initialize do
|
29
|
+
if N4j.neo4j_hash?(hsh)
|
30
|
+
load_neo4j_data(hsh)
|
31
|
+
load_attributes(from_neo4j)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def note_update!
|
37
|
+
changed_attributes.clear
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_neo4j_data(hsh)
|
41
|
+
changed_attributes.clear
|
42
|
+
if hsh # update requests do not return data
|
43
|
+
self.from_neo4j = HashWithIndifferentAccess.new.merge(hsh)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def from_neo4j_relative
|
48
|
+
# Use try here?
|
49
|
+
@from_neo4j_relative ||= Hash.new do |hash,key|
|
50
|
+
from_neo4j &&
|
51
|
+
from_neo4j[key].try(:sub, /http.+\/db\/data/, '')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def persisted?
|
56
|
+
N4j.neo4j_hash?(from_neo4j) && !destroyed?
|
57
|
+
end
|
58
|
+
|
59
|
+
def needs_persist?
|
60
|
+
!persisted? || changed?
|
61
|
+
end
|
62
|
+
|
63
|
+
def destroyed?
|
64
|
+
@destroyed
|
65
|
+
end
|
66
|
+
|
67
|
+
def destroy
|
68
|
+
destroyable = destroy_bundle.select(&:persisted?)
|
69
|
+
commands = destroyable.collect(&:destroy_hash)
|
70
|
+
results = N4j.batch(commands)
|
71
|
+
destroy_bundle.each {|entity| entity.destroyed = true }
|
72
|
+
end
|
73
|
+
|
74
|
+
def save
|
75
|
+
updateable_bundle = create_bundle.select(&:persist_hash)
|
76
|
+
commands = updateable_bundle.collect {|entity| entity.persist_hash(:id => entity.place_in_batch) }
|
77
|
+
results = N4j.batch(commands)
|
78
|
+
updateable_bundle.zip(results).each do |entity,result|
|
79
|
+
entity.load_neo4j_data(result['body'])
|
80
|
+
entity.post_save
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def prerequisites ; [] ; end
|
85
|
+
def dependent ; [] ; end
|
86
|
+
def connected_unsaved ; [] ; end
|
87
|
+
def post_save ; [] ; end
|
88
|
+
|
89
|
+
# Self and related nodes/relationships
|
90
|
+
def create_bundle(accumulated_bundle = [])
|
91
|
+
unless accumulated_bundle.detect {|entity| entity == self || entity.path == path }
|
92
|
+
accumulated_bundle = prerequisites.inject(accumulated_bundle) {|bundle, entity| entity.create_bundle(bundle) }
|
93
|
+
self.place_in_batch = accumulated_bundle.length
|
94
|
+
accumulated_bundle << self
|
95
|
+
connected_unsaved.inject(accumulated_bundle) {|bundle, entity| entity.create_bundle(bundle) }
|
96
|
+
end
|
97
|
+
accumulated_bundle
|
98
|
+
end
|
99
|
+
|
100
|
+
def destroy_bundle(accumulated_bundle = [])
|
101
|
+
unless accumulated_bundle.detect {|entity| entity == self || entity.path == path }
|
102
|
+
accumulated_bundle = dependent.inject(accumulated_bundle) {|bundle, entity| entity.destroy_bundle(bundle) }
|
103
|
+
accumulated_bundle << self
|
104
|
+
end
|
105
|
+
accumulated_bundle
|
106
|
+
end
|
107
|
+
|
108
|
+
def place_in_batch=(num)
|
109
|
+
@place_in_batch = num
|
110
|
+
end
|
111
|
+
|
112
|
+
def place_in_batch
|
113
|
+
@place_in_batch
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|