neo4j-http 0.0.2 → 1.0.0.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +91 -3
- data/lib/neo4j/http/auth_token.rb +23 -0
- data/lib/neo4j/http/client.rb +35 -0
- data/lib/neo4j/http/configuration.rb +37 -0
- data/lib/neo4j/http/cypher_client.rb +95 -0
- data/lib/neo4j/http/errors.rb +22 -0
- data/lib/neo4j/http/node.rb +12 -0
- data/lib/neo4j/http/node_client.rb +69 -0
- data/lib/neo4j/http/object_wrapper.rb +35 -0
- data/lib/neo4j/http/relationship.rb +8 -0
- data/lib/neo4j/http/relationship_client.rb +81 -0
- data/lib/neo4j/http/results.rb +27 -0
- data/lib/neo4j/http/version.rb +1 -1
- data/lib/neo4j/http.rb +28 -0
- metadata +16 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf2b30d0cffb2fb720bf747a3198b89f92476be14e0110fe239b150c2fb6ea5f
|
4
|
+
data.tar.gz: 73531f1a3e1f22afa9033ff455d09c9400916750200fd2b06a0589c870384368
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 13acafdcb7be32279fc4b33e32f719a6dddb01da2d4bacef98639d3f8b10a6e170ed135a71335471bb22408d351a9e9979cea25ef5669e921ad1e0424e644037
|
7
|
+
data.tar.gz: 3e7e38447a5659fc75b8af9a349321b23a0cae4ccaa83f0d8200bb60f71fbd4e5323858db03ed06531d436fb74e8e1afcd7cb748dd445fab24d5ada295a5965b
|
data/README.md
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
# Neo4j::Http
|
2
2
|
|
3
|
-
The `Neo4j::Http` gem provides `
|
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,96 @@ 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
|
31
|
+
* `NEO4J_USER` - The user name to use when authenticating to neo4j
|
32
|
+
* `NEO4J_PASSWORD` - The password of the user to be used for authentication
|
33
|
+
* `NEO4J_DATABASE` - The database name to be used when connecting. By default this will be nil and the path used for connecting to Neo4j wil be `/db/data/transaction/commit` to make it compliant with v3.5 of neo4j
|
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
|
+
|
37
|
+
These configuration values can also be set during initalization like:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
Neo4j::Http.configure do |config|
|
41
|
+
config.request_timeout_in_seconds = 42
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
## Usage
|
46
|
+
|
47
|
+
The core interface can be directly accessed on `Neo4::Http::Client` -
|
48
|
+
|
49
|
+
### Execute arbitrary cypher commands
|
50
|
+
```ruby
|
51
|
+
Neo4j::Http::Client.execute_cypher('MATCH (n:User{id: $id}) return n LIMIT 25', id: 42)
|
52
|
+
```
|
53
|
+
|
54
|
+
### Upsert, find and delete nodes
|
55
|
+
```ruby
|
56
|
+
node = Neo4j::Http::Node.new(label: "User", uuid: SecureRandom.uuid)
|
57
|
+
Neo4j::Http::Client.upsert_node(node)
|
58
|
+
Neo4j::Http::Client.find_node_by(label: "User", uuid: node.uuid)
|
59
|
+
Neo4j::Http::Client.find_nodes_by(label: "User", name: "Testing")
|
60
|
+
Neo4j::Http::Client.delete_node(node)
|
61
|
+
```
|
62
|
+
|
63
|
+
### Create a new relationship, also creating the nodes if they do not exist
|
64
|
+
```ruby
|
65
|
+
user1 = Neo4j::Http::Node.new(label: "User", uuid: SecureRandom.uuid)
|
66
|
+
user2 = Neo4j::Http::Node.new(label: "User", uuid: SecureRandom.uuid)
|
67
|
+
relationship = Neo4j::Http::Relationship.new(label: "KNOWS")
|
68
|
+
Neo4j::Http::Client.upsert_relationship(relationship: relationship, from: user1, to: user2, create_nodes: true)
|
69
|
+
```
|
70
|
+
|
71
|
+
### Find an existing relationship
|
72
|
+
```ruby
|
73
|
+
Neo4j::Http::Client.find_relationship(relationship: relationship, from: user1, to: user2)
|
74
|
+
```
|
75
|
+
|
76
|
+
### Delete the relationship if it exists
|
77
|
+
```ruby
|
78
|
+
Neo4j::Http::Client.delete_relationship(relationship: relationship, from: user1, to: user2)
|
79
|
+
```
|
80
|
+
|
81
|
+
Each of the methods exposed on `Neo4j::Http::Client` above are provided by instances of each of the following adapters:
|
82
|
+
* `Neo4j::Http::CypherClient` - provides an `execute_cypher` method which sends raw cypher commands to neo4j
|
83
|
+
* `Neo4j::Http::NodeClient` - provides a higher level API for upserting and deleting Nodes
|
84
|
+
* `Neo4j::Http::RelationshipClient` - provides a higher level API for upserting and deleting Relationships
|
85
|
+
|
86
|
+
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
|
87
|
+
|
88
|
+
`Neo4j::Http::NodeClient.default`
|
89
|
+
|
90
|
+
is equivalent to:
|
91
|
+
|
92
|
+
```
|
93
|
+
config = Neo4j::Http.config
|
94
|
+
cypher_client = Neo4j::Http::CypherClient.new(config)
|
95
|
+
node_client = Neo4j::Http::NodeClient.new(cypher_client)
|
96
|
+
```
|
97
|
+
|
98
|
+
to connect to a different Neo4j database, you can create a custom configuration like:
|
99
|
+
```
|
100
|
+
config = Neo4j::Http::Configuration.new({ database_name: 'test' })
|
101
|
+
cypher_client = Neo4j::Http::CypherClient.new(config)
|
102
|
+
node_client = Neo4j::Http::NodeClient.new(cypher_client)
|
103
|
+
```
|
104
|
+
|
21
105
|
## Versioning
|
22
106
|
|
23
107
|
This project follows [semantic versioning](https://semver.org).
|
24
108
|
|
25
109
|
## Development
|
26
110
|
|
27
|
-
After checking out the repo, run `bin/setup` to install dependencies.
|
111
|
+
After checking out the repo, run `bin/setup` to install dependencies.
|
112
|
+
|
113
|
+
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.
|
114
|
+
|
115
|
+
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
28
116
|
|
29
117
|
To install this gem onto your local machine, run `bundle exec rake install`.
|
30
118
|
|
@@ -33,7 +121,7 @@ To release a new version, update the version number in `version.rb`, issue a pul
|
|
33
121
|
## Contributing
|
34
122
|
|
35
123
|
1. Fork it
|
36
|
-
2. Create your feature branch (`git
|
124
|
+
2. Create your feature branch (`git switch -c my-new-feature`)
|
37
125
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
38
126
|
4. Push to the branch (`git push origin my-new-feature`)
|
39
127
|
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,37 @@
|
|
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
|
+
|
13
|
+
def initialize(options = ENV)
|
14
|
+
@uri = options.fetch("NEO4J_URL", "http://localhost:7474")
|
15
|
+
@user = options.fetch("NEO4J_USER", "")
|
16
|
+
@password = options.fetch("NEO4J_PASSWORD", "")
|
17
|
+
@database_name = options.fetch("NEO4J_DATABASE", nil)
|
18
|
+
@user_agent = options.fetch("NEO4J_HTTP_USER_AGENT", "Ruby Neo4j Http Client")
|
19
|
+
@request_timeout_in_seconds = options.fetch("NEO4J_REQUEST_TIMEOUT_IN_SECONDS", nil)
|
20
|
+
end
|
21
|
+
|
22
|
+
def transaction_path
|
23
|
+
# v3.5 - /db/data/transaction/commit
|
24
|
+
# v4.x - /db/#{database_name}/tx/commit
|
25
|
+
if database_name
|
26
|
+
"/db/#{database_name}/tx/commit"
|
27
|
+
else
|
28
|
+
"/db/data/transaction/commit"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def auth_token
|
33
|
+
Neo4j::Http::AuthToken.token(user, password)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,95 @@
|
|
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)
|
16
|
+
@configuration = configuration
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute_cypher(cypher, parameters = {})
|
20
|
+
request_body = {
|
21
|
+
statements: [
|
22
|
+
{statement: cypher,
|
23
|
+
parameters: parameters.as_json}
|
24
|
+
]
|
25
|
+
}
|
26
|
+
|
27
|
+
response = connection.post(transaction_path, request_body)
|
28
|
+
results = check_errors!(cypher, response, parameters)
|
29
|
+
|
30
|
+
Neo4j::Http::Results.parse(results&.first || {})
|
31
|
+
end
|
32
|
+
|
33
|
+
def connection
|
34
|
+
build_connection
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
delegate :auth_token, :transaction_path, to: :@configuration
|
40
|
+
def check_errors!(cypher, response, parameters)
|
41
|
+
raise Neo4j::Http::Errors::InvalidConnectionUrl, response.status if response.status == 404
|
42
|
+
body = response.body || {}
|
43
|
+
errors = body.fetch("errors", [])
|
44
|
+
return body.fetch("results", {}) unless errors.present?
|
45
|
+
|
46
|
+
error = errors.first
|
47
|
+
raise_error(error, cypher, parameters)
|
48
|
+
end
|
49
|
+
|
50
|
+
def raise_error(error, cypher, parameters = {})
|
51
|
+
code = error["code"]
|
52
|
+
message = error["message"]
|
53
|
+
klass = find_error_class(code)
|
54
|
+
parameters = JSON.pretty_generate(parameters.as_json)
|
55
|
+
raise klass, "#{code} - #{message}\n cypher: #{cypher} \n parameters given: \n#{parameters}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_error_class(code)
|
59
|
+
Neo4j::Http::Errors.const_get(code.gsub(".", "::"))
|
60
|
+
rescue
|
61
|
+
Neo4j::Http::Errors::Neo4jCodedError
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_connection
|
65
|
+
Faraday.new(url: @configuration.uri, headers: build_http_headers, request: build_request_options) do |f|
|
66
|
+
f.request :json # encode req bodies as JSON
|
67
|
+
f.request :retry # retry transient failures
|
68
|
+
f.response :json # decode response bodies as JSON
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def build_request_options
|
73
|
+
request_options = {}
|
74
|
+
|
75
|
+
timeout = @configuration.request_timeout_in_seconds.to_i
|
76
|
+
request_options[:timeout] = timeout if timeout.positive?
|
77
|
+
|
78
|
+
request_options
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_http_headers
|
82
|
+
{
|
83
|
+
"User-Agent" => @configuration.user_agent,
|
84
|
+
"Accept" => "application/json"
|
85
|
+
}.merge(authentication_headers)
|
86
|
+
end
|
87
|
+
|
88
|
+
def authentication_headers
|
89
|
+
return {} if auth_token.blank?
|
90
|
+
|
91
|
+
{"Authentication" => "Basic #{auth_token}"}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,22 @@
|
|
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
|
+
|
8
|
+
# These are specific Errors Neo4j can raise
|
9
|
+
module Neo
|
10
|
+
module ClientError
|
11
|
+
module Statement
|
12
|
+
SyntaxError = Class.new(Neo4jCodedError)
|
13
|
+
end
|
14
|
+
|
15
|
+
module Schema
|
16
|
+
ConstraintValidationFailed = Class.new(Neo4jCodedError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,69 @@
|
|
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)
|
45
|
+
return if results.empty?
|
46
|
+
results.first&.fetch("node")
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_nodes_by(label:, attributes:, limit: 100)
|
50
|
+
selectors = build_selectors(attributes)
|
51
|
+
cypher = "MATCH (node:#{label}) where #{selectors} RETURN node LIMIT #{limit}"
|
52
|
+
results = @cypher_client.execute_cypher(cypher, attributes: attributes)
|
53
|
+
results.map { |result| result["node"] }
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def build_selectors(attributes, node_name: :node)
|
59
|
+
attributes.map do |key, value|
|
60
|
+
if value.is_a?(Array)
|
61
|
+
"#{node_name}.#{key} IN $attributes.#{key}"
|
62
|
+
else
|
63
|
+
"#{node_name}.#{key} = $attributes.#{key}"
|
64
|
+
end
|
65
|
+
end.join(" AND ")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
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,81 @@
|
|
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(cypher, from: from, to: to, relationship: relationship)
|
56
|
+
results&.first
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_match_selector(name, data)
|
60
|
+
selector = +"#{name}:#{data.label}"
|
61
|
+
selector << " {#{data.key_name}: $#{name}.#{data.key_name}}" if data.key_name.present?
|
62
|
+
selector
|
63
|
+
end
|
64
|
+
|
65
|
+
def delete_relationship(relationship:, from:, to:)
|
66
|
+
from_selector = build_match_selector(:from, from)
|
67
|
+
to_selector = build_match_selector(:to, to)
|
68
|
+
relationship_selector = build_match_selector(:relationship, relationship)
|
69
|
+
cypher = <<-CYPHER
|
70
|
+
MATCH (#{from_selector}) - [#{relationship_selector}] - (#{to_selector})
|
71
|
+
WITH from, to, relationship
|
72
|
+
DELETE relationship
|
73
|
+
RETURN from, to
|
74
|
+
CYPHER
|
75
|
+
|
76
|
+
results = @cypher_client.execute_cypher(cypher, from: from, to: to)
|
77
|
+
results&.first
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Neo4j
|
4
|
+
module Http
|
5
|
+
class Results
|
6
|
+
# Example result set:
|
7
|
+
# [{"columns"=>["n"], "data"=>[{"row"=>[{"name"=>"Foo", "uuid"=>"8c7dcfda-d848-4937-a91a-2e6debad2dd6"}], "meta"=>[{"id"=>242, "type"=>"node", "deleted"=>false}]}]}]
|
8
|
+
#
|
9
|
+
def self.parse(results)
|
10
|
+
columns = results["columns"]
|
11
|
+
data = results["data"]
|
12
|
+
|
13
|
+
data.map do |result|
|
14
|
+
row = result["row"] || []
|
15
|
+
meta = result["meta"] || []
|
16
|
+
compacted_data = row.each_with_index.map do |attributes, index|
|
17
|
+
row_meta = meta[index] || {}
|
18
|
+
attributes["_neo4j_meta_data"] = row_meta if attributes.is_a?(Hash)
|
19
|
+
attributes
|
20
|
+
end
|
21
|
+
|
22
|
+
columns.zip(compacted_data).to_h.with_indifferent_access
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/neo4j/http/version.rb
CHANGED
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.
|
4
|
+
version: 1.0.0.pre
|
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-
|
11
|
+
date: 2022-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -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:
|
126
|
+
version: 1.3.1
|
116
127
|
requirements: []
|
117
|
-
rubygems_version: 3.
|
128
|
+
rubygems_version: 3.3.11
|
118
129
|
signing_key:
|
119
130
|
specification_version: 4
|
120
131
|
summary: A simple HTTP client for Neo4j
|