n4j 0.0.1
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.
- 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
|