neo4j-http 0.0.2 → 1.0.2.ben

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 819aaa895d72fbc40defc9cd543bf397fbfe856e1f615d560e9f9af80e247975
4
- data.tar.gz: 72542a042e0e1ae65f1fcb6791990d7f562510db42931eebe716b62cf38c411e
3
+ metadata.gz: 5d96303c02ea752544432e28e5510fbe6c88adc8478068cab9924f523dec5c3d
4
+ data.tar.gz: c905e048958cfd2dd2a4b22c03b2a0cc7d015389689198124a02566242f47a20
5
5
  SHA512:
6
- metadata.gz: ee3c850f9bba7744d9dbb38f2a76755269ab3309977cea4ce9b01139a93a456e59cde24657fd3ba372198028168733e21edc034b0146a24c5d924216311724fa
7
- data.tar.gz: 97dc54f49c8fdec52f8ec6cd0c9ba56e30c744edb07b8dceb972311d6ebe97b197954ccafc64e52d018b82154b397887ed89a980646dc5164ecc298db3bd7749
6
+ metadata.gz: 82acf63f31d626badac509a0018aec7777165caf8507253f7ce88f145f8c235255da55d6277a340d971ec3740202e7ed4dbed4671ffec0d22e76f5b53fc909d9
7
+ data.tar.gz: d8abb8918f0ee93487928ea09d8983fa7ee8e9be62fa0f5f59f4ab8213282157ddaa2146b600992edbe3f9d12f3b281e2a3d254dd31e02d2ae1414d6fc95663d
data/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Neo4j::Http
2
2
 
3
- The `Neo4j::Http` gem provides `Neo4j::Http::Client` as a thin wrapper around the [Neo4j HTTP API](https://neo4j.com/docs/http-api/current/).
3
+ The `Neo4j::Http` gem provides `a thin wrapper around the [Neo4j HTTP API](https://neo4j.com/docs/http-api/current/) (not the legacy REST api which was removed in 4.0). It works with Neo4j 3.5 through the latest release (at the time of this writing is 4.4)
4
+
5
+ ## Why a new gem?
6
+ The available gems to interact with Neo4j are generally: out of date relying on legacy APIs removed in 4.x, require the use of JRuby, or out of date C bindings.
7
+
8
+ The goal of this gem is to provide a dependency free Ruby implementation that provides a simple wrapper to the [Neo4j HTTP API](https://neo4j.com/docs/http-api/current/) to do most of what applications may need in order to leverage the power Neo4j provides.
4
9
 
5
10
  ## Installation
6
11
 
@@ -18,13 +23,118 @@ Or install it yourself as:
18
23
 
19
24
  $ gem install neo4j-http
20
25
 
26
+ ## Configuration
27
+
28
+ The client is configured by default via a set of environment variables from [Neo4j::Http::Configuration](https://github.com/doximity/neo4j-http/blob/master/lib/neo4j/http/configuration.rb):
29
+
30
+ * `NEO4J_URL` - The base URL to connect to Neo4j at - defaults to `"http://localhost:7474"`
31
+ * `NEO4J_USER` - The user name to use when authenticating to neo4j - defaults to `""`
32
+ * `NEO4J_PASSWORD` - The password of the user to be used for authentication - defaults to `""`
33
+ * `NEO4J_DATABASE` - The database name to be used when connecting. By default this will be `nil`.
34
+ * `NEO4J_HTTP_USER_AGENT` - The user agent name provided in the request - defaults to `"Ruby Neo4j Http Client"`
35
+ * `NEO4J_REQUEST_TIMEOUT_IN_SECONDS` - The number of seconds for the http request to time out if provided - defaults to `nil`
36
+ * `ACCESS_MODE` - "WRITE", or "READ" for read only instances of Neo4j clients - defaults to `"WRITE"`
37
+
38
+ These configuration values can also be set during initalization, and take precedence over the environment variables:
39
+
40
+ ```ruby
41
+ Neo4j::Http.configure do |config|
42
+ config.uri = "http://localhost:7474"
43
+ config.user = ""
44
+ config.password = ""
45
+ config.database_name = nil
46
+ config.user_agent = "Ruby Neo4j Http Client"
47
+ config.request_timeout_in_seconds = nil
48
+ config.access_mode = "WRITE"
49
+ end
50
+ ```
51
+
52
+ ### Multiple databases
53
+
54
+ The HTTP API endpoints [follow the pattern](https://neo4j.com/docs/upgrade-migration-guide/current/migration/surface-changes/http-api/) `/db/<NEO4J_DATABASE>/tx`
55
+
56
+ To route to a different database, set a value for `NEO4J_DATABASE`. If no value is supplied, or this ENV is unset, the URI defaults to `/db/data/transaction/commit`
57
+
58
+ This can be used for testing by setting up a test environment only variable using a gem like [dotenv-rails](https://github.com/bkeepers/dotenv):
59
+
60
+ ```
61
+ # .env.testing
62
+ NEO4J_DATABASE=test
63
+ ```
64
+
65
+ All testing operations are now routed to the URI `/db/test/tx/commit`.
66
+
67
+ ## Usage
68
+
69
+ The core interface can be directly accessed on `Neo4::Http::Client` -
70
+
71
+ ### Execute arbitrary cypher commands
72
+ ```ruby
73
+ Neo4j::Http::Client.execute_cypher('MATCH (n:User{id: $id}) return n LIMIT 25', id: 42)
74
+ ```
75
+
76
+ ### Upsert, find and delete nodes
77
+ ```ruby
78
+ node = Neo4j::Http::Node.new(label: "User", uuid: SecureRandom.uuid)
79
+ Neo4j::Http::Client.upsert_node(node)
80
+ Neo4j::Http::Client.find_node_by(label: "User", uuid: node.uuid)
81
+ Neo4j::Http::Client.find_nodes_by(label: "User", name: "Testing")
82
+ Neo4j::Http::Client.delete_node(node)
83
+ ```
84
+
85
+ ### Create a new relationship, also creating the nodes if they do not exist
86
+ ```ruby
87
+ user1 = Neo4j::Http::Node.new(label: "User", uuid: SecureRandom.uuid)
88
+ user2 = Neo4j::Http::Node.new(label: "User", uuid: SecureRandom.uuid)
89
+ relationship = Neo4j::Http::Relationship.new(label: "KNOWS")
90
+ Neo4j::Http::Client.upsert_relationship(relationship: relationship, from: user1, to: user2, create_nodes: true)
91
+ ```
92
+
93
+ ### Find an existing relationship
94
+ ```ruby
95
+ Neo4j::Http::Client.find_relationship(relationship: relationship, from: user1, to: user2)
96
+ ```
97
+
98
+ ### Delete the relationship if it exists
99
+ ```ruby
100
+ Neo4j::Http::Client.delete_relationship(relationship: relationship, from: user1, to: user2)
101
+ ```
102
+
103
+ Each of the methods exposed on `Neo4j::Http::Client` above are provided by instances of each of the following adapters:
104
+ * `Neo4j::Http::CypherClient` - provides an `execute_cypher` method which sends raw cypher commands to neo4j
105
+ * `Neo4j::Http::NodeClient` - provides a higher level API for upserting and deleting Nodes
106
+ * `Neo4j::Http::RelationshipClient` - provides a higher level API for upserting and deleting Relationships
107
+
108
+ The Node and Relationship clients use the `CypherClient` under the hood. Each of these provides simple access via a `default` class method, which uses the default `Neo4j::Http.config` for creating the connections. For example
109
+
110
+ `Neo4j::Http::NodeClient.default`
111
+
112
+ is equivalent to:
113
+
114
+ ```
115
+ config = Neo4j::Http.config
116
+ cypher_client = Neo4j::Http::CypherClient.new(config)
117
+ node_client = Neo4j::Http::NodeClient.new(cypher_client)
118
+ ```
119
+
120
+ to connect to a different Neo4j database, you can create a custom configuration like:
121
+ ```
122
+ config = Neo4j::Http::Configuration.new({ database_name: 'test' })
123
+ cypher_client = Neo4j::Http::CypherClient.new(config)
124
+ node_client = Neo4j::Http::NodeClient.new(cypher_client)
125
+ ```
126
+
21
127
  ## Versioning
22
128
 
23
129
  This project follows [semantic versioning](https://semver.org).
24
130
 
25
131
  ## Development
26
132
 
27
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
133
+ After checking out the repo, run `bin/setup` to install dependencies.
134
+
135
+ To run specs, you'll need a running neo4j instance available at `localhost:7474`. If you have Docker installed, this is easily done by using the provided [docker-file](https://github.com/doximity/neo4j-http/blob/master/docker-compose.yml) - simply run `docker-compose up` within the project directory, and once running, you can then, run `rake spec` to run the tests in another terminal.
136
+
137
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
28
138
 
29
139
  To install this gem onto your local machine, run `bundle exec rake install`.
30
140
 
@@ -33,7 +143,7 @@ To release a new version, update the version number in `version.rb`, issue a pul
33
143
  ## Contributing
34
144
 
35
145
  1. Fork it
36
- 2. Create your feature branch (`git checkout -b my-new-feature`)
146
+ 2. Create your feature branch (`git switch -c my-new-feature`)
37
147
  3. Commit your changes (`git commit -am 'Add some feature'`)
38
148
  4. Push to the branch (`git push origin my-new-feature`)
39
149
  5. Create a new Pull Request
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Http
5
+ class AuthToken
6
+ def self.token(username, password)
7
+ new(username, password).token
8
+ end
9
+
10
+ def initialize(username, password)
11
+ @username = username
12
+ @password = password
13
+ end
14
+
15
+ # See: https://neo4j.com/docs/developer-manual/current/http-api/authentication/#http-api-authenticate-to-access-the-server
16
+ def token
17
+ return "" if @username.blank? || @password.blank?
18
+
19
+ Base64.encode64("#{@username}:#{@password}")
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Http
5
+ class Client
6
+ CYPHER_CLIENT_METHODS = %i[execute_cypher].freeze
7
+ NODE_CLIENT_METHODS = %i[delete_node find_node_by find_nodes_by upsert_node].freeze
8
+ RELATIONSHIP_CLIENT_METHODS = %i[delete_relationship upsert_relationship].freeze
9
+ CLIENT_METHODS = (CYPHER_CLIENT_METHODS + NODE_CLIENT_METHODS + RELATIONSHIP_CLIENT_METHODS).freeze
10
+
11
+ class << self
12
+ delegate(*CLIENT_METHODS, to: :default)
13
+
14
+ def default
15
+ cypher_client = Http::CypherClient.new(Neo4j::Http.config)
16
+ node_client = Http::NodeClient.new(cypher_client)
17
+ relationship_client = Http::RelationshipClient.new(cypher_client)
18
+ @default ||= new(cypher_client, node_client, relationship_client)
19
+ end
20
+ end
21
+
22
+ attr_accessor :cypher_client, :node_client, :relationship_client
23
+
24
+ def initialize(cypher_client, node_client, relationship_client)
25
+ @cypher_client = cypher_client
26
+ @node_client = node_client
27
+ @relationship_client = relationship_client
28
+ end
29
+
30
+ delegate(*CYPHER_CLIENT_METHODS, to: :cypher_client)
31
+ delegate(*NODE_CLIENT_METHODS, to: :node_client)
32
+ delegate(*RELATIONSHIP_CLIENT_METHODS, to: :relationship_client)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Http
5
+ class Configuration
6
+ attr_accessor :database_name
7
+ attr_accessor :password
8
+ attr_accessor :request_timeout_in_seconds
9
+ attr_accessor :uri
10
+ attr_accessor :user
11
+ attr_accessor :user_agent
12
+ attr_accessor :access_mode
13
+
14
+ def initialize(options = ENV)
15
+ @uri = options.fetch("NEO4J_URL", "http://localhost:7474")
16
+ @user = options.fetch("NEO4J_USER", "")
17
+ @password = options.fetch("NEO4J_PASSWORD", "")
18
+ @database_name = options.fetch("NEO4J_DATABASE", nil)
19
+ @user_agent = options.fetch("NEO4J_HTTP_USER_AGENT", "Ruby Neo4j Http Client")
20
+ @request_timeout_in_seconds = options.fetch("NEO4J_REQUEST_TIMEOUT_IN_SECONDS", nil)
21
+ @access_mode = options.fetch("NEO4J_ACCESS_MODE", "WRITE")
22
+ end
23
+
24
+ # https://neo4j.com/developer/manage-multiple-databases/
25
+ # https://neo4j.com/docs/upgrade-migration-guide/current/migration/surface-changes/http-api/
26
+ def transaction_path
27
+ # v3.5 - /db/data/transaction/commit
28
+ # v4.x - /db/#{database_name}/tx/commit
29
+ if database_name
30
+ "/db/#{database_name}/tx/commit"
31
+ else
32
+ "/db/data/transaction/commit"
33
+ end
34
+ end
35
+
36
+ def auth_token
37
+ Neo4j::Http::AuthToken.token(user, password)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+ require "faraday"
5
+ require "faraday/retry"
6
+ require "faraday_middleware"
7
+
8
+ module Neo4j
9
+ module Http
10
+ class CypherClient
11
+ def self.default
12
+ @default ||= new(Neo4j::Http.config)
13
+ end
14
+
15
+ def initialize(configuration, injected_connection = nil)
16
+ @configuration = configuration
17
+ @injected_connection = injected_connection
18
+ end
19
+
20
+ # Executes a cypher query, passing in the cypher statement, with parameters as an optional hash
21
+ # e.g. Neo4j::Http::Cypherclient.execute_cypher("MATCH (n { foo: $foo }) LIMIT 1 RETURN n", { foo: "bar" })
22
+ def execute_cypher(cypher, parameters = {})
23
+ # By default the access mode is set to "WRITE", but can be set to "READ"
24
+ # for improved routing performance on read only queries
25
+ access_mode = parameters.delete(:access_mode) || @configuration.access_mode
26
+
27
+ request_body = {
28
+ statements: [
29
+ {
30
+ statement: cypher,
31
+ parameters: parameters.as_json
32
+ }
33
+ ]
34
+ }
35
+
36
+ @connection = @injected_connection || connection(access_mode)
37
+ response = @connection.post(transaction_path, request_body)
38
+ results = check_errors!(cypher, response, parameters)
39
+
40
+ Neo4j::Http::Results.parse(results&.first || {})
41
+ end
42
+
43
+ def connection(access_mode)
44
+ build_connection(access_mode)
45
+ end
46
+
47
+ protected
48
+
49
+ delegate :auth_token, :transaction_path, to: :@configuration
50
+ def check_errors!(cypher, response, parameters)
51
+ raise Neo4j::Http::Errors::InvalidConnectionUrl, response.status if response.status == 404
52
+ if response.body["errors"].any? { |error| error["message"][/Routing WRITE queries is not supported/] }
53
+ raise Neo4j::Http::Errors::ReadOnlyError
54
+ end
55
+
56
+ body = response.body || {}
57
+ errors = body.fetch("errors", [])
58
+ return body.fetch("results", {}) unless errors.present?
59
+
60
+ error = errors.first
61
+ raise_error(error, cypher, parameters)
62
+ end
63
+
64
+ def raise_error(error, cypher, parameters = {})
65
+ code = error["code"]
66
+ message = error["message"]
67
+ klass = find_error_class(code)
68
+ parameters = JSON.pretty_generate(parameters.as_json)
69
+ raise klass, "#{code} - #{message}\n cypher: #{cypher} \n parameters given: \n#{parameters}"
70
+ end
71
+
72
+ def find_error_class(code)
73
+ Neo4j::Http::Errors.const_get(code.gsub(".", "::"))
74
+ rescue
75
+ Neo4j::Http::Errors::Neo4jCodedError
76
+ end
77
+
78
+ def build_connection(access_mode)
79
+ # https://neo4j.com/docs/http-api/current/actions/transaction-configuration/
80
+ headers = build_http_headers.merge({"access-mode" => access_mode})
81
+ Faraday.new(url: @configuration.uri, headers: headers, request: build_request_options) do |f|
82
+ f.request :json # encode req bodies as JSON
83
+ f.request :retry # retry transient failures
84
+ f.response :json # decode response bodies as JSON
85
+ end
86
+ end
87
+
88
+ def build_request_options
89
+ request_options = {}
90
+
91
+ timeout = @configuration.request_timeout_in_seconds.to_i
92
+ request_options[:timeout] = timeout if timeout.positive?
93
+
94
+ request_options
95
+ end
96
+
97
+ def build_http_headers
98
+ {
99
+ "User-Agent" => @configuration.user_agent,
100
+ "Accept" => "application/json"
101
+ }.merge(authentication_headers)
102
+ end
103
+
104
+ def authentication_headers
105
+ return {} if auth_token.blank?
106
+
107
+ {"Authentication" => "Basic #{auth_token}"}
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,23 @@
1
+ module Neo4j
2
+ module Http
3
+ module Errors
4
+ Neo4jError = Class.new(StandardError)
5
+ InvalidConnectionUrl = Class.new(Neo4jError)
6
+ Neo4jCodedError = Class.new(Neo4jError)
7
+ ReadOnlyError = Class.new(Neo4jError)
8
+
9
+ # These are specific Errors Neo4j can raise
10
+ module Neo
11
+ module ClientError
12
+ module Statement
13
+ SyntaxError = Class.new(Neo4jCodedError)
14
+ end
15
+
16
+ module Schema
17
+ ConstraintValidationFailed = Class.new(Neo4jCodedError)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Http
5
+ class Node < ObjectWrapper
6
+ DEFAULT_PRIMARY_KEY_NAME = "uuid"
7
+ def initialize(label:, graph_node_primary_key_name: DEFAULT_PRIMARY_KEY_NAME, **attributes)
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Http
5
+ class NodeClient
6
+ def self.default
7
+ @default ||= new(CypherClient.default)
8
+ end
9
+
10
+ def initialize(cypher_client)
11
+ @cypher_client = cypher_client
12
+ end
13
+
14
+ def upsert_node(node)
15
+ raise "#{node.key_name} value cannot be blank - (node keys: #{node.to_h.keys})" if node.key_value.blank?
16
+
17
+ cypher = <<-CYPHER
18
+ MERGE (node:#{node.label} {#{node.key_name}: $key_value})
19
+ ON CREATE SET node += $attributes
20
+ ON MATCH SET node += $attributes
21
+ return node
22
+ CYPHER
23
+
24
+ results = @cypher_client.execute_cypher(cypher, key_value: node.key_value, attributes: node.attributes)
25
+
26
+ results.first&.fetch("node")
27
+ end
28
+
29
+ def delete_node(node)
30
+ cypher = <<-CYPHER
31
+ MATCH (node:#{node.label} {#{node.key_name}: $key_value})
32
+ WITH node
33
+ DETACH DELETE node
34
+ RETURN node
35
+ CYPHER
36
+
37
+ results = @cypher_client.execute_cypher(cypher, key_value: node.key_value)
38
+ results.first&.fetch("node")
39
+ end
40
+
41
+ def find_node_by(label:, **attributes)
42
+ selectors = attributes.map { |key, value| "#{key}: $attributes.#{key}" }.join(", ")
43
+ cypher = "MATCH (node:#{label} { #{selectors} }) RETURN node LIMIT 1"
44
+ results = @cypher_client.execute_cypher(cypher, attributes: attributes.merge(access_mode: "READ"))
45
+ return if results.empty?
46
+
47
+ results.first&.fetch("node")
48
+ end
49
+
50
+ def find_nodes_by(label:, attributes:, limit: 100)
51
+ selectors = build_selectors(attributes)
52
+ cypher = "MATCH (node:#{label}) where #{selectors} RETURN node LIMIT #{limit}"
53
+ results = @cypher_client.execute_cypher(cypher, attributes: attributes.merge(access_mode: "READ"))
54
+ results.map { |result| result["node"] }
55
+ end
56
+
57
+ protected
58
+
59
+ def build_selectors(attributes, node_name: :node)
60
+ attributes.map do |key, value|
61
+ if value.is_a?(Array)
62
+ "#{node_name}.#{key} IN $attributes.#{key}"
63
+ else
64
+ "#{node_name}.#{key} = $attributes.#{key}"
65
+ end
66
+ end.join(" AND ")
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Http
5
+ class ObjectWrapper
6
+ attr_reader :attributes,
7
+ :key_name,
8
+ :key_value,
9
+ :label,
10
+ :original_attributes
11
+
12
+ def initialize(label:, graph_node_primary_key_name: nil, **attributes)
13
+ @original_attributes = (attributes || {}).with_indifferent_access
14
+ @attributes = original_attributes.dup.with_indifferent_access
15
+ @key_name = graph_node_primary_key_name
16
+ @key_value = @attributes.delete(key_name)
17
+ @label = label
18
+ end
19
+
20
+ delegate :[], :to_h, :as_json, :to_json, to: :@original_attributes
21
+
22
+ def method_missing(method_name, *args, &block)
23
+ if @original_attributes.has_key?(method_name)
24
+ original_attributes[method_name]
25
+ else
26
+ super
27
+ end
28
+ end
29
+
30
+ def respond_to_missing?(method_name, include_private = false)
31
+ @original_attributes.has_key?(method_name) || super
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Http
5
+ class Relationship < ObjectWrapper
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Neo4j
4
+ module Http
5
+ class RelationshipClient
6
+ def self.default
7
+ @default ||= new(CypherClient.default)
8
+ end
9
+
10
+ def initialize(cypher_client)
11
+ @cypher_client = cypher_client
12
+ end
13
+
14
+ def upsert_relationship(relationship:, from:, to:, create_nodes: false)
15
+ match_or_merge = create_nodes ? "MERGE" : "MATCH"
16
+ from_selector = build_match_selector(:from, from)
17
+ to_selector = build_match_selector(:to, to)
18
+ relationship_selector = build_match_selector(:relationship, relationship)
19
+
20
+ on_match = ""
21
+ if relationship.attributes.present?
22
+ on_match = <<-CYPHER
23
+ ON CREATE SET relationship += $relationship_attributes
24
+ ON MATCH SET relationship += $relationship_attributes
25
+ CYPHER
26
+ end
27
+
28
+ cypher = +<<-CYPHER
29
+ #{match_or_merge} (#{from_selector})
30
+ #{match_or_merge} (#{to_selector})
31
+ MERGE (from) - [#{relationship_selector}] - (to)
32
+ #{on_match}
33
+ RETURN from, to, relationship
34
+ CYPHER
35
+
36
+ results = @cypher_client.execute_cypher(
37
+ cypher,
38
+ from: from,
39
+ to: to,
40
+ relationship: relationship,
41
+ relationship_attributes: relationship.attributes
42
+ )
43
+ results&.first
44
+ end
45
+
46
+ def find_relationship(from:, relationship:, to:)
47
+ from_match_clause = build_match_selector(:from, from)
48
+ to_match_clause = build_match_selector(:to, to)
49
+ relationship_clause = build_match_selector(:relationship, relationship)
50
+ cypher = <<-CYPHER
51
+ MATCH (#{from_match_clause}) - [#{relationship_clause}] - (#{to_match_clause})
52
+ RETURN from, to, relationship
53
+ CYPHER
54
+
55
+ results = @cypher_client.execute_cypher(
56
+ cypher,
57
+ from: from,
58
+ to: to,
59
+ relationship: relationship,
60
+ access_mode: "READ"
61
+ )
62
+ results&.first
63
+ end
64
+
65
+ def build_match_selector(name, data)
66
+ selector = +"#{name}:#{data.label}"
67
+ selector << " {#{data.key_name}: $#{name}.#{data.key_name}}" if data.key_name.present?
68
+ selector
69
+ end
70
+
71
+ def delete_relationship(relationship:, from:, to:)
72
+ from_selector = build_match_selector(:from, from)
73
+ to_selector = build_match_selector(:to, to)
74
+ relationship_selector = build_match_selector(:relationship, relationship)
75
+ cypher = <<-CYPHER
76
+ MATCH (#{from_selector}) - [#{relationship_selector}] - (#{to_selector})
77
+ WITH from, to, relationship
78
+ DELETE relationship
79
+ RETURN from, to
80
+ CYPHER
81
+
82
+ results = @cypher_client.execute_cypher(cypher, from: from, to: to)
83
+ results&.first
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,30 @@
1
+ require "forwardable"
2
+
3
+ module Neo4j
4
+ module Http
5
+ class Results
6
+ # Example result set:
7
+ # [{"columns"=>["n"],
8
+ # "data"=>
9
+ # [{"row"=>[{"name"=>"Foo", "uuid"=>"8c7dcfda-d848-4937-a91a-2e6debad2dd6"}],
10
+ # "meta"=>[{"id"=>242, "type"=>"node", "deleted"=>false}]}]}]
11
+ #
12
+ def self.parse(results)
13
+ columns = results["columns"]
14
+ data = results["data"]
15
+
16
+ data.map do |result|
17
+ row = result["row"] || []
18
+ meta = result["meta"] || []
19
+ compacted_data = row.each_with_index.map do |attributes, index|
20
+ row_meta = meta[index] || {}
21
+ attributes["_neo4j_meta_data"] = row_meta if attributes.is_a?(Hash)
22
+ attributes
23
+ end
24
+
25
+ columns.zip(compacted_data).to_h.with_indifferent_access
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,5 +1,5 @@
1
1
  module Neo4j
2
2
  module Http
3
- VERSION = "0.0.2"
3
+ VERSION = "1.0.2.ben"
4
4
  end
5
5
  end
data/lib/neo4j/http.rb CHANGED
@@ -1,6 +1,34 @@
1
1
  require "neo4j/http/version"
2
2
 
3
+ require "active_support"
4
+ require "active_support/core_ext/module/delegation"
5
+ require "active_support/core_ext/object/blank"
6
+ require "active_support/core_ext/object/json"
7
+ require "active_support/core_ext/hash/indifferent_access"
8
+
9
+ require "neo4j/http/auth_token"
10
+ require "neo4j/http/client"
11
+ require "neo4j/http/configuration"
12
+ require "neo4j/http/cypher_client"
13
+ require "neo4j/http/object_wrapper"
14
+ require "neo4j/http/node"
15
+ require "neo4j/http/node_client"
16
+ require "neo4j/http/relationship"
17
+ require "neo4j/http/relationship_client"
18
+ require "neo4j/http/results"
19
+
20
+ require "neo4j/http/errors"
21
+
3
22
  module Neo4j
4
23
  module Http
24
+ extend self
25
+
26
+ def config
27
+ @congiguration ||= Configuration.new
28
+ end
29
+
30
+ def configure
31
+ yield config
32
+ end
5
33
  end
6
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neo4j-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 1.0.2.ben
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Stawarz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-12 00:00:00.000000000 Z
11
+ date: 2022-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -73,7 +73,7 @@ dependencies:
73
73
  - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
- type: :development
76
+ type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
@@ -91,6 +91,17 @@ files:
91
91
  - README.md
92
92
  - Rakefile
93
93
  - lib/neo4j/http.rb
94
+ - lib/neo4j/http/auth_token.rb
95
+ - lib/neo4j/http/client.rb
96
+ - lib/neo4j/http/configuration.rb
97
+ - lib/neo4j/http/cypher_client.rb
98
+ - lib/neo4j/http/errors.rb
99
+ - lib/neo4j/http/node.rb
100
+ - lib/neo4j/http/node_client.rb
101
+ - lib/neo4j/http/object_wrapper.rb
102
+ - lib/neo4j/http/relationship.rb
103
+ - lib/neo4j/http/relationship_client.rb
104
+ - lib/neo4j/http/results.rb
94
105
  - lib/neo4j/http/version.rb
95
106
  homepage: https://github.com/doximity/neo4j-http
96
107
  licenses:
@@ -110,11 +121,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
110
121
  version: 2.3.0
111
122
  required_rubygems_version: !ruby/object:Gem::Requirement
112
123
  requirements:
113
- - - ">="
124
+ - - ">"
114
125
  - !ruby/object:Gem::Version
115
- version: '0'
126
+ version: 1.3.1
116
127
  requirements: []
117
- rubygems_version: 3.2.32
128
+ rubygems_version: 3.3.11
118
129
  signing_key:
119
130
  specification_version: 4
120
131
  summary: A simple HTTP client for Neo4j