neography 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.
@@ -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