diff --git a/Gemfile.lock b/Gemfile.lock index 3b503b6..db63124 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -13,7 +13,7 @@ GEM remote: http://rubygems.org/ specs: diff-lcs (1.1.3) - httparty (0.8.3) + httparty (0.9.0) multi_json (~> 1.0) multi_xml json (1.7.5) diff --git a/lib/neography.rb b/lib/neography.rb index 2400f67..03c7f1b 100644 --- a/lib/neography.rb +++ b/lib/neography.rb @@ -29,7 +29,28 @@ def find_and_require_user_defined_code require 'neography/version' require 'neography/config' + +require 'neography/rest/helpers' +require 'neography/rest/paths' + +require 'neography/rest/nodes' +require 'neography/rest/node_properties' +require 'neography/rest/node_relationships' +require 'neography/rest/node_indexes' +require 'neography/rest/node_auto_indexes' +require 'neography/rest/node_traversal' +require 'neography/rest/node_paths' +require 'neography/rest/relationships' +require 'neography/rest/relationship_properties' +require 'neography/rest/relationship_indexes' +require 'neography/rest/relationship_auto_indexes' +require 'neography/rest/cypher' +require 'neography/rest/gremlin' +require 'neography/rest/batch' +require 'neography/rest/clean' +require 'neography/connection' require 'neography/rest' + require 'neography/neography' require 'neography/property_container' diff --git a/lib/neography/connection.rb b/lib/neography/connection.rb new file mode 100644 index 0000000..bdadc3b --- /dev/null +++ b/lib/neography/connection.rb @@ -0,0 +1,120 @@ +module Neography + class Connection + + USER_AGENT = "Neography/#{Neography::VERSION}" + + attr_accessor :protocol, :server, :port, :directory, + :cypher_path, :gremlin_path, + :log_file, :log_enabled, :logger, + :max_threads, + :authentication, :username, :password, + :parser + + def initialize(options=ENV['NEO4J_URL'] || {}) + init = { + :protocol => Neography::Config.protocol, + :server => Neography::Config.server, + :port => Neography::Config.port, + :directory => Neography::Config.directory, + :cypher_path => Neography::Config.cypher_path, + :gremlin_path => Neography::Config.gremlin_path, + :log_file => Neography::Config.log_file, + :log_enabled => Neography::Config.log_enabled, + :max_threads => Neography::Config.max_threads, + :authentication => Neography::Config.authentication, + :username => Neography::Config.username, + :password => Neography::Config.password, + :parser => Neography::Config.parser + } + + unless options.respond_to?(:each_pair) + url = URI.parse(options) + options = { + :protocol => url.scheme + "://", + :server => url.host, + :port => url.port, + :directory => url.path, + :username => url.user, + :password => url.password + } + options[:authentication] = 'basic' unless url.user.nil? + end + + init.merge!(options) + + @protocol = init[:protocol] + @server = init[:server] + @port = init[:port] + @directory = init[:directory] + @cypher_path = init[:cypher_path] + @gremlin_path = init[:gremlin_path] + @log_file = init[:log_file] + @log_enabled = init[:log_enabled] + @logger = Logger.new(@log_file) if @log_enabled + @max_threads = init[:max_threads] + @authentication = {} + @authentication = {"#{init[:authentication]}_auth".to_sym => {:username => init[:username], :password => init[:password]}} unless init[:authentication].empty? + @parser = init[:parser] + @user_agent = {"User-Agent" => USER_AGENT} + end + + def configure(protocol, server, port, directory) + @protocol = protocol + @server = server + @port = port + @directory = directory + end + + def configuration + @protocol + @server + ':' + @port.to_s + @directory + "/db/data" + end + + def merge_options(options) + merged_options = options.merge!(@authentication).merge!(@parser) + merged_options[:headers].merge!(@user_agent) if merged_options[:headers] + merged_options + end + + def get(path, options={}) + evaluate_response(HTTParty.get(configuration + path, merge_options(options))) + end + + def post(path, options={}) + evaluate_response(HTTParty.post(configuration + path, merge_options(options))) + end + + def put(path, options={}) + evaluate_response(HTTParty.put(configuration + path, merge_options(options))) + end + + def delete(path, options={}) + evaluate_response(HTTParty.delete(configuration + path, merge_options(options))) + end + + def evaluate_response(response) + code = response.code + body = response.body + case code + when 200 + @logger.debug "OK" if @log_enabled + response.parsed_response + when 201 + @logger.debug "OK, created #{body}" if @log_enabled + response.parsed_response + when 204 + @logger.debug "OK, no content returned" if @log_enabled + nil + when 400 + @logger.error "Invalid data sent #{body}" if @log_enabled + nil + when 404 + @logger.error "Not Found #{body}" if @log_enabled + nil + when 409 + @logger.error "Node could not be deleted (still has relationships?)" if @log_enabled + nil + end + end + + end +end diff --git a/lib/neography/node_traverser.rb b/lib/neography/node_traverser.rb index b88e6b8..18b1d46 100644 --- a/lib/neography/node_traverser.rb +++ b/lib/neography/node_traverser.rb @@ -59,16 +59,18 @@ def order(o) end def filter(body) - @filter = Hash.new - @filter["language"] = "javascript" - @filter["body"] = body + @filter = { + "language" => "javascript", + "body" => body + } self end def prune(body) - @prune = Hash.new - @prune["language"] = "javascript" - @prune["body"] = body + @prune = { + "language" => "javascript", + "body" => body + } self end @@ -79,9 +81,10 @@ def depth(d) end def include_start_node - @filter = Hash.new - @filter["language"] = "builtin" - @filter["name"] = "all" + @filter = { + "language" => "builtin", + "name" => "all" + } self end @@ -108,13 +111,14 @@ def each end def iterator - options = Hash.new - options["order"] = @order - options["uniqueness"] = @uniqueness - options["relationships"] = @relationships - options["prune evaluator"] = @prune unless @prune.nil? - options["return filter"] = @filter unless @filter.nil? - options["depth"] = @depth unless @depth.nil? + options = { + "order" => @order, + "uniqueness" => @uniqueness, + "relationships" => @relationships + } + options["prune evaluator"] = @prune unless @prune.nil? + options["return filter"] = @filter unless @filter.nil? + options["depth"] = @depth unless @depth.nil? if @relationships[0]["type"].empty? rels = @from.neo_server.get_node_relationships(@from, @relationships[0]["direction"]) @@ -124,11 +128,11 @@ def iterator when "out" rels.collect { |r| @from.neo_server.get_node(r["end"]) } #.uniq else - rels.collect { |r| + rels.collect { |r| if @from.neo_id == r["start"].split('/').last - @from.neo_server.get_node(r["end"]) + @from.neo_server.get_node(r["end"]) else - @from.neo_server.get_node(r["start"]) + @from.neo_server.get_node(r["start"]) end } #.uniq end @@ -139,4 +143,4 @@ def iterator end -end \ No newline at end of file +end diff --git a/lib/neography/rest.rb b/lib/neography/rest.rb index cf08901..87a408d 100644 --- a/lib/neography/rest.rb +++ b/lib/neography/rest.rb @@ -1,691 +1,362 @@ +require 'forwardable' + module Neography - + class Rest include HTTParty - USER_AGENT = "Neography/#{Neography::VERSION}" - - attr_accessor :protocol, :server, :port, :directory, :cypher_path, :gremlin_path, :log_file, :log_enabled, :logger, :max_threads, :authentication, :username, :password, :parser - - def initialize(options=ENV['NEO4J_URL'] || {}) - init = {:protocol => Neography::Config.protocol, - :server => Neography::Config.server, - :port => Neography::Config.port, - :directory => Neography::Config.directory, - :cypher_path => Neography::Config.cypher_path, - :gremlin_path => Neography::Config.gremlin_path, - :log_file => Neography::Config.log_file, - :log_enabled => Neography::Config.log_enabled, - :max_threads => Neography::Config.max_threads, - :authentication => Neography::Config.authentication, - :username => Neography::Config.username, - :password => Neography::Config.password, - :parser => Neography::Config.parser - } - - unless options.respond_to?(:each_pair) - url = URI.parse(options) - options = Hash.new - options[:protocol] = url.scheme + "://" - options[:server] = url.host - options[:port] = url.port - options[:directory] = url.path - options[:username] = url.user - options[:password] = url.password - options[:authentication] = 'basic' unless url.user.nil? - end - - init.merge!(options) - - @protocol = init[:protocol] - @server = init[:server] - @port = init[:port] - @directory = init[:directory] - @cypher_path = init[:cypher_path] - @gremlin_path = init[:gremlin_path] - @log_file = init[:log_file] - @log_enabled = init[:log_enabled] - @logger = Logger.new(@log_file) if @log_enabled - @max_threads = init[:max_threads] - @authentication = Hash.new - @authentication = {"#{init[:authentication]}_auth".to_sym => {:username => init[:username], :password => init[:password]}} unless init[:authentication].empty? - @parser = init[:parser] - @user_agent = {"User-Agent" => USER_AGENT} - end + include Helpers + extend Forwardable - def configure(protocol, server, port, directory) - @protocol = protocol - @server = server - @port = port - @directory = directory - end + attr_reader :connection - def configuration - @protocol + @server + ':' + @port.to_s + @directory + "/db/data" - end + def_delegators :@connection, :configuration - def get_root - get("/node/#{get_id(get('/')["reference_node"])}") - end + def initialize(options=ENV['NEO4J_URL'] || {}) + @connection = Connection.new(options) - def create_node(*args) - if args[0].respond_to?(:each_pair) && args[0] - options = { :body => args[0].delete_if { |k, v| v.nil? }.to_json, :headers => {'Content-Type' => 'application/json'} } - post("/node", options) - else - post("/node") - end - end + @nodes = Nodes.new(@connection) + @node_properties = NodeProperties.new(@connection) + @node_relationships = NodeRelationships.new(@connection) + @node_indexes = NodeIndexes.new(@connection) + @node_auto_indexes = NodeAutoIndexes.new(@connection) + @node_traversal = NodeTraversal.new(@connection) + @node_paths = NodePaths.new(@connection) - def create_nodes(nodes) - nodes = Array.new(nodes) if nodes.kind_of? Fixnum - created_nodes = Array.new - nodes.each do |node| - created_nodes << create_node(node) - end - created_nodes - end + @relationships = Relationships.new(@connection) + @relationship_properties = RelationshipProperties.new(@connection) + @relationship_indexes = RelationshipIndexes.new(@connection) + @relationship_auto_indexes = RelationshipAutoIndexes.new(@connection) - def create_nodes_threaded(nodes) - nodes = Array.new(nodes) if nodes.kind_of? Fixnum - - node_queue = Queue.new - thread_pool = [] - responses = Queue.new - - nodes.each do |node| - node_queue.push node - end - - [nodes.size, @max_threads].min.times do - thread_pool << Thread.new do - until node_queue.empty? do - node = node_queue.pop - if node.respond_to?(:each_pair) - responses.push( post("/node", { :body => node.to_json, :headers => {'Content-Type' => 'application/json'} } ) ) - else - responses.push( post("/node") ) - end - end - self.join - end - end - - created_nodes = Array.new - - while created_nodes.size < nodes.size - created_nodes << responses.pop - end - created_nodes - end + @cypher = Cypher.new(@connection) + @gremlin = Gremlin.new(@connection) + @batch = Batch.new(@connection) + @clean = Clean.new(@connection) + end -# This is not yet implemented in the REST API -# -# def get_all_node -# puts "get all nodes" -# get("/nodes/") -# end + def get_root + @nodes.root + end - def get_node(id) - get("/node/#{get_id(id)}") - end + def create_node(*args) + @nodes.create(*args) + end - def get_nodes(*nodes) - gotten_nodes = Array.new - Array(nodes).flatten.each do |node| - gotten_nodes << get_node(node) - end - gotten_nodes - end + def create_nodes(nodes) + @nodes.create_multiple(nodes) + end - def reset_node_properties(id, properties) - options = { :body => properties.to_json, :headers => {'Content-Type' => 'application/json'} } - put("/node/#{get_id(id)}/properties", options) - end + def create_nodes_threaded(nodes) + @nodes.create_multiple_threaded(nodes) + end - def get_node_properties(id, properties = nil) - if properties.nil? - get("/node/#{get_id(id)}/properties") - else - node_properties = Hash.new - Array(properties).each do |property| - value = get("/node/#{get_id(id)}/properties/#{property}") - node_properties[property] = value unless value.nil? - end - return nil if node_properties.empty? - node_properties - end - end + # nodes - def remove_node_properties(id, properties = nil) - if properties.nil? - delete("/node/#{get_id(id)}/properties") - else - Array(properties).each do |property| - delete("/node/#{get_id(id)}/properties/#{property}") - end - end - end + def get_node(id) + @nodes.get(id) + end - def set_node_properties(id, properties) - properties.each do |key, value| - options = { :body => value.to_json, :headers => {'Content-Type' => 'application/json'} } - put("/node/#{get_id(id)}/properties/#{key}", options) - end - end + def get_nodes(*nodes) + @nodes.get_each(*nodes) + end - def delete_node(id) - delete("/node/#{get_id(id)}") - end + # This is not yet implemented in the REST API + # + # def get_all_node + # puts "get all nodes" + # get("/nodes/") + # end - def create_relationship(type, from, to, props = nil) - options = { :body => {:to => self.configuration + "/node/#{get_id(to)}", :data => props, :type => type }.to_json, :headers => {'Content-Type' => 'application/json'} } - post("/node/#{get_id(from)}/relationships", options) - end + def delete_node(id) + @nodes.delete(id) + end - def create_unique_relationship(index, key, value, type, from, to) - body = {:key=>key,:value=>value, :type => type } - body[:start] = self.configuration + "/node/#{get_id(from)}" - body[:end] = self.configuration + "/node/#{get_id(to)}" - options = { :body => body.to_json, :headers => {'Content-Type' => 'application/json'} } - post("/index/relationship/#{index}?unique", options) - end + def delete_node!(id) + relationships = get_node_relationships(get_id(id)) + relationships.each do |relationship| + delete_relationship(relationship["self"].split('/').last) + end unless relationships.nil? - def get_relationship(id) - get("/relationship/#{get_id(id)}") - end + delete_node(id) + end - def get_relationship_start_node(rel) - get_node(rel["start"]) - end - - def get_relationship_end_node(rel) - get_node(rel["end"]) - end - - def reset_relationship_properties(id, properties) - options = { :body => properties.to_json, :headers => {'Content-Type' => 'application/json'} } - put("/relationship/#{get_id(id)}/properties", options) - end + # node properties - def get_relationship_properties(id, properties = nil) - if properties.nil? - get("/relationship/#{get_id(id)}/properties") - else - relationship_properties = Hash.new - Array(properties).each do |property| - value = get("/relationship/#{get_id(id)}/properties/#{property}") - relationship_properties[property] = value unless value.nil? - end - return nil if relationship_properties.empty? - relationship_properties - end - end + def reset_node_properties(id, properties) + @node_properties.reset(id, properties) + end - def remove_relationship_properties(id, properties = nil) - if properties.nil? - delete("/relationship/#{get_id(id)}/properties") - else - Array(properties).each do |property| - delete("/relationship/#{get_id(id)}/properties/#{property}") - end - end - end + def get_node_properties(id, *properties) + @node_properties.get(id, *properties.flatten) + end - def set_relationship_properties(id, properties) - properties.each do |key, value| - options = { :body => value.to_json, :headers => {'Content-Type' => 'application/json'} } - put("/relationship/#{get_id(id)}/properties/#{key}", options) - end - end + def remove_node_properties(id, *properties) + @node_properties.remove(id, *properties.flatten) + end - def delete_relationship(id) - delete("/relationship/#{get_id(id)}") - end + def set_node_properties(id, properties) + @node_properties.set(id, properties) + end - def get_node_relationships(id, dir=nil, types=nil) - dir = get_dir(dir) + # relationships - if types.nil? - node_relationships = get("/node/#{get_id(id)}/relationships/#{dir}") || Array.new - else - node_relationships = get("/node/#{get_id(id)}/relationships/#{dir}/#{Array(types).join('&')}") || Array.new - end - return nil if node_relationships.empty? - node_relationships - end + def get_relationship(id) + @relationships.get(id) + end - def delete_node!(id) - relationships = get_node_relationships(get_id(id)) - relationships.each { |r| delete_relationship(r["self"].split('/').last) } unless relationships.nil? - delete("/node/#{get_id(id)}") - end + def delete_relationship(id) + @relationships.delete(id) + end - def list_node_indexes - get("/index/node") - end + def get_relationship_start_node(rel) + get_node(rel["start"]) + end - def create_node_index(name, type = "exact", provider = "lucene") - options = { :body => ({:name => name, :config => {:type => type, :provider => provider}}).to_json, :headers => {'Content-Type' => 'application/json'} } - post("/index/node", options) - end + def get_relationship_end_node(rel) + get_node(rel["end"]) + end + + # relationship properties - def create_node_auto_index(type = "exact", provider = "lucene") - create_node_index("node_auto_index", type, provider) - end + def reset_relationship_properties(id, properties) + @relationship_properties.reset(id, properties) + end - def add_node_to_index(index, key, value, id) - options = { :body => ({:uri => self.configuration + "/node/#{get_id(id)}", :key => key, :value => value }).to_json, :headers => {'Content-Type' => 'application/json'} } - post("/index/node/#{index}", options) - end + def get_relationship_properties(id, *properties) + @relationship_properties.get(id, *properties.flatten) + end - def create_unique_node(index, key, value, props={}) - options = { :body => ({:properties=>props, :key => key, :value => value }).to_json, :headers => {'Content-Type' => 'application/json'} } - post("/index/node/#{index}?unique", options) - end + def remove_relationship_properties(id, *properties) + @relationship_properties.remove(id, *properties.flatten) + end - def remove_node_from_index(*args) - case args.size - when 4 then delete("/index/node/#{args[0]}/#{args[1]}/#{args[2]}/#{get_id(args[3])}") - when 3 then delete("/index/node/#{args[0]}/#{args[1]}/#{get_id(args[2])}") - when 2 then delete("/index/node/#{args[0]}/#{get_id(args[1])}") - end - end + def set_relationship_properties(id, properties) + @relationship_properties.set(id, properties) + end - def get_node_index(index, key, value) - index = get("/index/node/#{index}/#{key}/#{value}") || Array.new - return nil if index.empty? - index - end + # node relationships - def get_node_auto_index(key, value) - index = get("/index/auto/node/#{key}/#{value}") || Array.new - return nil if index.empty? - index - end + def create_relationship(type, from, to, props = nil) + @node_relationships.create(type, from, to, props) + end - def find_node_auto_index(*args) - case args.size - when 2 then index = get("/index/auto/node/#{args[0]}/#{args[1]}") || Array.new - when 1 then index = get("/index/auto/node/?query=#{args[0]}") || Array.new - end - return nil if index.empty? - index - end + def get_node_relationships(id, dir = nil, types = nil) + @node_relationships.get(id, dir, types) + end - def find_node_index(*args) - case args.size - when 3 then index = get("/index/node/#{args[0]}/#{args[1]}?query=\"#{args[2]}\"") || Array.new - when 2 then index = get("/index/node/#{args[0]}?query=#{args[1]}") || Array.new - end - return nil if index.empty? - index - end + # node indexes - alias_method :list_indexes, :list_node_indexes - alias_method :add_to_index, :add_node_to_index - alias_method :remove_from_index, :remove_node_from_index - alias_method :get_index, :get_node_index + def list_node_indexes + @node_indexes.list + end + alias_method :list_indexes, :list_node_indexes - def list_relationship_indexes - get("/index/relationship") - end + def create_node_index(name, type = "exact", provider = "lucene") + @node_indexes.create(name, type, provider) + end - def create_relationship_index(name, type = "exact", provider = "lucene") - options = { :body => ({:name => name, :config => {:type => type, :provider => provider}}).to_json, :headers => {'Content-Type' => 'application/json'} } - post("/index/relationship", options) - end + def create_node_auto_index(type = "exact", provider = "lucene") + @node_indexes.create_auto(type, provider) + end - def create_relationship_auto_index(type = "exact", provider = "lucene") - create_relationship_index("relationship_auto_index", type, provider) - end + def add_node_to_index(index, key, value, id) + @node_indexes.add(index, key, value, id) + end + alias_method :add_to_index, :add_node_to_index - def add_relationship_to_index(index, key, value, id) - options = { :body => ({:uri => self.configuration + "/relationship/#{get_id(id)}", :key => key, :value => value}).to_json, :headers => {'Content-Type' => 'application/json'} } - post("/index/relationship/#{index}", options) - end + def create_unique_node(index, key, value, props={}) + @node_indexes.create_unique(index, key, value, props) + end - def remove_relationship_from_index(*args) - case args.size - when 4 then delete("/index/relationship/#{args[0]}/#{args[1]}/#{args[2]}/#{get_id(args[3])}") - when 3 then delete("/index/relationship/#{args[0]}/#{args[1]}/#{get_id(args[2])}") - when 2 then delete("/index/relationship/#{args[0]}/#{get_id(args[1])}") - end + def remove_node_from_index(*args) + case args.size + when 4 then @node_indexes.remove_by_value(args[0], args[3], args[1], args[2]) + when 3 then @node_indexes.remove_by_key(args[0], args[2], args[1]) + when 2 then @node_indexes.remove(args[0], args[1]) end + end + alias_method :remove_from_index, :remove_node_from_index - def get_relationship_index(index, key, value) - index = get("/index/relationship/#{index}/#{key}/#{value}") || Array.new - return nil if index.empty? - index - end + def get_node_index(index, key, value) + @node_indexes.get(index, key, value) + end + alias_method :get_index, :get_node_index - def find_relationship_index(*args) - case args.size - when 3 then index = get("/index/relationship/#{args[0]}/#{args[1]}?query=#{args[2]}") || Array.new - when 2 then index = get("/index/relationship/#{args[0]}?query=#{args[1]}") || Array.new - end - return nil if index.empty? - index + def find_node_index(*args) + case args.size + when 3 then index = @node_indexes.find_by_value(args[0], args[1], args[2]) + when 2 then index = @node_indexes.find_by_query(args[0], args[1]) end + return nil if index.empty? + index + end - def get_relationship_auto_index(key, value) - index = get("/index/auto/relationship/#{key}/#{value}") || Array.new - return nil if index.empty? - index - end + # auto node indexes - def find_relationship_auto_index(*args) - case args.size - when 2 then index = get("/index/auto/relationship/#{args[0]}/#{args[1]}") || Array.new - when 1 then index = get("/index/auto/relationship/?query=#{args[0]}") || Array.new - end - return nil if index.empty? - index - end + def get_node_auto_index(key, value) + @node_auto_indexes.get(key, value) + end - def get_node_auto_index_status - get("/index/auto/node/status") + def find_node_auto_index(*args) + case args.size + when 2 then index = @node_auto_indexes.find(args[0], args[1]) + when 1 then index = @node_auto_indexes.query(args[0]) end + return nil if index.empty? + index + end - def get_relationship_auto_index_status - get("/index/auto/relationship/status") - end + def get_node_auto_index_status + @node_auto_indexes.status + end - def set_node_auto_index_status(change_to = true) - options = { :body => change_to.to_json, :headers => {'Content-Type' => 'application/json'} } - put("/index/auto/node/status", options) - end + def set_node_auto_index_status(change_to = true) + @node_auto_indexes.status = change_to + end - def set_relationship_auto_index_status(change_to = true) - options = { :body => change_to.to_json, :headers => {'Content-Type' => 'application/json'} } - put("/index/auto/relationship/status", options) - end + def get_node_auto_index_properties + @node_auto_indexes.properties + end - def get_node_auto_index_properties - get("/index/auto/node/properties") - end + def add_node_auto_index_property(property) + @node_auto_indexes.add_property(property) + end - def get_relationship_auto_index_properties - get("/index/auto/relationship/properties") - end + def remove_node_auto_index_property(property) + @node_auto_indexes.remove_property(property) + end - def add_node_auto_index_property(property) - options = { :body => property, :headers => {'Content-Type' => 'application/json'} } - post("/index/auto/node/properties", options) - end + # relationship indexes - def remove_node_auto_index_property(property) - delete("/index/auto/node/properties/#{property}") - end + def list_relationship_indexes + @relationship_indexes.list + end - def add_relationship_auto_index_property(property) - options = { :body => property, :headers => {'Content-Type' => 'application/json'} } - post("/index/auto/relationship/properties", options) - end + def create_relationship_index(name, type = "exact", provider = "lucene") + @relationship_indexes.create(name, type, provider) + end - def remove_relationship_auto_index_property(property) - delete("/index/auto/relationship/properties/#{property}") - end + def create_relationship_auto_index(type = "exact", provider = "lucene") + @relationship_indexes.create_auto(type, provider) + end - def traverse(id, return_type, description) - options = { :body => {"order" => get_order(description["order"]), - "uniqueness" => get_uniqueness(description["uniqueness"]), - "relationships" => description["relationships"], - "prune_evaluator" => description["prune evaluator"], - "return_filter" => description["return filter"], - "max_depth" => get_depth(description["depth"]), }.to_json, :headers => {'Content-Type' => 'application/json'} } - traversal = post("/node/#{get_id(id)}/traverse/#{get_type(return_type)}", options) || Array.new - end + def create_unique_relationship(index, key, value, type, from, to) + @relationship_indexes.create_unique(index, key, value, type, from, to) + end - def get_path(from, to, relationships, depth=1, algorithm="shortestPath") - options = { :body => {"to" => self.configuration + "/node/#{get_id(to)}", "relationships" => relationships, "max_depth" => depth, "algorithm" => get_algorithm(algorithm) }.to_json, :headers => {'Content-Type' => 'application/json'} } - path = post("/node/#{get_id(from)}/path", options) || Hash.new - end + def add_relationship_to_index(index, key, value, id) + @relationship_indexes.add(index, key, value, id) + end - def get_paths(from, to, relationships, depth=1, algorithm="allPaths") - options = { :body => {"to" => self.configuration + "/node/#{get_id(to)}", "relationships" => relationships, "max_depth" => depth, "algorithm" => get_algorithm(algorithm) }.to_json, :headers => {'Content-Type' => 'application/json'} } - paths = post("/node/#{get_id(from)}/paths", options) || Array.new - end - - def get_shortest_weighted_path(from, to, relationships, weight_attr='weight', depth=1, algorithm="dijkstra") - options = { :body => {"to" => self.configuration + "/node/#{get_id(to)}", "relationships" => relationships, "cost_property" => weight_attr, "max_depth" => depth, "algorithm" => get_algorithm(algorithm) }.to_json, :headers => {'Content-Type' => 'application/json'} } - paths = post("/node/#{get_id(from)}/paths", options) || Hash.new + def remove_relationship_from_index(*args) + case args.size + when 4 then @relationship_indexes.remove_by_value(args[0], get_id(args[3]), args[1], args[2]) + when 3 then @relationship_indexes.remove_by_key(args[0], get_id(args[2]), args[1]) + when 2 then @relationship_indexes.remove(args[0], get_id(args[1])) end + end - def execute_query(query, params = {}) - options = { :body => {:query => query, :params => params}.to_json, :headers => {'Content-Type' => 'application/json', 'Accept' => 'application/json;stream=true'} } - result = post(@cypher_path, options) - end - - def execute_script(script, params = {}) - options = { :body => {:script => script, :params => params}.to_json , :headers => {'Content-Type' => 'application/json'} } - result = post(@gremlin_path, options) - result == "null" ? nil : result - end + def get_relationship_index(index, key, value) + @relationship_indexes.get(index, key, value) + end - def batch(*args) - batch = [] - Array(args).each_with_index do |c,i| - batch << {:id => i}.merge(get_batch(c)) - end - options = { :body => batch.to_json, :headers => {'Content-Type' => 'application/json', 'Accept' => 'application/json;stream=true'} } - post("/batch", options) - end - - def batch_not_streaming(*args) - batch = [] - Array(args).each_with_index do |c,i| - batch << {:id => i}.merge(get_batch(c)) - end - options = { :body => batch.to_json, :headers => {'Content-Type' => 'application/json'} } - post("/batch", options) - end - - # For testing (use a separate neo4j instance) - # call this before each test or spec - def clean_database(sanity_check = "not_really") - if sanity_check == "yes_i_really_want_to_clean_the_database" - delete("/cleandb/secret-key") - true - else - false - end + def find_relationship_index(*args) + case args.size + when 3 then index = @relationship_indexes.find_by_key_query(args[0], args[1], args[2]) + when 2 then index = @relationship_indexes.find_by_query(args[0], args[1]) end + return nil if index.empty? + index + end - def merge_options(options) - merged_options = options.merge!(@authentication).merge!(@parser) - merged_options[:headers].merge!(@user_agent) if merged_options[:headers] - merged_options - end - - private - - def get_batch(args) - case args[0] - when :get_node - {:method => "GET", :to => "/node/#{get_id(args[1])}"} - when :create_node - {:method => "POST", :to => "/node/", :body => args[1]} - when :create_unique_node - {:method => "POST", :to => "/index/node/#{args[1]}?unique", :body => {:key => args[2], :value => args[3], :properties => args[4]}} - when :set_node_property - {:method => "PUT", :to => "/node/#{get_id(args[1])}/properties/#{args[2].keys.first}", :body => args[2].values.first} - when :reset_node_properties - {:method => "PUT", :to => "/node/#{get_id(args[1])}/properties", :body => args[2]} - when :get_relationship - {:method => "GET", :to => "/relationship/#{get_id(args[1])}"} - when :create_relationship - {:method => "POST", :to => (args[2].is_a?(String) && args[2].start_with?("{") ? "" : "/node/") + "#{get_id(args[2])}/relationships", :body => {:to => (args[3].is_a?(String) && args[3].start_with?("{") ? "" : "/node/") + "#{get_id(args[3])}", :type => args[1], :data => args[4] } } - when :create_unique_relationship - {:method => "POST", :to => "/index/relationship/#{args[1]}?unique", :body => {:key => args[2], :value => args[3], :type => args[4], :start => (args[5].is_a?(String) && args[5].start_with?("{") ? "" : "/node/") + "#{get_id(args[5])}", :end=> (args[6].is_a?(String) && args[6].start_with?("{") ? "" : "/node/") + "#{get_id(args[6])}"} } - when :delete_relationship - {:method => "DELETE", :to => "/relationship/#{get_id(args[1])}"} - when :set_relationship_property - {:method => "PUT", :to => "/relationship/#{get_id(args[1])}/properties/#{args[2].keys.first}", :body => args[2].values.first} - when :reset_relationship_properties - {:method => "PUT", :to => (args[1].is_a?(String) && args[1].start_with?("{") ? "" : "/relationship/") + "#{get_id(args[1])}/properties", :body => args[2]} - when :add_node_to_index - {:method => "POST", :to => "/index/node/#{args[1]}", :body => {:uri => (args[4].is_a?(String) && args[4].start_with?("{") ? "" : "/node/") + "#{get_id(args[4])}", :key => args[2], :value => args[3] } } - when :add_relationship_to_index - {:method => "POST", :to => "/index/relationship/#{args[1]}", :body => {:uri => (args[4].is_a?(String) && args[4].start_with?("{") ? "" : "/relationship/") + "#{get_id(args[4])}", :key => args[2], :value => args[3] } } - when :get_node_index - {:method => "GET", :to => "/index/node/#{args[1]}/#{args[2]}/#{args[3]}"} - when :get_relationship_index - {:method => "GET", :to => "/index/relationship/#{args[1]}/#{args[2]}/#{args[3]}"} - when :get_node_relationships - {:method => "GET", :to => "/node/#{get_id(args[1])}/relationships/#{args[2] || 'all'}"} - when :execute_script - {:method => "POST", :to => @gremlin_path, :body => {:script => args[1], :params => args[2]}} - when :execute_query - if args[2] - {:method => "POST", :to => @cypher_path, :body => {:query => args[1], :params => args[2]}} - else - {:method => "POST", :to => @cypher_path, :body => {:query => args[1]}} - end - when :remove_node_from_index - case args.size - when 5 then {:method => "DELETE", :to => "/index/node/#{args[1]}/#{args[2]}/#{args[3]}/#{get_id(args[4])}" } - when 4 then {:method => "DELETE", :to => "/index/node/#{args[1]}/#{args[2]}/#{get_id(args[3])}" } - when 3 then {:method => "DELETE", :to => "/index/node/#{args[1]}/#{get_id(args[2])}" } - end - when :remove_relationship_from_index - case args.size - when 5 then {:method => "DELETE", :to => "/index/relationship/#{args[1]}/#{args[2]}/#{args[3]}/#{get_id(args[4])}" } - when 4 then {:method => "DELETE", :to => "/index/relationship/#{args[1]}/#{args[2]}/#{get_id(args[3])}" } - when 3 then {:method => "DELETE", :to => "/index/relationship/#{args[1]}/#{get_id(args[2])}" } - end - when :delete_node - {:method => "DELETE", :to => "/node/#{get_id(args[1])}"} - else - raise "Unknown option #{args[0]}" - end - end + # relationship auto indexes - def evaluate_response(response) - code = response.code - body = response.body - case code - when 200 - @logger.debug "OK" if @log_enabled - response.parsed_response - when 201 - @logger.debug "OK, created #{body}" if @log_enabled - response.parsed_response - when 204 - @logger.debug "OK, no content returned" if @log_enabled - nil - when 400 - @logger.error "Invalid data sent #{body}" if @log_enabled - nil - when 404 - @logger.error "Not Found #{body}" if @log_enabled - nil - when 409 - @logger.error "Node could not be deleted (still has relationships?)" if @log_enabled - nil - end - end + def get_relationship_auto_index(key, value) + @relationship_auto_indexes.get(key, value) + end - def get(path,options={}) - evaluate_response(HTTParty.get(configuration + URI.encode(path), merge_options(options))) - end - - def post(path,options={}) - evaluate_response(HTTParty.post(configuration + URI.encode(path), merge_options(options))) - end - - def put(path,options={}) - evaluate_response(HTTParty.put(configuration + URI.encode(path), merge_options(options))) - end - - def delete(path,options={}) - evaluate_response(HTTParty.delete(configuration + URI.encode(path), merge_options(options))) - end - - def get_id(id) - case id - when Array - get_id(id.first) - when Hash - id["self"].split('/').last - when String - id.split('/').last - when Neography::Node, Neography::Relationship - id.neo_id - else - id - end + def find_relationship_auto_index(*args) + case args.size + when 2 then index = @relationship_auto_indexes.find(args[0], args[1]) + when 1 then index = @relationship_auto_indexes.query(args[0]) end + return nil if index.empty? + index + end - def get_dir(dir) - case dir - when :incoming, "incoming", :in, "in" - "in" - when :outgoing, "outgoing", :out, "out" - "out" - else - "all" - end - end + def get_relationship_auto_index_status + @relationship_auto_indexes.status + end - def get_algorithm(algorithm) - case algorithm - when :shortest, "shortest", :shortestPath, "shortestPath", :short, "short" - "shortestPath" - when :allSimplePaths, "allSimplePaths", :simple, "simple" - "allSimplePaths" - when :dijkstra, "dijkstra" - "dijkstra" - else - "allPaths" - end - end + def set_relationship_auto_index_status(change_to = true) + @relationship_auto_indexes.status = change_to + end - def get_order(order) - case order - when :breadth, "breadth", "breadth first", "breadthFirst", :wide, "wide" - "breadth first" - else - "depth first" - end - end + def get_relationship_auto_index_properties + @relationship_auto_indexes.properties + end - def get_type(type) - case type - when :relationship, "relationship", :relationships, "relationships" - "relationship" - when :path, "path", :paths, "paths" - "path" - when :fullpath, "fullpath", :fullpaths, "fullpaths" - "fullpath" - else - "node" - end - end + def add_relationship_auto_index_property(property) + @relationship_auto_indexes.add_property(property) + end - def get_uniqueness(uniqueness) - case uniqueness - when :nodeglobal, "node global", "nodeglobal", "node_global" - "node global" - when :nodepath, "node path", "nodepath", "node_path" - "node path" - when :noderecent, "node recent", "noderecent", "node_recent" - "node recent" - when :relationshipglobal, "relationship global", "relationshipglobal", "relationship_global" - "relationship global" - when :relationshippath, "relationship path", "relationshippath", "relationship_path" - "relationship path" - when :relationshiprecent, "relationship recent", "relationshiprecent", "relationship_recent" - "relationship recent" - else - "none" - end - end + def remove_relationship_auto_index_property(property) + @relationship_auto_indexes.remove_property(property) + end + + # traversal + + def traverse(id, return_type, description) + @node_traversal.traverse(id, return_type, description) + end + + # paths + + def get_path(from, to, relationships, depth = 1, algorithm = "shortestPath") + @node_paths.get(from, to, relationships, depth, algorithm) + end + + def get_paths(from, to, relationships, depth = 1, algorithm = "allPaths") + @node_paths.get_all(from, to, relationships, depth, algorithm) + end + + def get_shortest_weighted_path(from, to, relationships, weight_attr = "weight", depth = 1, algorithm = "dijkstra") + @node_paths.shortest_weighted(from, to, relationships, weight_attr, depth, algorithm) + end + + # cypher query + + def execute_query(query, params = {}) + @cypher.query(query, params) + end + + # gremlin script + + def execute_script(script, params = {}) + @gremlin.execute(script, params) + end + + # batch + + def batch(*args) + @batch.execute(*args) + end + + def batch_not_streaming(*args) + @batch.not_streaming(*args) + end + + # clean database - def get_depth(depth) - return nil if depth.nil? - return 1 if depth.to_i == 0 - depth.to_i + # For testing (use a separate neo4j instance) + # call this before each test or spec + def clean_database(sanity_check = "not_really") + if sanity_check == "yes_i_really_want_to_clean_the_database" + @clean.execute + true + else + false end + end end end diff --git a/lib/neography/rest/batch.rb b/lib/neography/rest/batch.rb new file mode 100644 index 0000000..445ad0e --- /dev/null +++ b/lib/neography/rest/batch.rb @@ -0,0 +1,99 @@ +module Neography + class Rest + class Batch + include Neography::Rest::Paths + include Neography::Rest::Helpers + + add_path :batch, "/batch" + + def initialize(connection) + @connection = connection + end + + def execute(*args) + batch({'Accept' => 'application/json;stream=true'}, *args) + end + + def not_streaming(*args) + batch({}, *args) + end + + private + + def batch(accept_header, *args) + batch = [] + Array(args).each_with_index do |c,i| + batch << {:id => i }.merge(get_batch(c)) + end + options = { + :body => batch.to_json, + :headers => json_content_type.merge(accept_header) + } + + @connection.post(batch_path, options) + end + + def get_batch(args) + case args[0] + when :get_node + {:method => "GET", :to => "/node/#{get_id(args[1])}"} + when :create_node + {:method => "POST", :to => "/node/", :body => args[1]} + when :create_unique_node + {:method => "POST", :to => "/index/node/#{args[1]}?unique", :body => {:key => args[2], :value => args[3], :properties => args[4]}} + when :set_node_property + {:method => "PUT", :to => "/node/#{get_id(args[1])}/properties/#{args[2].keys.first}", :body => args[2].values.first} + when :reset_node_properties + {:method => "PUT", :to => "/node/#{get_id(args[1])}/properties", :body => args[2]} + when :get_relationship + {:method => "GET", :to => "/relationship/#{get_id(args[1])}"} + when :create_relationship + {:method => "POST", :to => (args[2].is_a?(String) && args[2].start_with?("{") ? "" : "/node/") + "#{get_id(args[2])}/relationships", :body => {:to => (args[3].is_a?(String) && args[3].start_with?("{") ? "" : "/node/") + "#{get_id(args[3])}", :type => args[1], :data => args[4] } } + when :create_unique_relationship + {:method => "POST", :to => "/index/relationship/#{args[1]}?unique", :body => {:key => args[2], :value => args[3], :type => args[4], :start => (args[5].is_a?(String) && args[5].start_with?("{") ? "" : "/node/") + "#{get_id(args[5])}", :end=> (args[6].is_a?(String) && args[6].start_with?("{") ? "" : "/node/") + "#{get_id(args[6])}"} } + when :delete_relationship + {:method => "DELETE", :to => "/relationship/#{get_id(args[1])}"} + when :set_relationship_property + {:method => "PUT", :to => "/relationship/#{get_id(args[1])}/properties/#{args[2].keys.first}", :body => args[2].values.first} + when :reset_relationship_properties + {:method => "PUT", :to => (args[1].is_a?(String) && args[1].start_with?("{") ? "" : "/relationship/") + "#{get_id(args[1])}/properties", :body => args[2]} + when :add_node_to_index + {:method => "POST", :to => "/index/node/#{args[1]}", :body => {:uri => (args[4].is_a?(String) && args[4].start_with?("{") ? "" : "/node/") + "#{get_id(args[4])}", :key => args[2], :value => args[3] } } + when :add_relationship_to_index + {:method => "POST", :to => "/index/relationship/#{args[1]}", :body => {:uri => (args[4].is_a?(String) && args[4].start_with?("{") ? "" : "/relationship/") + "#{get_id(args[4])}", :key => args[2], :value => args[3] } } + when :get_node_index + {:method => "GET", :to => "/index/node/#{args[1]}/#{args[2]}/#{args[3]}"} + when :get_relationship_index + {:method => "GET", :to => "/index/relationship/#{args[1]}/#{args[2]}/#{args[3]}"} + when :get_node_relationships + {:method => "GET", :to => "/node/#{get_id(args[1])}/relationships/#{args[2] || 'all'}"} + when :execute_script + {:method => "POST", :to => @connection.gremlin_path, :body => {:script => args[1], :params => args[2]}} + when :execute_query + if args[2] + {:method => "POST", :to => @connection.cypher_path, :body => {:query => args[1], :params => args[2]}} + else + {:method => "POST", :to => @connection.cypher_path, :body => {:query => args[1]}} + end + when :remove_node_from_index + case args.size + when 5 then {:method => "DELETE", :to => "/index/node/#{args[1]}/#{args[2]}/#{args[3]}/#{get_id(args[4])}" } + when 4 then {:method => "DELETE", :to => "/index/node/#{args[1]}/#{args[2]}/#{get_id(args[3])}" } + when 3 then {:method => "DELETE", :to => "/index/node/#{args[1]}/#{get_id(args[2])}" } + end + when :remove_relationship_from_index + case args.size + when 5 then {:method => "DELETE", :to => "/index/relationship/#{args[1]}/#{args[2]}/#{args[3]}/#{get_id(args[4])}" } + when 4 then {:method => "DELETE", :to => "/index/relationship/#{args[1]}/#{args[2]}/#{get_id(args[3])}" } + when 3 then {:method => "DELETE", :to => "/index/relationship/#{args[1]}/#{get_id(args[2])}" } + end + when :delete_node + {:method => "DELETE", :to => "/node/#{get_id(args[1])}"} + else + raise "Unknown option #{args[0]}" + end + end + + end + end +end diff --git a/lib/neography/rest/clean.rb b/lib/neography/rest/clean.rb new file mode 100644 index 0000000..d0ea00c --- /dev/null +++ b/lib/neography/rest/clean.rb @@ -0,0 +1,19 @@ +module Neography + class Rest + class Clean + include Neography::Rest::Paths + include Neography::Rest::Helpers + + add_path :clean, "/cleandb/secret-key" + + def initialize(connection) + @connection = connection + end + + def execute + @connection.delete(clean_path) + end + + end + end +end diff --git a/lib/neography/rest/cypher.rb b/lib/neography/rest/cypher.rb new file mode 100644 index 0000000..c959295 --- /dev/null +++ b/lib/neography/rest/cypher.rb @@ -0,0 +1,24 @@ +module Neography + class Rest + class Cypher + include Neography::Rest::Helpers + + def initialize(connection) + @connection = connection + end + + def query(query, parameters) + options = { + :body => { + :query => query, + :params => parameters + }.to_json, + :headers => json_content_type.merge({'Accept' => 'application/json;stream=true'}) + } + + @connection.post(@connection.cypher_path, options) + end + + end + end +end diff --git a/lib/neography/rest/gremlin.rb b/lib/neography/rest/gremlin.rb new file mode 100644 index 0000000..d109123 --- /dev/null +++ b/lib/neography/rest/gremlin.rb @@ -0,0 +1,24 @@ +module Neography + class Rest + class Gremlin + include Neography::Rest::Helpers + + def initialize(connection) + @connection = connection + end + + def execute(script, parameters) + options = { + :body => { + :script => script, + :params => parameters, + }.to_json, + :headers => json_content_type + } + result = @connection.post(@connection.gremlin_path, options) + result == "null" ? nil : result + end + + end + end +end diff --git a/lib/neography/rest/helpers.rb b/lib/neography/rest/helpers.rb new file mode 100644 index 0000000..cf4b398 --- /dev/null +++ b/lib/neography/rest/helpers.rb @@ -0,0 +1,26 @@ +module Neography + class Rest + module Helpers + + def get_id(id) + case id + when Array + get_id(id.first) + when Hash + id["self"].split('/').last + when String + id.split('/').last + when Neography::Node, Neography::Relationship + id.neo_id + else + id + end + end + + def json_content_type + {'Content-Type' => 'application/json'} + end + + end + end +end diff --git a/lib/neography/rest/node_auto_indexes.rb b/lib/neography/rest/node_auto_indexes.rb new file mode 100644 index 0000000..ae752da --- /dev/null +++ b/lib/neography/rest/node_auto_indexes.rb @@ -0,0 +1,61 @@ +module Neography + class Rest + class NodeAutoIndexes + include Neography::Rest::Paths + include Neography::Rest::Helpers + + add_path :key_value, "/index/auto/node/:key/:value" + add_path :query_index, "/index/auto/node/?query=:query" + add_path :index_status, "/index/auto/node/status" + add_path :index_properties, "/index/auto/node/properties" + add_path :index_property, "/index/auto/node/properties/:property" + + def initialize(connection) + @connection = connection + end + + def get(key, value) + index = @connection.get(key_value_path(:key => key, :value => value)) || [] + return nil if index.empty? + index + end + + def find(key, value) + @connection.get(key_value_path(:key => key, :value => value)) || [] + end + + def query(query_expression) + @connection.get(query_index_path(:query => query_expression)) || [] + end + + def status + @connection.get(index_status_path) + end + + def status=(value) + options = { + :body => value.to_json, + :headers => json_content_type + } + @connection.put(index_status_path, options) + end + + def properties + @connection.get(index_properties_path) + end + + def add_property(property) + options = { + :body => property, + :headers => json_content_type + } + @connection.post(index_properties_path, options) + end + + def remove_property(property) + @connection.delete(index_property_path(:property => property)) + end + + end + end +end diff --git a/lib/neography/rest/node_indexes.rb b/lib/neography/rest/node_indexes.rb new file mode 100644 index 0000000..ce8e8fd --- /dev/null +++ b/lib/neography/rest/node_indexes.rb @@ -0,0 +1,98 @@ +module Neography + class Rest + class NodeIndexes + include Neography::Rest::Paths + include Neography::Rest::Helpers + + add_path :all, "/index/node" + add_path :base, "/index/node/:index" + add_path :unique, "/index/node/:index?unique" + add_path :node, "/index/node/:index/:id" + add_path :key, "/index/node/:index/:key/:id" + add_path :value, "/index/node/:index/:key/:value/:id" + add_path :key_value, "/index/node/:index/:key/:value" + add_path :query, "/index/node/:index?query=:query" + + def initialize(connection) + @connection = connection + end + + def list + @connection.get(all_path) + end + + def create(name, type, provider) + options = { + :body => ( + { :name => name, + :config => { + :type => type, + :provider => provider + } + } + ).to_json, + :headers => json_content_type + } + @connection.post(all_path, options) + end + + def create_auto(type, provider) + create("node_auto_index", type, provider) + end + + def create_unique(index, key, value, properties) + options = { + :body => ( + { :properties => properties, + :key => key, + :value => value + } + ).to_json, + :headers => json_content_type + } + @connection.post(unique_path(:index => index), options) + end + + def add(index, key, value, id) + options = { + :body => ( + { :uri => @connection.configuration + "/node/#{get_id(id)}", + :key => key, + :value => value + } + ).to_json, + :headers => json_content_type + } + @connection.post(base_path(:index => index), options) + end + + def get(index, key, value) + index = @connection.get(key_value_path(:index => index, :key => key, :value => value)) || [] + return nil if index.empty? + index + end + + # TODO FIX BUG %20 + def find_by_value(index, key, value) + @connection.get(key_value_path(:index => index, :key => key, :value => value)) || [] + end + + def find_by_query(index, query) + @connection.get(query_path(:index => index, :query => query)) || [] + end + + def remove(index, id) + @connection.delete(node_path(:index => index, :id => get_id(id))) + end + + def remove_by_key(index, id, key) + @connection.delete(key_path(:index => index, :id => get_id(id), :key => key)) + end + + def remove_by_value(index, id, key, value) + @connection.delete(value_path(:index => index, :id => get_id(id), :key => key, :value => value)) + end + + end + end +end diff --git a/lib/neography/rest/node_paths.rb b/lib/neography/rest/node_paths.rb new file mode 100644 index 0000000..1b0cb47 --- /dev/null +++ b/lib/neography/rest/node_paths.rb @@ -0,0 +1,68 @@ +module Neography + class Rest + class NodePaths + include Neography::Rest::Paths + include Neography::Rest::Helpers + + add_path :base, "/node/:id/path" + add_path :all, "/node/:id/paths" + + def initialize(connection) + @connection = connection + end + + def get(from, to, relationships, depth, algorithm) + options = { :body => { + "to" => @connection.configuration + "/node/#{get_id(to)}", + "relationships" => relationships, + "max_depth" => depth, + "algorithm" => get_algorithm(algorithm) + }.to_json, + :headers => json_content_type + } + @connection.post(base_path(:id => get_id(from)), options) || {} + end + + def get_all(from, to, relationships, depth, algorithm) + options = { :body => { + "to" => @connection.configuration + "/node/#{get_id(to)}", + "relationships" => relationships, + "max_depth" => depth, + "algorithm" => get_algorithm(algorithm) + }.to_json, + :headers => json_content_type + } + @connection.post(all_path(:id => get_id(from)), options) || [] + end + + def shortest_weighted(from, to, relationships, weight_attribute, depth, algorithm) + options = { :body => { + "to" => @connection.configuration + "/node/#{get_id(to)}", + "relationships" => relationships, + "cost_property" => weight_attribute, + "max_depth" => depth, + "algorithm" => get_algorithm(algorithm) + }.to_json, + :headers => json_content_type + } + @connection.post(all_path(:id => get_id(from)), options) || {} + end + + private + + def get_algorithm(algorithm) + case algorithm + when :shortest, "shortest", :shortestPath, "shortestPath", :short, "short" + "shortestPath" + when :allSimplePaths, "allSimplePaths", :simple, "simple" + "allSimplePaths" + when :dijkstra, "dijkstra" + "dijkstra" + else + "allPaths" + end + end + + end + end +end diff --git a/lib/neography/rest/node_properties.rb b/lib/neography/rest/node_properties.rb new file mode 100644 index 0000000..29f3f2a --- /dev/null +++ b/lib/neography/rest/node_properties.rb @@ -0,0 +1,60 @@ +module Neography + class Rest + class NodeProperties + include Neography::Rest::Paths + include Neography::Rest::Helpers + + add_path :all, "/node/:id/properties" + add_path :single, "/node/:id/properties/:property" + + def initialize(connection) + @connection = connection + end + + def set(id, properties) + properties.each do |property, value| + options = { :body => value.to_json, :headers => json_content_type } + @connection.put(single_path(:id => get_id(id), :property => property), options) + end + end + + def reset(id, properties) + options = { :body => properties.to_json, :headers => json_content_type } + @connection.put(all_path(:id => get_id(id)), options) + end + + def get(id, *properties) + if properties.none? + @connection.get(all_path(:id => get_id(id))) + else + get_each(id, *properties) + end + end + + def get_each(id, *properties) + node_properties = properties.inject({}) do |memo, property| + value = @connection.get(single_path(:id => get_id(id), :property => property)) + memo[property] = value unless value.nil? + memo + end + return nil if node_properties.empty? + node_properties + end + + def remove(id, *properties) + if properties.none? + @connection.delete(all_path(:id => get_id(id))) + else + remove_each(id, *properties) + end + end + + def remove_each(id, *properties) + properties.each do |property| + @connection.delete(single_path(:id => get_id(id), :property => property)) + end + end + + end + end +end diff --git a/lib/neography/rest/node_relationships.rb b/lib/neography/rest/node_relationships.rb new file mode 100644 index 0000000..c9ce528 --- /dev/null +++ b/lib/neography/rest/node_relationships.rb @@ -0,0 +1,53 @@ +module Neography + class Rest + class NodeRelationships + include Neography::Rest::Paths + include Neography::Rest::Helpers + + add_path :base, "/node/:id/relationships" + add_path :direction, "/node/:id/relationships/:direction" + add_path :type, "/node/:id/relationships/:direction/:types" + + def initialize(connection) + @connection = connection + end + + def create(type, from, to, properties) + options = { + :body => { + :to => @connection.configuration + "/node/#{get_id(to)}", + :data => properties, + :type => type + }.to_json, + :headers => json_content_type } + + @connection.post(base_path(:id => get_id(from)), options) + end + + def get(id, direction = nil, types = nil) + direction = parse_direction(direction) + + if types.nil? + node_relationships = @connection.get(direction_path(:id => get_id(id), :direction => direction)) || [] + else + node_relationships = @connection.get(type_path(:id => get_id(id), :direction => direction, :types => Array(types).join('&'))) || [] + end + + return nil if node_relationships.empty? + node_relationships + end + + def parse_direction(direction) + case direction + when :incoming, "incoming", :in, "in" + "in" + when :outgoing, "outgoing", :out, "out" + "out" + else + "all" + end + end + + end + end +end diff --git a/lib/neography/rest/node_traversal.rb b/lib/neography/rest/node_traversal.rb new file mode 100644 index 0000000..b6d6946 --- /dev/null +++ b/lib/neography/rest/node_traversal.rb @@ -0,0 +1,81 @@ +module Neography + class Rest + class NodeTraversal + include Neography::Rest::Paths + include Neography::Rest::Helpers + + add_path :traversal, "/node/:id/traverse/:type" + + def initialize(connection) + @connection = connection + end + + def traverse(id, return_type, description) + options = { :body => { + "order" => get_order(description["order"]), + "uniqueness" => get_uniqueness(description["uniqueness"]), + "relationships" => description["relationships"], + "prune_evaluator" => description["prune evaluator"], + "return_filter" => description["return filter"], + "max_depth" => get_depth(description["depth"]) + }.to_json, + :headers => json_content_type + } + + type = get_type(return_type) + + @connection.post(traversal_path(:id => get_id(id), :type => type), options) || [] + end + + private + + def get_order(order) + case order + when :breadth, "breadth", "breadth first", "breadthFirst", :wide, "wide" + "breadth first" + else + "depth first" + end + end + + def get_uniqueness(uniqueness) + case uniqueness + when :nodeglobal, "node global", "nodeglobal", "node_global" + "node global" + when :nodepath, "node path", "nodepath", "node_path" + "node path" + when :noderecent, "node recent", "noderecent", "node_recent" + "node recent" + when :relationshipglobal, "relationship global", "relationshipglobal", "relationship_global" + "relationship global" + when :relationshippath, "relationship path", "relationshippath", "relationship_path" + "relationship path" + when :relationshiprecent, "relationship recent", "relationshiprecent", "relationship_recent" + "relationship recent" + else + "none" + end + end + + def get_depth(depth) + return nil if depth.nil? + return 1 if depth.to_i == 0 + depth.to_i + end + + def get_type(type) + case type + when :relationship, "relationship", :relationships, "relationships" + "relationship" + when :path, "path", :paths, "paths" + "path" + when :fullpath, "fullpath", :fullpaths, "fullpaths" + "fullpath" + else + "node" + end + end + + end + end +end diff --git a/lib/neography/rest/nodes.rb b/lib/neography/rest/nodes.rb new file mode 100644 index 0000000..9f2cacb --- /dev/null +++ b/lib/neography/rest/nodes.rb @@ -0,0 +1,102 @@ +module Neography + class Rest + class Nodes + include Neography::Rest::Paths + include Neography::Rest::Helpers + + add_path :index, "/node" + add_path :base, "/node/:id" + + def initialize(connection) + @connection = connection + end + + def get(id) + @connection.get(base_path(:id => get_id(id))) + end + + def get_each(*nodes) + gotten_nodes = [] + Array(nodes).flatten.each do |node| + gotten_nodes << get(node) + end + gotten_nodes + end + + def root + root_node = @connection.get('/')["reference_node"] + @connection.get(base_path(:id => get_id(root_node))) + end + + def create(*args) + if args[0].respond_to?(:each_pair) && args[0] + create_with_attributes(args[0]) + else + create_empty + end + end + + def create_with_attributes(attributes) + options = { + :body => attributes.delete_if { |k, v| v.nil? }.to_json, + :headers => json_content_type + } + @connection.post(index_path, options) + end + + def create_empty + @connection.post(index_path) + end + + def delete(id) + @connection.delete(base_path(:id => get_id(id))) + end + + def create_multiple(nodes) + nodes = Array.new(nodes) if nodes.kind_of? Fixnum + created_nodes = [] + nodes.each do |node| + created_nodes << create(node) + end + created_nodes + end + + def create_multiple_threaded(nodes) + nodes = Array.new(nodes) if nodes.kind_of? Fixnum + + node_queue = Queue.new + thread_pool = [] + responses = Queue.new + + nodes.each do |node| + node_queue.push node + end + + [nodes.size, @connection.max_threads].min.times do + thread_pool << Thread.new do + until node_queue.empty? do + node = node_queue.pop + if node.respond_to?(:each_pair) + responses.push( @connection.post(index_path, { + :body => node.to_json, + :headers => json_content_type + } ) ) + else + responses.push( @connection.post(index_path) ) + end + end + self.join + end + end + + created_nodes = [] + + while created_nodes.size < nodes.size + created_nodes << responses.pop + end + created_nodes + end + + end + end +end diff --git a/lib/neography/rest/paths.rb b/lib/neography/rest/paths.rb new file mode 100644 index 0000000..bd25b59 --- /dev/null +++ b/lib/neography/rest/paths.rb @@ -0,0 +1,33 @@ +module Neography + class Rest + module Paths + + def self.included(mod) + mod.send :extend, ClassMethods + end + + def build_path(path, attributes) + path.gsub(/:([\w_]*)/) do + encode(attributes[$1.to_sym].to_s) + end + end + + def encode(value) + URI.encode(value).gsub("/","%2F") + end + + module ClassMethods + def add_path(key, path) + define_method :"#{key}_path" do |*attributes| + if attributes.any? + build_path(path, *attributes) + else + path + end + end + end + end + + end + end +end diff --git a/lib/neography/rest/relationship_auto_indexes.rb b/lib/neography/rest/relationship_auto_indexes.rb new file mode 100644 index 0000000..7b282bd --- /dev/null +++ b/lib/neography/rest/relationship_auto_indexes.rb @@ -0,0 +1,61 @@ +module Neography + class Rest + class RelationshipAutoIndexes + include Neography::Rest::Paths + include Neography::Rest::Helpers + + add_path :key_value, "/index/auto/relationship/:key/:value" + add_path :query_index, "/index/auto/relationship/?query=:query" + add_path :index_status, "/index/auto/relationship/status" + add_path :index_properties, "/index/auto/relationship/properties" + add_path :index_property, "/index/auto/relationship/properties/:property" + + def initialize(connection) + @connection = connection + end + + def get(key, value) + index = @connection.get(key_value_path(:key => key, :value => value)) || [] + return nil if index.empty? + index + end + + def find(key, value) + @connection.get(key_value_path(:key => key, :value => value)) || [] + end + + def query(query_expression) + @connection.get(query_index_path(:query => query_expression)) || [] + end + + def status + @connection.get(index_status_path) + end + + def status=(value) + options = { + :body => value.to_json, + :headers => json_content_type + } + @connection.put(index_status_path, options) + end + + def properties + @connection.get(index_properties_path) + end + + def add_property(property) + options = { + :body => property, + :headers => json_content_type + } + @connection.post(index_properties_path, options) + end + + def remove_property(property) + @connection.delete(index_property_path(:property => property)) + end + + end + end +end diff --git a/lib/neography/rest/relationship_indexes.rb b/lib/neography/rest/relationship_indexes.rb new file mode 100644 index 0000000..97f578a --- /dev/null +++ b/lib/neography/rest/relationship_indexes.rb @@ -0,0 +1,101 @@ +module Neography + class Rest + class RelationshipIndexes + include Neography::Rest::Paths + include Neography::Rest::Helpers + + add_path :all, "/index/relationship" + add_path :base, "/index/relationship/:index" + add_path :unique, "/index/relationship/:index?unique" + + add_path :relationship, "/index/relationship/:index/:id" + add_path :key, "/index/relationship/:index/:key/:id" + add_path :value, "/index/relationship/:index/:key/:value/:id" + add_path :key_value, "/index/relationship/:index/:key/:value" + + add_path :key_query, "/index/relationship/:index/:key?query=:query" + add_path :query, "/index/relationship/:index?query=:query" + + def initialize(connection) + @connection = connection + end + + def list + @connection.get(all_path) + end + + def create(name, type, provider) + options = { + :body => ( + { :name => name, + :config => { + :type => type, + :provider => provider + } + } + ).to_json, + :headers => json_content_type + } + @connection.post(all_path, options) + end + + def create_auto(type, provider) + create("relationship_auto_index", type, provider) + end + + def create_unique(index, key, value, type, from, to) + body = { + :key => key, + :value => value, + :type => type, + :start => @connection.configuration + "/node/#{get_id(from)}", + :end => @connection.configuration + "/node/#{get_id(to)}" + } + options = { :body => body.to_json, :headers => json_content_type } + + @connection.post(unique_path(:index => index), options) + end + + def add(index, key, value, id) + options = { + :body => ( + { :uri => @connection.configuration + "/relationship/#{get_id(id)}", + :key => key, + :value => value + } + ).to_json, + :headers => json_content_type + } + + @connection.post(base_path(:index => index), options) + end + + def get(index, key, value) + index = @connection.get(key_value_path(:index => index, :key => key, :value => value)) || [] + return nil if index.empty? + index + end + + def find_by_key_query(index, key, query) + @connection.get(key_query_path(:index => index, :key => key, :query => query)) || [] + end + + def find_by_query(index, query) + @connection.get(query_path(:index => index, :query => query)) || [] + end + + def remove(index, id) + @connection.delete(relationship_path(:index => index, :id => get_id(id))) + end + + def remove_by_key(index, id, key) + @connection.delete(key_path(:index => index, :id => get_id(id), :key => key)) + end + + def remove_by_value(index, id, key, value) + @connection.delete(value_path(:index => index, :id => get_id(id), :key => key, :value => value)) + end + + end + end +end diff --git a/lib/neography/rest/relationship_properties.rb b/lib/neography/rest/relationship_properties.rb new file mode 100644 index 0000000..8f25d45 --- /dev/null +++ b/lib/neography/rest/relationship_properties.rb @@ -0,0 +1,60 @@ +module Neography + class Rest + class RelationshipProperties + include Neography::Rest::Paths + include Neography::Rest::Helpers + + add_path :all, "/relationship/:id/properties" + add_path :single, "/relationship/:id/properties/:property" + + def initialize(connection) + @connection = connection + end + + def set(id, properties) + properties.each do |key, value| + options = { :body => value.to_json, :headers => json_content_type } + @connection.put(single_path(:id => get_id(id), :property => key), options) + end + end + + def reset(id, properties) + options = { :body => properties.to_json, :headers => json_content_type } + @connection.put(all_path(:id => get_id(id)), options) + end + + def get(id, *properties) + if properties.none? + @connection.get(all_path(:id => get_id(id))) + else + get_each(id, *properties) + end + end + + def get_each(id, *properties) + relationship_properties = properties.inject({}) do |memo, property| + value = @connection.get(single_path(:id => get_id(id), :property => property)) + memo[property] = value unless value.nil? + memo + end + return nil if relationship_properties.empty? + relationship_properties + end + + def remove(id, *properties) + if properties.none? + @connection.delete(all_path(:id => get_id(id))) + else + remove_each(id, *properties) + end + end + + def remove_each(id, *properties) + properties.each do |property| + @connection.delete(single_path(:id => get_id(id), :property => property)) + end + end + + end + end +end diff --git a/lib/neography/rest/relationships.rb b/lib/neography/rest/relationships.rb new file mode 100644 index 0000000..43cfa17 --- /dev/null +++ b/lib/neography/rest/relationships.rb @@ -0,0 +1,23 @@ +module Neography + class Rest + class Relationships + include Neography::Rest::Paths + include Neography::Rest::Helpers + + add_path :base, "/relationship/:id" + + def initialize(connection) + @connection = connection + end + + def get(id) + @connection.get(base_path(:id => get_id(id))) + end + + def delete(id) + @connection.delete(base_path(:id => get_id(id))) + end + + end + end +end diff --git a/spec/integration/rest_header_spec.rb b/spec/integration/rest_header_spec.rb index 15b0eef..fadaffc 100644 --- a/spec/integration/rest_header_spec.rb +++ b/spec/integration/rest_header_spec.rb @@ -1,19 +1,14 @@ require File.join(File.dirname(__FILE__), '..', 'spec_helper') -describe Neography::Rest do - before(:each) do - @neo = Neography::Rest.new - end +describe Neography::Connection do it "should not add a content-type header if there's no existing headers" do - @neo.merge_options({}).keys.should == [:parser] + subject.merge_options({}).keys.should == [:parser] end it "should add a content type if there's existing headers" do - @neo.merge_options({:headers => {'Content-Type' => 'foo/bar'}})[:headers].should == + subject.merge_options({:headers => {'Content-Type' => 'foo/bar'}})[:headers].should == {'Content-Type' => "foo/bar", "User-Agent" => "Neography/#{Neography::VERSION}"} - end - end diff --git a/spec/integration/rest_index_spec.rb b/spec/integration/rest_index_spec.rb index 2688ddf..bbf8424 100644 --- a/spec/integration/rest_index_spec.rb +++ b/spec/integration/rest_index_spec.rb @@ -180,6 +180,26 @@ @neo.remove_node_from_index("test_node_index", key, value, new_node) end + it "can get a node index with a space in the value" do + new_node = @neo.create_node + key = generate_text(6) + value = generate_text + " " + generate_text + @neo.add_node_to_index("test_node_index", key, value, new_node) + new_index = @neo.get_node_index("test_node_index", key, value) + new_index.should_not be_nil + @neo.remove_node_from_index("test_node_index", key, value, new_node) + end + + it "can get a node index with a slash in the value" do + new_node = @neo.create_node + key = generate_text(6) + value = generate_text + "/" + generate_text + @neo.add_node_to_index("test_node_index", key, value, new_node) + new_index = @neo.get_node_index("test_node_index", key, value) + new_index.should_not be_nil + @neo.remove_node_from_index("test_node_index", key, value, new_node) + end + it "can get a relationship index" do new_node1 = @neo.create_node new_node2 = @neo.create_node diff --git a/spec/matchers.rb b/spec/matchers.rb new file mode 100644 index 0000000..27d9695 --- /dev/null +++ b/spec/matchers.rb @@ -0,0 +1,33 @@ +# Convenience matcher for matching JSON fields with a hash +RSpec::Matchers.define :json_match do |field, expected| + + match do |actual| + expected == JSON.parse(actual[field]) + end + + failure_message_for_should do + "expected JSON in field '#{field}' to match '#{expected}'" + end + + description do + "JSON in field '#{field}' should match '#{expected.inspect}'" + end + +end + +# Convenience matcher for matching fields in a hash +RSpec::Matchers.define :hash_match do |field, expected| + + match do |actual| + expected == actual[field] + end + + failure_message_for_should do + "expected field '#{field}' to match '#{expected}'" + end + + description do + "field '#{field}' should match '#{expected.inspect}'" + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 78437da..755d9fe 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,6 @@ require 'neography' require 'benchmark' +require 'matchers' # If you want to see more, uncomment the next few lines # require 'net-http-spy' @@ -15,4 +16,10 @@ def generate_text(length=8) RSpec.configure do |c| c.filter_run_excluding :slow => true, :break_gremlin => true -end \ No newline at end of file +end + + +def json_content_type + {"Content-Type"=>"application/json"} +end + diff --git a/spec/unit/rest/clean_spec.rb b/spec/unit/rest/clean_spec.rb new file mode 100644 index 0000000..f10a1f6 --- /dev/null +++ b/spec/unit/rest/clean_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +module Neography + class Rest + describe Clean do + + let(:connection) { stub } + subject { Clean.new(connection) } + + it "cleans the database" do + connection.should_receive(:delete).with("/cleandb/secret-key") + subject.execute + end + + end + end +end diff --git a/spec/unit/rest/cypher_spec.rb b/spec/unit/rest/cypher_spec.rb new file mode 100644 index 0000000..8b34b57 --- /dev/null +++ b/spec/unit/rest/cypher_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +module Neography + class Rest + describe Cypher do + + let(:connection) { stub(:cypher_path => "/cypher") } + subject { Cypher.new(connection) } + + it "executes a cypher query" do + options = { + :body=>"{\"query\":\"SOME QUERY\",\"params\":{\"foo\":\"bar\",\"baz\":\"qux\"}}", + :headers=>{"Content-Type"=>"application/json", "Accept"=>"application/json;stream=true"} + } + connection.should_receive(:post).with("/cypher", options) + subject.query("SOME QUERY", { :foo => "bar", :baz => "qux" }) + end + + end + end +end diff --git a/spec/unit/rest/gremlin_spec.rb b/spec/unit/rest/gremlin_spec.rb new file mode 100644 index 0000000..44b78dd --- /dev/null +++ b/spec/unit/rest/gremlin_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +module Neography + class Rest + describe Gremlin do + + let(:connection) { stub(:gremlin_path => "/gremlin") } + subject { Gremlin.new(connection) } + + it "executes a gremlin script" do + options = { + :body=>"{\"script\":\"SOME SCRIPT\",\"params\":{\"foo\":\"bar\",\"baz\":\"qux\"}}", + :headers=>{"Content-Type"=>"application/json"} + } + connection.should_receive(:post).with("/gremlin", options) + subject.execute("SOME SCRIPT", { :foo => "bar", :baz => "qux" }) + end + + it "returns nil if script result is null" do + connection.stub(:post).and_return("null") + subject.execute("", {}).should be_nil + end + + end + end +end diff --git a/spec/unit/rest/node_auto_indexes_spec.rb b/spec/unit/rest/node_auto_indexes_spec.rb new file mode 100644 index 0000000..8bd623e --- /dev/null +++ b/spec/unit/rest/node_auto_indexes_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +module Neography + class Rest + describe NodeAutoIndexes do + + let(:connection) { stub } + subject { NodeAutoIndexes.new(connection) } + + it "gets a node from an auto index" do + connection.should_receive(:get).with("/index/auto/node/some_key/some_value") + subject.get("some_key", "some_value") + end + + it "returns nil if nothing was found in the auto index" do + connection.stub(:get).and_return(nil) + subject.get("some_key", "some_value").should be_nil + end + + it "finds by key and value" do + connection.should_receive(:get).with("/index/auto/node/some_key/some_value") + subject.find("some_key", "some_value") + end + + it "finds by query" do + connection.should_receive(:get).with("/index/auto/node/?query=some_query") + subject.query("some_query") + end + + it "gets the status" do + connection.should_receive(:get).with("/index/auto/node/status") + subject.status + end + + it "sets the status" do + connection.should_receive(:put).with("/index/auto/node/status", hash_match(:body, '"foo"')) + subject.status = "foo" + end + + it "gets auto index properties" do + connection.should_receive(:get).with("/index/auto/node/properties") + subject.properties + end + + it "adds a property to an auto index" do + connection.should_receive(:post).with("/index/auto/node/properties", hash_match(:body, "foo")) + subject.add_property("foo") + end + + it "removes a property from an auto index" do + connection.should_receive(:delete).with("/index/auto/node/properties/foo") + subject.remove_property("foo") + end + + end + end +end diff --git a/spec/unit/rest/node_indexes_spec.rb b/spec/unit/rest/node_indexes_spec.rb new file mode 100644 index 0000000..10d5382 --- /dev/null +++ b/spec/unit/rest/node_indexes_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +module Neography + class Rest + describe NodeIndexes do + + let(:connection) { stub(:configuration => "http://configuration") } + subject { NodeIndexes.new(connection) } + + it "lists all indexes" do + connection.should_receive(:get).with("/index/node") + subject.list + end + + it "creates a node index" do + expected_body = { + "config" => { + "type" => "some_type", + "provider" => "some_provider" + }, + "name" => "some_index" + } + connection.should_receive(:post).with("/index/node", json_match(:body, expected_body)) + subject.create("some_index", "some_type", "some_provider") + end + + it "returns the post result after creation" do + connection.stub(:post).and_return("foo") + subject.create("some_index", "some_type", "some_provider").should == "foo" + end + + it "creates an auto-index" do + expected_body = { + "config" => { + "type" => "some_type", + "provider" => "some_provider" + }, + "name" => "node_auto_index" + } + connection.should_receive(:post).with("/index/node", json_match(:body, expected_body)) + subject.create_auto("some_type", "some_provider") + end + + it "creates a unique node in an index" do + expected_body = { + "properties" => "properties", + "key" => "key", + "value" => "value" + } + connection.should_receive(:post).with("/index/node/some_index?unique", json_match(:body, expected_body)) + subject.create_unique("some_index", "key", "value", "properties") + end + + it "adds a node to an index" do + expected_body = { + "uri" => "http://configuration/node/42", + "key" => "key", + "value" => "value" + } + connection.should_receive(:post).with("/index/node/some_index", json_match(:body, expected_body)) + subject.add("some_index", "key", "value", "42") + end + + it "gets a node from an index" do + connection.should_receive(:get).with("/index/node/some_index/some_key/some_value") + subject.get("some_index", "some_key", "some_value") + end + + it "returns nil if nothing was found in the index" do + connection.stub(:get).and_return(nil) + subject.get("some_index", "some_key", "some_value").should be_nil + end + + it "finds by key and value" do + connection.should_receive(:get).with("/index/node/some_index/some_key/some_value") + subject.find_by_value("some_index", "some_key", "some_value") + end + + it "finds by query" do + connection.should_receive(:get).with("/index/node/some_index?query=some_query") + subject.find_by_query("some_index", "some_query") + end + + it "removes a node from an index" do + connection.should_receive(:delete).with("/index/node/some_index/42") + subject.remove("some_index", "42") + end + + it "removes a node from an index by key" do + connection.should_receive(:delete).with("/index/node/some_index/some_key/42") + subject.remove_by_key("some_index", "42", "some_key") + end + + it "removes a node from an index by key and value" do + connection.should_receive(:delete).with("/index/node/some_index/some_key/some_value/42") + subject.remove_by_value("some_index", "42", "some_key", "some_value") + end + + end + end +end diff --git a/spec/unit/rest/node_paths_spec.rb b/spec/unit/rest/node_paths_spec.rb new file mode 100644 index 0000000..372509f --- /dev/null +++ b/spec/unit/rest/node_paths_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' + +module Neography + class Rest + describe NodePaths do + + let(:connection) { stub(:configuration => "http://configuration") } + subject { NodePaths.new(connection) } + + it "gets a shortest path between two nodes" do + expected_body = { + "to" => "http://configuration/node/43", + "relationships" => "relationships", + "max_depth" => 3, + "algorithm" => "shortestPath" + } + + connection.should_receive(:post).with("/node/42/path", json_match(:body, expected_body)) + + subject.get("42", "43", "relationships", 3, "shortestPath") + end + + it "gets all shortest paths between two nodes" do + expected_body = { + "to" => "http://configuration/node/43", + "relationships" => "relationships", + "max_depth" => 3, + "algorithm" => "shortestPath" + } + + connection.should_receive(:post).with("/node/42/paths", json_match(:body, expected_body)) + + subject.get_all("42", "43", "relationships", 3, "shortestPath") + end + + it "gets all shortest weighted paths between two nodes" do + expected_body = { + "to" => "http://configuration/node/43", + "relationships" => "relationships", + "cost_property" => "cost", + "max_depth" => 3, + "algorithm" => "shortestPath" + } + + connection.should_receive(:post).with("/node/42/paths", json_match(:body, expected_body)) + + subject.shortest_weighted("42", "43", "relationships", "cost", 3, "shortestPath") + end + + context "algorithm" do + + subject { NodePaths.new(nil) } + + [ :shortest, "shortest", :shortestPath, "shortestPath", :short, "short" ].each do |algorithm| + it "parses shortestPath" do + subject.send(:get_algorithm, algorithm).should == "shortestPath" + end + end + + [ :allSimplePaths, "allSimplePaths", :simple, "simple" ].each do |algorithm| + it "parses allSimplePaths" do + subject.send(:get_algorithm, algorithm).should == "allSimplePaths" + end + end + + [ :dijkstra, "dijkstra" ].each do |algorithm| + it "parses dijkstra" do + subject.send(:get_algorithm, algorithm).should == "dijkstra" + end + end + + it "parses allPaths by default" do + subject.send(:get_algorithm, "foo").should == "allPaths" + end + + end + + end + end +end diff --git a/spec/unit/rest/node_properties_spec.rb b/spec/unit/rest/node_properties_spec.rb new file mode 100644 index 0000000..19a3a97 --- /dev/null +++ b/spec/unit/rest/node_properties_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' + +module Neography + class Rest + describe NodeProperties do + + let(:connection) { stub } + subject { NodeProperties.new(connection) } + + it "sets properties" do + options1 = { + :body => '"bar"', + :headers => json_content_type + } + options2 = { + :body => '"qux"', + :headers => json_content_type + } + connection.should_receive(:put).with("/node/42/properties/foo", options1) + connection.should_receive(:put).with("/node/42/properties/baz", options2) + subject.set("42", {:foo => "bar", :baz => "qux"}) + end + + it "resets properties" do + options = { + :body => '{"foo":"bar"}', + :headers => json_content_type + } + connection.should_receive(:put).with("/node/42/properties", options) + subject.reset("42", {:foo => "bar"}) + end + + context "getting properties" do + + it "gets all properties" do + connection.should_receive(:get).with("/node/42/properties") + subject.get("42") + end + + it "gets multiple properties" do + connection.should_receive(:get).with("/node/42/properties/foo") + connection.should_receive(:get).with("/node/42/properties/bar") + subject.get("42", "foo", "bar") + end + + it "returns multiple properties as a hash" do + connection.stub(:get).and_return("baz", "qux") + subject.get("42", "foo", "bar").should == { "foo" => "baz", "bar" => "qux" } + end + + it "returns nil if no properties were found" do + connection.stub(:get).and_return(nil, nil) + subject.get("42", "foo", "bar").should be_nil + end + + it "returns hash without nil return values" do + connection.stub(:get).and_return("baz", nil) + subject.get("42", "foo", "bar").should == { "foo" => "baz" } + end + + end + + context "removing properties" do + + it "removes all properties" do + connection.should_receive(:delete).with("/node/42/properties") + subject.remove("42") + end + + it "removes multiple properties" do + connection.should_receive(:delete).with("/node/42/properties/foo") + connection.should_receive(:delete).with("/node/42/properties/bar") + subject.remove("42", "foo", "bar") + end + + end + + end + end +end diff --git a/spec/unit/rest/node_relationships_spec.rb b/spec/unit/rest/node_relationships_spec.rb new file mode 100644 index 0000000..2c8da00 --- /dev/null +++ b/spec/unit/rest/node_relationships_spec.rb @@ -0,0 +1,78 @@ +require 'spec_helper' + +module Neography + class Rest + describe NodeRelationships do + + let(:connection) { stub(:configuration => "http://configuration") } + subject { NodeRelationships.new(connection) } + + it "creates a relationship" do + body_hash = { "type" => "some_type", + "to" => "http://configuration/node/43", + "data" => {"foo"=>"bar","baz"=>"qux"} + } + connection.should_receive(:post).with("/node/42/relationships", json_match(:body, body_hash)) + + subject.create("some_type", "42", "43", {:foo => "bar", :baz => "qux"}) + end + + it "returns the post results" do + connection.stub(:post).and_return("foo") + + subject.create("some_type", "42", "43", {}).should == "foo" + end + + it "gets relationships" do + connection.should_receive(:get).with("/node/42/relationships/all") + subject.get("42") + end + + it "gets relationships with direction" do + connection.should_receive(:get).with("/node/42/relationships/in") + subject.get("42", :in) + end + + it "gets relationships with direction and type" do + connection.should_receive(:get).with("/node/42/relationships/in/foo") + subject.get("42", :in, "foo") + end + + it "gets relationships with direction and types" do + connection.should_receive(:get).with("/node/42/relationships/in/foo&bar") + subject.get("42", :in, ["foo", "bar"]) + end + + it "returns nil if no relationships were found" do + connection.stub(:get).and_return(nil) + subject.get("42", :in).should be_nil + end + + it "returns nil if no relationships were found by type" do + connection.stub(:get).and_return(nil) + subject.get("42", :in, "foo") + end + + context "directions" do + + [ :incoming, "incoming", :in, "in" ].each do |direction| + it "parses 'in' direction" do + NodeRelationships.new(nil).parse_direction(direction).should == "in" + end + end + + [ :outgoing, "outgoing", :out, "out" ].each do |direction| + it "parses 'out' direction" do + NodeRelationships.new(nil).parse_direction(direction).should == "out" + end + end + + it "parses 'all' direction by default" do + NodeRelationships.new(nil).parse_direction("foo").should == "all" + end + + end + + end + end +end diff --git a/spec/unit/rest/node_traversal_spec.rb b/spec/unit/rest/node_traversal_spec.rb new file mode 100644 index 0000000..637e87a --- /dev/null +++ b/spec/unit/rest/node_traversal_spec.rb @@ -0,0 +1,128 @@ +require 'spec_helper' + +module Neography + class Rest + describe NodeTraversal do + + let(:connection) { stub } + subject { NodeTraversal.new(connection) } + + it "traverses" do + description = { + "order" => :breadth, + "uniqueness" => :nodeglobal, + "relationships" => "relationships", + "prune evaluator" => "prune_evaluator", + "return filter" => "return_filter", + "depth" => 4 + } + + expected_body = { + "order" => "breadth first", + "uniqueness" => "node global", + "relationships" => "relationships", + "prune_evaluator" => "prune_evaluator", + "return_filter" => "return_filter", + "max_depth" => 4 + } + + connection.should_receive(:post).with("/node/42/traverse/relationship", json_match(:body, expected_body)) + + subject.traverse("42", :relationship, description) + end + + context "options" do + let(:traversal) { NodeTraversal.new(nil) } + + context "order" do + [ :breadth, "breadth", "breadth first", "breadthFirst", :wide, "wide" ].each do |order| + it "parses breadth first" do + subject.send(:get_order, order).should == "breadth first" + end + end + + it "parses depth first by default" do + subject.send(:get_order, "foo").should == "depth first" + end + end + + context "uniqueness" do + [ :nodeglobal, "node global", "nodeglobal", "node_global" ].each do |order| + it "parses node global" do + subject.send(:get_uniqueness, order).should == "node global" + end + end + + [ :nodepath, "node path", "nodepath", "node_path" ].each do |order| + it "parses node path" do + subject.send(:get_uniqueness, order).should == "node path" + end + end + + [ :noderecent, "node recent", "noderecent", "node_recent" ].each do |order| + it "parses node recent" do + subject.send(:get_uniqueness, order).should == "node recent" + end + end + + [ :relationshipglobal, "relationship global", "relationshipglobal", "relationship_global" ].each do |order| + it "parses relationship global" do + subject.send(:get_uniqueness, order).should == "relationship global" + end + end + + [ :relationshippath, "relationship path", "relationshippath", "relationship_path" ].each do |order| + it "parses relationship path" do + subject.send(:get_uniqueness, order).should == "relationship path" + end + end + + [ :relationshiprecent, "relationship recent", "relationshiprecent", "relationship_recent" ].each do |order| + it "parses relationship recent" do + subject.send(:get_uniqueness, order).should == "relationship recent" + end + end + + it "parses none by default" do + subject.send(:get_uniqueness, "foo").should == "none" + end + end + + context "depth" do + it "parses nil as nil" do + subject.send(:get_depth, nil).should be_nil + end + it "parses 0 as 1" do + subject.send(:get_depth, "0").should == 1 + end + it "parses integers" do + subject.send(:get_depth, "42").should == 42 + end + end + + context "type" do + [ :relationship, "relationship", :relationships, "relationships" ].each do |type| + it "parses relationship" do + subject.send(:get_type, type).should == "relationship" + end + end + [ :path, "path", :paths, "paths" ].each do |type| + it "parses path" do + subject.send(:get_type, type).should == "path" + end + end + [ :fullpath, "fullpath", :fullpaths, "fullpaths" ].each do |type| + it "parses fullpath" do + subject.send(:get_type, type).should == "fullpath" + end + end + + it "parses node by default" do + subject.send(:get_type, "foo").should == "node" + end + end + end + + end + end +end diff --git a/spec/unit/rest/nodes_spec.rb b/spec/unit/rest/nodes_spec.rb new file mode 100644 index 0000000..efe0775 --- /dev/null +++ b/spec/unit/rest/nodes_spec.rb @@ -0,0 +1,188 @@ +require 'spec_helper' + +module Neography + class Rest + describe Nodes do + + let(:connection) { stub } + subject { Nodes.new(connection) } + + context "get nodes" do + it "gets single nodes" do + connection.should_receive(:get).with("/node/42") + subject.get("42") + end + + it "gets multiple nodes" do + connection.should_receive(:get).with("/node/42") + connection.should_receive(:get).with("/node/43") + subject.get_each("42", "43") + end + + it "returns multiple nodes in an array" do + connection.stub(:get).and_return("foo", "bar") + subject.get_each("42", "43").should == [ "foo", "bar" ] + end + + it "gets the root node" do + connection.stub(:get).with("/").and_return({ "reference_node" => "42" }) + connection.should_receive(:get).with("/node/42") + subject.root + end + + it "returns the root node" do + connection.stub(:get).and_return({ "reference_node" => "42" }, "foo") + subject.root.should == "foo" + end + end + + context "create nodes" do + + it "creates with attributes" do + options = { + :body => '{"foo":"bar","baz":"qux"}', + :headers => json_content_type + } + connection.should_receive(:post).with("/node", options) + subject.create_with_attributes({:foo => "bar", :baz => "qux"}) + end + + it "returns the created node" do + connection.stub(:post).and_return("foo") + subject.create_with_attributes({}).should == "foo" + end + + it "creates with attributes using #create method" do + options = { + :body => '{"foo":"bar","baz":"qux"}', + :headers => json_content_type + } + connection.should_receive(:post).with("/node", options) + subject.create({:foo => "bar", :baz => "qux"}) + end + + it "creates empty nodes" do + connection.should_receive(:post).with("/node") + subject.create_empty + end + + it "returns an empty node" do + connection.stub(:post).and_return("foo") + subject.create_empty.should == "foo" + end + + it "creates empty nodes using #create method" do + connection.should_receive(:post).with("/node") + subject.create + end + + end + + context "delete nodes" do + + it "deletes a node" do + connection.should_receive(:delete).with("/node/42") + subject.delete("42") + end + + end + + context "#create_multiple" do + + it "creates multiple with attributes" do + options1 = { + :body => '{"foo1":"bar1","baz1":"qux1"}', + :headers => json_content_type + } + options2 = { + :body => '{"foo2":"bar2","baz2":"qux2"}', + :headers => json_content_type + } + connection.should_receive(:post).with("/node", options1) + connection.should_receive(:post).with("/node", options2) + + subject.create_multiple([ + {:foo1 => "bar1", :baz1 => "qux1"}, + {:foo2 => "bar2", :baz2 => "qux2"} + ]) + end + + it "returns multiple nodes with attributes in an array" do + connection.stub(:post).and_return("foo", "bar") + subject.create_multiple([{},{}]).should == ["foo", "bar"] + end + + # exotic? + it "creates multiple with and without attributes" do + options1 = { + :body => '{"foo1":"bar1","baz1":"qux1"}', + :headers => json_content_type + } + connection.should_receive(:post).with("/node", options1) + connection.should_receive(:post).with("/node") + + subject.create_multiple([ + {:foo1 => "bar1", :baz1 => "qux1"}, + "not a hash" # ? + ]) + end + + it "creates multiple empty nodes" do + connection.should_receive(:post).with("/node").twice + subject.create_multiple(2) + end + + it "returns multiple empty nodes in an array" do + connection.stub(:post).and_return("foo", "bar") + subject.create_multiple(2).should == ["foo", "bar"] + end + + end + + context "#create_multiple_threaded" do + + let(:connection) { stub(:max_threads => 2) } + + it "creates multiple with attributes" do + options1 = { + :body => '{"foo1":"bar1","baz1":"qux1"}', + :headers => json_content_type + } + options2 = { + :body => '{"foo2":"bar2","baz2":"qux2"}', + :headers => json_content_type + } + connection.should_receive(:post).with("/node", options1) + connection.should_receive(:post).with("/node", options2) + + subject.create_multiple_threaded([ + {:foo1 => "bar1", :baz1 => "qux1"}, + {:foo2 => "bar2", :baz2 => "qux2"} + ]) + end + + # exotic? + it "creates multiple with and without attributes" do + options1 = { + :body => '{"foo1":"bar1","baz1":"qux1"}', + :headers => json_content_type + } + connection.should_receive(:post).with("/node", options1) + connection.should_receive(:post).with("/node") + + subject.create_multiple_threaded([ + {:foo1 => "bar1", :baz1 => "qux1"}, + "not a hash" # ? + ]) + end + + it "creates multiple empty nodes" do + connection.should_receive(:post).with("/node").twice + subject.create_multiple_threaded(2) + end + + end + + end + end +end diff --git a/spec/unit/rest/paths_spec.rb b/spec/unit/rest/paths_spec.rb new file mode 100644 index 0000000..a30bfae --- /dev/null +++ b/spec/unit/rest/paths_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +module Neography + class Rest + + class Dummy + include Paths + + add_path :one, "/node/:id" + add_path :two, "/node/:id/properties/:property" + end + + describe Dummy do + + it { should respond_to(:one_path) } + it { should respond_to(:two_path) } + + it "replaces a key" do + subject.one_path(:id => 42).should == "/node/42" + end + + it "replaces multiple keys" do + subject.two_path(:id => 42, :property => "foo").should == "/node/42/properties/foo" + end + + it "url encodes spaces" do + subject.one_path(:id => "with space").should == "/node/with%20space" + end + + # URI.encode does not escape slashes (and rightly so), but should escape these keys + it "url encodes slashes" do + subject.one_path(:id => "with/slash").should == "/node/with%2Fslash" + end + + end + + end +end + diff --git a/spec/unit/rest/relationship_auto_indexes.rb b/spec/unit/rest/relationship_auto_indexes.rb new file mode 100644 index 0000000..74528af --- /dev/null +++ b/spec/unit/rest/relationship_auto_indexes.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +module Neography + class Rest + describe RelationshipAutoIndexes do + + let(:connection) { stub } + subject { RelationshipAutoIndexes.new(connection) } + + it "gets a relationship from an auto index" do + connection.should_receive(:get).with("/index/auto/relationship/some_key/some_value") + subject.get("some_key", "some_value") + end + + it "returns nil if nothing was found in the auto index" do + connection.stub(:get).and_return(nil) + subject.get("some_key", "some_value").should be_nil + end + + it "finds by key and value" do + connection.should_receive(:get).with("/index/auto/relationship/some_key/some_value") + subject.find("some_key", "some_value") + end + + it "finds by query" do + connection.should_receive(:get).with("/index/auto/relationship/?query=some_query") + subject.query("some_query") + end + + it "gets the status" do + connection.should_receive(:get).with("/index/auto/relationship/status") + subject.status + end + + it "sets the status" do + connection.should_receive(:put).with("/index/auto/relationship/status", hash_match(:body, '"foo"')) + subject.status = "foo" + end + + it "gets auto index properties" do + connection.should_receive(:get).with("/index/auto/relationship/properties") + subject.properties + end + + it "adds a property to an auto index" do + connection.should_receive(:post).with("/index/auto/relationship/properties", hash_match(:body, "foo")) + subject.add_property("foo") + end + + it "removes a property from an auto index" do + connection.should_receive(:delete).with("/index/auto/relationship/properties/foo") + subject.remove_property("foo") + end + + end + end +end diff --git a/spec/unit/rest/relationship_indexes_spec.rb b/spec/unit/rest/relationship_indexes_spec.rb new file mode 100644 index 0000000..6cacf41 --- /dev/null +++ b/spec/unit/rest/relationship_indexes_spec.rb @@ -0,0 +1,103 @@ +require 'spec_helper' + +module Neography + class Rest + describe RelationshipIndexes do + + let(:connection) { stub(:configuration => "http://configuration") } + subject { RelationshipIndexes.new(connection) } + + it "lists all indexes" do + connection.should_receive(:get).with("/index/relationship") + subject.list + end + + it "creates a relationship index" do + expected_body = { + "config" => { + "type" => "some_type", + "provider" => "some_provider" + }, + "name" => "some_index" + } + connection.should_receive(:post).with("/index/relationship", json_match(:body, expected_body)) + subject.create("some_index", "some_type", "some_provider") + end + + it "returns the post result after creation" do + connection.stub(:post).and_return("foo") + subject.create("some_index", "some_type", "some_provider").should == "foo" + end + + it "creates an auto-index" do + expected_body = { + "config" => { + "type" => "some_type", + "provider" => "some_provider" + }, + "name" => "relationship_auto_index" + } + connection.should_receive(:post).with("/index/relationship", json_match(:body, expected_body)) + subject.create_auto("some_type", "some_provider") + end + + it "creates a unique relationship in an index" do + expected_body = { + "key" => "key", + "value" => "value", + "type" => "type", + "start" => "http://configuration/node/42", + "end" => "http://configuration/node/43" + } + connection.should_receive(:post).with("/index/relationship/some_index?unique", json_match(:body, expected_body)) + subject.create_unique("some_index", "key", "value", "type", "42", "43") + end + + it "adds a relationship to an index" do + expected_body = { + "uri" => "http://configuration/relationship/42", + "key" => "key", + "value" => "value" + } + connection.should_receive(:post).with("/index/relationship/some_index", json_match(:body, expected_body)) + subject.add("some_index", "key", "value", "42") + end + + it "gets a relationship from an index" do + connection.should_receive(:get).with("/index/relationship/some_index/some_key/some_value") + subject.get("some_index", "some_key", "some_value") + end + + it "returns nil if nothing was found in the index" do + connection.stub(:get).and_return(nil) + subject.get("some_index", "some_key", "some_value").should be_nil + end + + it "finds by key query" do + connection.should_receive(:get).with("/index/relationship/some_index/some_key?query=some_query") + subject.find_by_key_query("some_index", "some_key", "some_query") + end + + it "finds by query" do + connection.should_receive(:get).with("/index/relationship/some_index?query=some_query") + subject.find_by_query("some_index", "some_query") + end + + it "removes a relationship from an index" do + connection.should_receive(:delete).with("/index/relationship/some_index/42") + subject.remove("some_index", "42") + end + + it "removes a relationship from an index by key" do + connection.should_receive(:delete).with("/index/relationship/some_index/some_key/42") + subject.remove_by_key("some_index", "42", "some_key") + end + + it "removes a relationship from an index by key and value" do + connection.should_receive(:delete).with("/index/relationship/some_index/some_key/some_value/42") + subject.remove_by_value("some_index", "42", "some_key", "some_value") + end + + end + end +end diff --git a/spec/unit/rest/relationship_properties_spec.rb b/spec/unit/rest/relationship_properties_spec.rb new file mode 100644 index 0000000..d110210 --- /dev/null +++ b/spec/unit/rest/relationship_properties_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' + +module Neography + class Rest + describe RelationshipProperties do + + let(:connection) { stub } + subject { RelationshipProperties.new(connection) } + + it "sets properties" do + options1 = { + :body => '"bar"', + :headers => json_content_type + } + options2 = { + :body => '"qux"', + :headers => json_content_type + } + connection.should_receive(:put).with("/relationship/42/properties/foo", options1) + connection.should_receive(:put).with("/relationship/42/properties/baz", options2) + subject.set("42", {:foo => "bar", :baz => "qux"}) + end + + it "resets properties" do + options = { + :body => '{"foo":"bar"}', + :headers => json_content_type + } + connection.should_receive(:put).with("/relationship/42/properties", options) + subject.reset("42", {:foo => "bar"}) + end + + context "getting properties" do + + it "gets all properties" do + connection.should_receive(:get).with("/relationship/42/properties") + subject.get("42") + end + + it "gets multiple properties" do + connection.should_receive(:get).with("/relationship/42/properties/foo") + connection.should_receive(:get).with("/relationship/42/properties/bar") + subject.get("42", "foo", "bar") + end + + it "returns multiple properties as a hash" do + connection.stub(:get).and_return("baz", "qux") + subject.get("42", "foo", "bar").should == { "foo" => "baz", "bar" => "qux" } + end + + it "returns nil if no properties were found" do + connection.stub(:get).and_return(nil, nil) + subject.get("42", "foo", "bar").should be_nil + end + + it "returns hash without nil return values" do + connection.stub(:get).and_return("baz", nil) + subject.get("42", "foo", "bar").should == { "foo" => "baz" } + end + + end + + context "removing properties" do + + it "removes all properties" do + connection.should_receive(:delete).with("/relationship/42/properties") + subject.remove("42") + end + + it "removes multiple properties" do + connection.should_receive(:delete).with("/relationship/42/properties/foo") + connection.should_receive(:delete).with("/relationship/42/properties/bar") + subject.remove("42", "foo", "bar") + end + + end + + end + end +end diff --git a/spec/unit/rest/relationships_spec.rb b/spec/unit/rest/relationships_spec.rb new file mode 100644 index 0000000..7ef5e0e --- /dev/null +++ b/spec/unit/rest/relationships_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +module Neography + class Rest + describe Relationships do + + let(:connection) { stub } + subject { Relationships.new(connection) } + + it "gets a relationship" do + connection.should_receive(:get).with("/relationship/42") + subject.get("42") + end + + it "deletes a relationship" do + connection.should_receive(:delete).with("/relationship/42") + subject.delete("42") + end + + end + end +end