neography 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ log/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in neography.gemspec
4
+ gemspec
@@ -0,0 +1,38 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ neography (0.0.1)
5
+ httparty (~> 0.6.1)
6
+ json
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ crack (0.1.8)
12
+ diff-lcs (1.1.2)
13
+ fakeweb (1.3.0)
14
+ httparty (0.6.1)
15
+ crack (= 0.1.8)
16
+ json (1.4.6-java)
17
+ net-http-spy (0.2.1)
18
+ rspec (2.0.1)
19
+ rspec-core (~> 2.0.1)
20
+ rspec-expectations (~> 2.0.1)
21
+ rspec-mocks (~> 2.0.1)
22
+ rspec-core (2.0.1)
23
+ rspec-expectations (2.0.1)
24
+ diff-lcs (>= 1.1.2)
25
+ rspec-mocks (2.0.1)
26
+ rspec-core (~> 2.0.1)
27
+ rspec-expectations (~> 2.0.1)
28
+
29
+ PLATFORMS
30
+ java
31
+
32
+ DEPENDENCIES
33
+ fakeweb (~> 1.3.0)
34
+ httparty (~> 0.6.1)
35
+ json
36
+ neography!
37
+ net-http-spy (~> 0.2.1)
38
+ rspec (~> 2.0.0.beta.22)
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010 Max De Marzi
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,94 @@
1
+ == Welcome to Neography
2
+
3
+ Neography is a thin ruby wrapper to the Neo4j Rest API, for more information:
4
+ * {Getting Started with Neo4j Server}[http://wiki.neo4j.org/content/Getting_Started_with_Neo4j_Server]
5
+ * {Neo4j Rest API Reference}[http://components.neo4j.org/neo4j-rest/]
6
+
7
+
8
+ === Installation
9
+
10
+ gem install 'neography'
11
+ require 'neography'
12
+
13
+ === Dependencies
14
+
15
+ for use:
16
+ json
17
+ httparty
18
+
19
+ for development:
20
+ rspec
21
+ net-http-spy
22
+ fakeweb
23
+
24
+ ==== Rails
25
+
26
+ Just add gem 'neography' to your Gemfile and run bundle install
27
+
28
+ === Documentation
29
+
30
+ A thin ruby wrapper Neography::Rest which tries to mirror the Neo4j Rest API and returns JSON or Nil:
31
+
32
+ # protocol, server, port, log_file, log_enabled
33
+ @neo = Neography::Rest.new ('http://', '192.168.10.1', 7479, 'log/neography.log', true)
34
+
35
+ Default Parameters are:
36
+
37
+ @neo = Neography::Rest.new ('http://', 'localhost', 7474, '/neography.log', false)
38
+
39
+ To Use:
40
+
41
+ @neo = Neography::Rest.new
42
+
43
+ @neo.get_root # Get the root node
44
+ @neo.create_node # Create an empty node
45
+ @neo.create_node("age" => 31, "name" => "Max") # Create a node with some properties
46
+ @neo.get_node(id) # Get a node and its properties
47
+ @neo.delete_node(id) # Delete an unrelated node
48
+ @neo.delete_node!(id) # Delete a node and all its relationships
49
+
50
+ @neo.reset_node_properties(id, {"age" => 31}) # Reset a node's properties
51
+ @neo.set_node_properties(id, {"weight" => 200}) # Set a node's properties
52
+ @neo.get_node_properties(id) # Get just the node properties
53
+ @neo.get_node_properties(id, ["weight","age"]) # Get some of the node properties
54
+ @neo.remove_node_properties(id) # Remove all properties of a node
55
+ @neo.remove_node_properties(id, "weight") # Remove one property of a node
56
+ @neo.remove_node_properties(id, ["weight","age"]) # Remove multiple properties of a node
57
+
58
+ @neo.create_relationship("friends", node1, node2) # Create a relationship between node1 and node2
59
+ @neo.get_node_relationships(id) # Get all relationships
60
+ @neo.get_node_relationships(id, "in") # Get only incoming relationships
61
+ @neo.get_node_relationships(id, "all", "enemies") # Get all relationships of type enemies
62
+ @neo.get_node_relationships(id, "in", "enemies") # Get only incoming relationships of type enemies
63
+ @neo.delete_relationship(id) # Delete a relationship
64
+
65
+ @neo.reset_relationship_properties(id, {"age" => 31}) # Reset a relationship's properties
66
+ @neo.set_relationship_properties(id, {"weight" => 200}) # Set a relationship's properties
67
+ @neo.get_relationship_properties(id) # Get just the relationship properties
68
+ @neo.get_relationship_properties(id, ["since","met"]) # Get some of the relationship properties
69
+ @neo.remove_relationship_properties(id) # Remove all properties of a relationship
70
+ @neo.remove_relationship_properties(id, "since") # Remove one property of a relationship
71
+ @neo.remove_relationship_properties(id, ["since","met"]) # Remove multiple properties of a relationship
72
+
73
+ @neo.list_indexes # doesn't really seam to do what the api says it does
74
+ @neo.add_to_index(key, value, id) # adds a node to an index with the given key/value pair
75
+ @neo.remove_from_index(key, value, id) # removes a node to an index with the given key/value pair
76
+ @neo.get_index(key, value) # gets an index with the given key/value pair
77
+
78
+ @neo.get_path(from, to, relationships, depth=4, algorithm="shortestPath") # finds the shortest path between two nodes
79
+ @neo.get_paths(from, to, relationships, depth=3, algorithm="allPaths") # finds all paths between two nodes
80
+
81
+ === To Do
82
+
83
+ Path tests
84
+ Traverse tests
85
+ @neo.traverse()
86
+ examples
87
+
88
+ === License
89
+
90
+ * Neography - MIT, see the LICENSE file http://github.com/maxdemarzi/neography/tree/master/LICENSE.
91
+ * Lucene - Apache, see http://lucene.apache.org/java/docs/features.html
92
+ * Neo4j - Dual free software/commercial license, see http://neo4j.org/
93
+
94
+
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,26 @@
1
+ def find_and_require_user_defined_code
2
+ extensions_path = ENV['neography_extensions'] || "~/.neography"
3
+ extensions_path = File.expand_path(extensions_path)
4
+ if File.exists?(extensions_path)
5
+ Dir.open extensions_path do |dir|
6
+ dir.entries.each do |file|
7
+ if file.split('.').size > 1 && file.split('.').last == 'rb'
8
+ extension = File.join(File.expand_path(extensions_path), file)
9
+ require(extension) && puts("Loaded Extension: #{extension}")
10
+ end
11
+ end
12
+ end
13
+ else
14
+ puts "No Extensions Found: #{extensions_path}"
15
+ end
16
+ end
17
+
18
+
19
+ require 'httparty'
20
+ require 'json'
21
+ require 'logger'
22
+
23
+ require 'neography/rest'
24
+
25
+ find_and_require_user_defined_code
26
+
@@ -0,0 +1,152 @@
1
+ module Neography
2
+
3
+ # == Keeps configuration for neography
4
+ #
5
+ # The most important configuration options are <tt>Neograophy::Config[:server]</tt> and <tt>Neograophy::Config[:port]</tt> which are
6
+ # used to locate where the neo4j database and is stored on the network.
7
+ # If these options are not supplied then the default of localhost:9999 will be used.
8
+ #
9
+ # ==== Default Configurations
10
+ # <tt>:protocol</tt>:: default <tt>http://</tt> protocol to use (can be https://)
11
+ # <tt>:server</tt>:: default <tt>localhost</tt> where the database is stored on the network
12
+ # <tt>:port</tt>:: default <tt>7474</tt> what port is listening
13
+ #
14
+ class Config
15
+ # This code is copied from merb-core/config.rb.
16
+ class << self
17
+ # Returns the hash of default config values for neography
18
+ #
19
+ # ==== Returns
20
+ # Hash:: The defaults for the config.
21
+ def defaults
22
+ @defaults ||= {
23
+ :protocol => 'http://',
24
+ :server => 'localhost',
25
+ :port => '7474'
26
+ }
27
+ end
28
+
29
+
30
+ # Yields the configuration.
31
+ #
32
+ # ==== Block parameters
33
+ # c :: The configuration parameters, a hash.
34
+ #
35
+ # ==== Examples
36
+ # Neography::Config.use do |config|
37
+ # config[:server] = '192.168.1.13'
38
+ # end
39
+ #
40
+ # ==== Returns
41
+ # nil
42
+ def use
43
+ @configuration ||= {}
44
+ yield @configuration
45
+ nil
46
+ end
47
+
48
+
49
+ # Set the value of a config entry.
50
+ #
51
+ # ==== Parameters
52
+ # key :: The key to set the parameter for.
53
+ # val :: The value of the parameter.
54
+ #
55
+ def []=(key, val)
56
+ (@configuration ||= setup)[key] = val
57
+ end
58
+
59
+
60
+ # Gets the the value of a config entry
61
+ #
62
+ # ==== Parameters
63
+ # key:: The key of the config entry value we want
64
+ #
65
+ def [](key)
66
+ (@configuration ||= setup)[key]
67
+ end
68
+
69
+
70
+ # Remove the value of a config entry.
71
+ #
72
+ # ==== Parameters
73
+ # key<Object>:: The key of the parameter to delete.
74
+ #
75
+ # ==== Returns
76
+ # The value of the removed entry.
77
+ #
78
+ def delete(key)
79
+ @configuration.delete(key)
80
+ end
81
+
82
+
83
+ # Remove all configuration. This can be useful for testing purpose.
84
+ #
85
+ #
86
+ # ==== Returns
87
+ # nil
88
+ #
89
+ def delete_all
90
+ @configuration = nil
91
+ end
92
+
93
+
94
+ # Retrieve the value of a config entry, returning the provided default if the key is not present
95
+ #
96
+ # ==== Parameters
97
+ # key:: The key to retrieve the parameter for.
98
+ # default::The default value to return if the parameter is not set.
99
+ #
100
+ # ==== Returns
101
+ # The value of the configuration parameter or the default.
102
+ #
103
+ def fetch(key, default)
104
+ @configuration.fetch(key, default)
105
+ end
106
+
107
+ # Sets up the configuration
108
+ #
109
+ # ==== Returns
110
+ # The configuration as a hash.
111
+ #
112
+ def setup()
113
+ @configuration = {}
114
+ @configuration.merge!(defaults)
115
+ @configuration
116
+ end
117
+
118
+
119
+ # Returns the configuration as a hash.
120
+ #
121
+ # ==== Returns
122
+ # The config as a hash.
123
+ #
124
+ def to_hash
125
+ @configuration
126
+ end
127
+
128
+ # Returns the config as YAML.
129
+ #
130
+ # ==== Returns
131
+ # The config as YAML.
132
+ #
133
+ def to_yaml
134
+ require "yaml"
135
+ @configuration.to_yaml
136
+ end
137
+
138
+ # Returns the configuration as a string.
139
+ #
140
+ # ==== Returns
141
+ # The config as a string.
142
+ #
143
+ def to_s
144
+ setup
145
+ @configuration[:protocol] + @configuration[:server].to_s + ':' + @configuration[:port].to_s
146
+ end
147
+
148
+
149
+ end
150
+ end
151
+
152
+ end
@@ -0,0 +1,241 @@
1
+ module Neography
2
+ class Rest
3
+ include HTTParty
4
+ attr_accessor :protocol, :server, :port, :log_file, :log_enabled, :logger
5
+
6
+ def initialize(protocol='http://', server='localhost', port=7474, log_file='neography.log', log_enabled=true)
7
+ @protocol = protocol
8
+ @server = server
9
+ @port = port
10
+ @log_file = log_file
11
+ @log_enabled = log_enabled
12
+ @logger = Logger.new(@log_file) if @log_enabled
13
+ end
14
+
15
+ def configure(protocol, server, port)
16
+ @protocol = protocol
17
+ @server = server
18
+ @port = port
19
+ end
20
+
21
+ def configuration
22
+ @protocol + @server + ':' + @port.to_s + "/db/data"
23
+ end
24
+
25
+ def get_root
26
+ get('/')
27
+ end
28
+
29
+ def create_node(*args)
30
+ if args[0].respond_to?(:each_pair) && args[0]
31
+ options = { :body => args[0].to_json, :headers => {'Content-Type' => 'application/json'} }
32
+ post("/node", options)
33
+ else
34
+ post("/node")
35
+ end
36
+ end
37
+
38
+ def get_node(id)
39
+ get("/node/#{id}")
40
+ end
41
+
42
+ def reset_node_properties(id, properties)
43
+ options = { :body => properties.to_json, :headers => {'Content-Type' => 'application/json'} }
44
+ put("/node/#{id}/properties", options)
45
+ end
46
+
47
+ def get_node_properties(id, properties = nil)
48
+ if properties.nil?
49
+ get("/node/#{id}/properties")
50
+ else
51
+ node_properties = Hash.new
52
+ properties.to_a.each do |property|
53
+ value = get("/node/#{id}/properties/#{property}")
54
+ node_properties[property] = value unless value.nil?
55
+ end
56
+ return nil if node_properties.empty?
57
+ node_properties
58
+ end
59
+ end
60
+
61
+ def remove_node_properties(id, properties = nil)
62
+ if properties.nil?
63
+ delete("/node/#{id}/properties")
64
+ else
65
+ properties.to_a.each do |property|
66
+ delete("/node/#{id}/properties/#{property}")
67
+ end
68
+ end
69
+ end
70
+
71
+ def set_node_properties(id, properties)
72
+ properties.each do |key, value|
73
+ options = { :body => value.to_json, :headers => {'Content-Type' => 'application/json'} }
74
+ put("/node/#{id}/properties/#{key}", options)
75
+ end
76
+ end
77
+
78
+ def delete_node(id)
79
+ delete("/node/#{id}")
80
+ end
81
+
82
+ def create_relationship(type, from, to, props = nil)
83
+ options = { :body => {:to => self.configuration + "/node/#{to}", :data => props, :type => type }.to_json, :headers => {'Content-Type' => 'application/json'} }
84
+ post("/node/#{from}/relationships", options)
85
+ end
86
+
87
+ def reset_relationship_properties(id, properties)
88
+ options = { :body => properties.to_json, :headers => {'Content-Type' => 'application/json'} }
89
+ put("/relationship/#{id}/properties", options)
90
+ end
91
+
92
+ def get_relationship_properties(id, properties = nil)
93
+ if properties.nil?
94
+ get("/relationship/#{id}/properties")
95
+ else
96
+ relationship_properties = Hash.new
97
+ properties.to_a.each do |property|
98
+ value = get("/relationship/#{id}/properties/#{property}")
99
+ relationship_properties[property] = value unless value.nil?
100
+ end
101
+ return nil if relationship_properties.empty?
102
+ relationship_properties
103
+ end
104
+ end
105
+
106
+ def remove_relationship_properties(id, properties = nil)
107
+ if properties.nil?
108
+ delete("/relationship/#{id}/properties")
109
+ else
110
+ properties.to_a.each do |property|
111
+ delete("/relationship/#{id}/properties/#{property}")
112
+ end
113
+ end
114
+ end
115
+
116
+ def set_relationship_properties(id, properties)
117
+ properties.each do |key, value|
118
+ options = { :body => value.to_json, :headers => {'Content-Type' => 'application/json'} }
119
+ put("/relationship/#{id}/properties/#{key}", options)
120
+ end
121
+ end
122
+
123
+ def delete_relationship(id)
124
+ delete("/relationship/#{id}")
125
+ end
126
+
127
+ def get_node_relationships(id, dir=nil, types=nil)
128
+ dir = get_dir(dir)
129
+
130
+ if types.nil?
131
+ node_relationships = get("/node/#{id}/relationships/#{dir}") || Array.new
132
+ else
133
+ node_relationships = get("/node/#{id}/relationships/#{dir}/#{types.to_a.join('&')}") || Array.new
134
+ end
135
+ return nil if node_relationships.empty?
136
+ node_relationships
137
+ end
138
+
139
+ def delete_node!(id)
140
+ relationships = get_node_relationships(id)
141
+ relationships.each { |r| delete_relationship(r["self"].split('/').last) } unless relationships.nil?
142
+ delete("/node/#{id}")
143
+ end
144
+
145
+ def list_indexes
146
+ get("/index")
147
+ end
148
+
149
+ def add_to_index(key, value, id)
150
+ options = { :body => (self.configuration + "/node/#{id}").to_json, :headers => {'Content-Type' => 'application/json'} }
151
+ post("/index/node/#{key}/#{value}", options)
152
+ end
153
+
154
+ def remove_from_index(key, value, id)
155
+ delete("/index/node/#{key}/#{value}/#{id}")
156
+ end
157
+
158
+ def get_index(key, value)
159
+ index = get("/index/node/#{key}/#{value}") || Array.new
160
+ return nil if index.empty?
161
+ index
162
+ end
163
+
164
+ def get_path(from, to, relationships, depth=1, algorithm="shortestPath")
165
+ options = { :body => {"to" => self.configuration + "/node/#{to}", "relationships" => relationships, "max depth" => depth, "algorithm" => get_algorithm(algorithm) }.to_json, :headers => {'Content-Type' => 'application/json'} }
166
+ path = post("/node/#{from}/path", options) || Hash.new
167
+ end
168
+
169
+ def get_paths(from, to, relationships, depth=1, algorithm="allPaths")
170
+ options = { :body => {"to" => self.configuration + "/node/#{to}", "relationships" => relationships, "max depth" => depth, "algorithm" => get_algorithm(algorithm) }.to_json, :headers => {'Content-Type' => 'application/json'} }
171
+ paths = post("/node/#{from}/paths", options) || Array.new
172
+ end
173
+
174
+ private
175
+
176
+ def evaluate_response(response)
177
+ code = response.code
178
+ body = response.body
179
+
180
+ case code
181
+ when 200
182
+ @logger.debug "OK" if @log_enabled
183
+ response
184
+ when 201
185
+ @logger.debug "OK, created #{body}" if @log_enabled
186
+ response
187
+ when 204
188
+ @logger.debug "OK, no content returned" if @log_enabled
189
+ nil
190
+ when 400
191
+ @logger.error "Invalid data sent #{body}" if @log_enabled
192
+ nil
193
+ when 404
194
+ @logger.error "#{body}" if @log_enabled
195
+ nil
196
+ when 409
197
+ @logger.error "Node could not be deleted (still has relationships?)" if @log_enabled
198
+ nil
199
+ end
200
+ end
201
+
202
+ def get(path,options={})
203
+ evaluate_response(HTTParty.get(configuration + path, options))
204
+ end
205
+
206
+ def post(path,options={})
207
+ evaluate_response(HTTParty.post(configuration + path, options))
208
+ end
209
+
210
+ def put(path,options={})
211
+ evaluate_response(HTTParty.put(configuration + path, options))
212
+ end
213
+
214
+ def delete(path,options={})
215
+ evaluate_response(HTTParty.delete(configuration + path, options))
216
+ end
217
+
218
+ def get_dir(dir)
219
+ case dir
220
+ when :incoming, "incoming", :in, "in"
221
+ "in"
222
+ when :outgoing, "outgoing", :out, "out"
223
+ "out"
224
+ else
225
+ "all"
226
+ end
227
+ end
228
+
229
+ def get_algorithm(algorithm)
230
+ case algorithm
231
+ when :shortest, "shortest", :shortestPath, "shortestPath", :short, "short"
232
+ "shortestPath"
233
+ when :allSimplePaths, "allSimplePaths", :simple, "simple"
234
+ "allSimplePaths"
235
+ else
236
+ "allPaths"
237
+ end
238
+ end
239
+
240
+ end
241
+ end