neo4j-core 3.1.1 → 4.0.0

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -12
  3. data/README.md +7 -7
  4. data/lib/neo4j-core.rb +3 -2
  5. data/lib/neo4j-core/active_entity.rb +8 -10
  6. data/lib/neo4j-core/cypher_translator.rb +61 -59
  7. data/lib/neo4j-core/hash_with_indifferent_access.rb +31 -22
  8. data/lib/neo4j-core/helpers.rb +15 -17
  9. data/lib/neo4j-core/label.rb +7 -6
  10. data/lib/neo4j-core/query.rb +271 -268
  11. data/lib/neo4j-core/query_clauses.rb +371 -355
  12. data/lib/neo4j-core/query_find_in_batches.rb +26 -26
  13. data/lib/neo4j-core/version.rb +1 -1
  14. data/lib/neo4j-embedded.rb +2 -2
  15. data/lib/neo4j-embedded/cypher_response.rb +40 -41
  16. data/lib/neo4j-embedded/embedded_database.rb +21 -22
  17. data/lib/neo4j-embedded/embedded_ha_session.rb +13 -11
  18. data/lib/neo4j-embedded/embedded_impermanent_session.rb +9 -8
  19. data/lib/neo4j-embedded/embedded_label.rb +64 -70
  20. data/lib/neo4j-embedded/embedded_node.rb +68 -73
  21. data/lib/neo4j-embedded/embedded_relationship.rb +6 -13
  22. data/lib/neo4j-embedded/embedded_session.rb +128 -132
  23. data/lib/neo4j-embedded/embedded_transaction.rb +34 -33
  24. data/lib/neo4j-embedded/property.rb +84 -77
  25. data/lib/neo4j-embedded/to_java.rb +24 -23
  26. data/lib/neo4j-server.rb +1 -1
  27. data/lib/neo4j-server/cypher_authentication.rb +105 -103
  28. data/lib/neo4j-server/cypher_label.rb +25 -23
  29. data/lib/neo4j-server/cypher_node.rb +180 -177
  30. data/lib/neo4j-server/cypher_node_uncommited.rb +11 -9
  31. data/lib/neo4j-server/cypher_relationship.rb +101 -102
  32. data/lib/neo4j-server/cypher_response.rb +171 -170
  33. data/lib/neo4j-server/cypher_session.rb +209 -205
  34. data/lib/neo4j-server/cypher_transaction.rb +66 -48
  35. data/lib/neo4j-server/resource.rb +17 -22
  36. data/lib/neo4j/entity_equality.rb +3 -4
  37. data/lib/neo4j/label.rb +13 -16
  38. data/lib/neo4j/node.rb +30 -34
  39. data/lib/neo4j/property_container.rb +3 -3
  40. data/lib/neo4j/property_validator.rb +4 -5
  41. data/lib/neo4j/relationship.rb +17 -22
  42. data/lib/neo4j/session.rb +19 -21
  43. data/lib/neo4j/tasks/config_server.rb +2 -3
  44. data/lib/neo4j/tasks/neo4j_server.rake +82 -74
  45. data/lib/neo4j/transaction.rb +23 -22
  46. data/neo4j-core.gemspec +21 -16
  47. metadata +72 -2
@@ -1,22 +1,23 @@
1
- module Neo4j::Embedded
2
- # A Utility class for translating Ruby object to Neo4j Java types
3
- # @private
4
- module ToJava
5
- def type_to_java(type)
6
- Java::OrgNeo4jGraphdb::DynamicRelationshipType.withName(type.to_s)
7
- end
1
+ module Neo4j
2
+ module Embedded
3
+ # A Utility class for translating Ruby object to Neo4j Java types
4
+ # @private
5
+ module ToJava
6
+ def type_to_java(type)
7
+ Java::OrgNeo4jGraphdb::DynamicRelationshipType.withName(type.to_s)
8
+ end
8
9
 
9
- module_function :type_to_java
10
+ module_function :type_to_java
10
11
 
11
- def types_to_java(types)
12
- types.inject([]) { |result, type| result << type_to_java(type) }.to_java(Java::OrgNeo4jGraphdb::RelationshipType)
13
- end
12
+ def types_to_java(types)
13
+ types.inject([]) { |result, type| result << type_to_java(type) }.to_java(Java::OrgNeo4jGraphdb::RelationshipType)
14
+ end
14
15
 
15
- module_function :types_to_java
16
+ module_function :types_to_java
16
17
 
17
18
 
18
- def dir_from_java(dir)
19
- case dir
19
+ def dir_from_java(dir)
20
+ case dir
20
21
  when Java::OrgNeo4jGraphdb::Direction::OUTGOING then
21
22
  :outgoing
22
23
  when Java::OrgNeo4jGraphdb::Direction::BOTH then
@@ -24,14 +25,14 @@ module Neo4j::Embedded
24
25
  when Java::OrgNeo4jGraphdb::Direction::INCOMING then
25
26
  :incoming
26
27
  else
27
- raise "unknown direction '#{dir} / #{dir.class}'"
28
+ fail "unknown direction '#{dir} / #{dir.class}'"
29
+ end
28
30
  end
29
- end
30
31
 
31
- module_function :dir_from_java
32
+ module_function :dir_from_java
32
33
 
33
- def dir_to_java(dir)
34
- case dir
34
+ def dir_to_java(dir)
35
+ case dir
35
36
  when :outgoing then
36
37
  Java::OrgNeo4jGraphdb::Direction::OUTGOING
37
38
  when :both then
@@ -39,11 +40,11 @@ module Neo4j::Embedded
39
40
  when :incoming then
40
41
  Java::OrgNeo4jGraphdb::Direction::INCOMING
41
42
  else
42
- raise "unknown direction '#{dir}', expects argument: outgoing, :incoming or :both"
43
+ fail "unknown direction '#{dir}', expects argument: outgoing, :incoming or :both"
44
+ end
43
45
  end
44
- end
45
-
46
- module_function :dir_to_java
47
46
 
47
+ module_function :dir_to_java
48
+ end
48
49
  end
49
50
  end
data/lib/neo4j-server.rb CHANGED
@@ -9,4 +9,4 @@ require 'neo4j-server/cypher_session'
9
9
  require 'neo4j-server/cypher_node_uncommited'
10
10
  require 'neo4j-server/cypher_relationship'
11
11
  require 'neo4j-server/cypher_response'
12
- require 'neo4j-server/cypher_transaction'
12
+ require 'neo4j-server/cypher_transaction'
@@ -1,123 +1,125 @@
1
- module Neo4j::Server
2
- # Neo4j 2.2 has an authentication layer. This class provides methods for interacting with it.
3
- class CypherAuthentication
4
- class InvalidPasswordError < RuntimeError; end
5
- class PasswordChangeRequiredError < RuntimeError; end
6
- class MissingCredentialsError < RuntimeError; end
1
+ module Neo4j
2
+ module Server
3
+ # Neo4j 2.2 has an authentication layer. This class provides methods for interacting with it.
4
+ class CypherAuthentication
5
+ class InvalidPasswordError < RuntimeError; end
6
+ class PasswordChangeRequiredError < RuntimeError; end
7
+ class MissingCredentialsError < RuntimeError; end
7
8
 
8
- attr_reader :connection, :url, :params, :token
9
+ attr_reader :connection, :url, :params, :token
9
10
 
10
- # @param [String] url_string The server address with protocol and port.
11
- # @param [Faraday::Connection] session_connection A Faraday::Connection object. This is either an existing object, likely the
12
- # same object used by the server for data, or a new one created specifically for auth tasks.
13
- # @param [Hash] params_hash Faraday connection options. In particularly, we're looking for basic_auth creds.
14
- def initialize(url_string, session_connection = new_connection, params_hash = {})
15
- @url = url_string
16
- @connection = session_connection
17
- @params = params_hash
18
- end
11
+ # @param [String] url_string The server address with protocol and port.
12
+ # @param [Faraday::Connection] session_connection A Faraday::Connection object. This is either an existing object, likely the
13
+ # same object used by the server for data, or a new one created specifically for auth tasks.
14
+ # @param [Hash] params_hash Faraday connection options. In particularly, we're looking for basic_auth creds.
15
+ def initialize(url_string, session_connection = new_connection, params_hash = {})
16
+ @url = url_string
17
+ @connection = session_connection
18
+ @params = params_hash
19
+ end
19
20
 
20
- # Set the username and password used to communicate with the server.
21
- def basic_auth(username, password)
22
- params[:basic_auth] ||= {}
23
- params[:basic_auth][:username] = username
24
- params[:basic_auth][:password] = password
25
- end
21
+ # Set the username and password used to communicate with the server.
22
+ def basic_auth(username, password)
23
+ params[:basic_auth] ||= {}
24
+ params[:basic_auth][:username] = username
25
+ params[:basic_auth][:password] = password
26
+ end
26
27
 
27
- # POSTs to the password change endpoint of the API. Does not invalidate tokens.
28
- # @param [String] old_password The current password.
29
- # @param [String] new_password The password you want to use.
30
- # @return [Hash] The response from the server.
31
- def change_password(old_password, new_password)
32
- connection.post("#{url}/user/neo4j/password", { 'password' => old_password, 'new_password' => new_password }).body
33
- end
28
+ # POSTs to the password change endpoint of the API. Does not invalidate tokens.
29
+ # @param [String] old_password The current password.
30
+ # @param [String] new_password The password you want to use.
31
+ # @return [Hash] The response from the server.
32
+ def change_password(old_password, new_password)
33
+ connection.post("#{url}/user/neo4j/password", 'password' => old_password, 'new_password' => new_password).body
34
+ end
34
35
 
35
- # Uses the given username and password to obtain a token, then adds the token to the connection's parameters.
36
- # @return [String] An access token provided by the server.
37
- def authenticate
38
- auth_response = auth_connection("#{url}/authentication")
39
- auth_hash = if auth_response.body.empty?
40
- nil
41
- elsif auth_response.body.is_a?(String)
42
- JSON.parse(auth_response.body)['errors'][0]['code'] == 'Neo.ClientError.Security.AuthorizationFailed' ? auth_attempt : nil
43
- else
44
- auth_response
45
- end
46
- return nil if auth_hash.nil?
47
- add_auth_headers(token_or_error(auth_hash))
48
- end
36
+ # Uses the given username and password to obtain a token, then adds the token to the connection's parameters.
37
+ # @return [String] An access token provided by the server.
38
+ def authenticate
39
+ auth_response = auth_connection("#{url}/authentication")
40
+ auth_hash = if auth_response.body.empty?
41
+ nil
42
+ elsif auth_response.body.is_a?(String)
43
+ JSON.parse(auth_response.body)['errors'][0]['code'] == 'Neo.ClientError.Security.AuthorizationFailed' ? auth_attempt : nil
44
+ else
45
+ auth_response
46
+ end
47
+ return nil if auth_hash.nil?
48
+ add_auth_headers(token_or_error(auth_hash))
49
+ end
49
50
 
50
- # Invalidates the existing token, which will invalidate all conncetions using this token, applies for a new token, adds this into
51
- # the connection headers.
52
- # @param [String] password The current server password.
53
- def reauthenticate(password)
54
- invalidate_token(password)
55
- add_auth_headers(token_or_error(auth_attempt))
56
- end
51
+ # Invalidates the existing token, which will invalidate all conncetions using this token, applies for a new token, adds this into
52
+ # the connection headers.
53
+ # @param [String] password The current server password.
54
+ def reauthenticate(password)
55
+ invalidate_token(password)
56
+ add_auth_headers(token_or_error(auth_attempt))
57
+ end
57
58
 
58
- # Requests a token from the authentication endpoint using the given username and password.
59
- # @return [Faraday::Response] The server's response, to be interpreted.
60
- def auth_attempt
61
- begin
62
- user = params[:basic_auth][:username]
63
- pass = params[:basic_auth][:password]
64
- rescue NoMethodError
65
- raise MissingCredentialsError, 'Neo4j authentication is enabled, username/password are required but missing'
59
+ # Requests a token from the authentication endpoint using the given username and password.
60
+ # @return [Faraday::Response] The server's response, to be interpreted.
61
+ def auth_attempt
62
+ begin
63
+ user = params[:basic_auth][:username]
64
+ pass = params[:basic_auth][:password]
65
+ rescue NoMethodError
66
+ raise MissingCredentialsError, 'Neo4j authentication is enabled, username/password are required but missing'
67
+ end
68
+ connection.post("#{url}/authentication", 'username' => user, 'password' => pass)
66
69
  end
67
- connection.post("#{url}/authentication", { 'username' => user, 'password' => pass })
68
- end
69
70
 
70
- # Takes a response object from the server and returns a token or fails with an error.
71
- # Todo: more error states!
72
- # @param [Farday::Response] auth_response The response after attempting authentication
73
- # @return [String] An authentication token.
74
- def token_or_error(auth_response)
75
- begin
76
- raise PasswordChangeRequiredError, "Server requires a password change, please visit #{url}" if auth_response.body['password_change_required']
77
- raise InvalidPasswordError, "Neo4j server responded with: #{auth_response.body['errors'][0]['message']}" if auth_response.status.to_i == 422
78
- rescue NoMethodError
79
- raise 'Unexpected auth response, please open an issue at https://github.com/neo4jrb/neo4j-core/issues'
71
+ # Takes a response object from the server and returns a token or fails with an error.
72
+ # TODO: more error states!
73
+ # @param [Farday::Response] auth_response The response after attempting authentication
74
+ # @return [String] An authentication token.
75
+ def token_or_error(auth_response)
76
+ begin
77
+ fail PasswordChangeRequiredError, "Server requires a password change, please visit #{url}" if auth_response.body['password_change_required']
78
+ fail InvalidPasswordError, "Neo4j server responded with: #{auth_response.body['errors'][0]['message']}" if auth_response.status.to_i == 422
79
+ rescue NoMethodError
80
+ raise 'Unexpected auth response, please open an issue at https://github.com/neo4jrb/neo4j-core/issues'
81
+ end
82
+ auth_response.body['authorization_token']
80
83
  end
81
- auth_response.body['authorization_token']
82
- end
83
84
 
84
- # Invalidates tokens as described at http://neo4j.com/docs/snapshot/rest-api-security.html#rest-api-invalidating-the-authorization-token
85
- # @param [String] current_password The current password used to connect to the database
86
- def invalidate_token(current_password)
87
- connection.post("#{url}/user/neo4j/authorization_token", { 'password' => current_password }).body
88
- end
85
+ # Invalidates tokens as described at http://neo4j.com/docs/snapshot/rest-api-security.html#rest-api-invalidating-the-authorization-token
86
+ # @param [String] current_password The current password used to connect to the database
87
+ def invalidate_token(current_password)
88
+ connection.post("#{url}/user/neo4j/authorization_token", 'password' => current_password).body
89
+ end
89
90
 
90
- # Stores an authentication token in the properly-formatted header.
91
- # This does not do any checking that what it has been given is a token. Whatever param is given will be base64 encoded and used as the header.
92
- # @param [String] token The authentication token provided by the database.
93
- def add_auth_headers(token)
94
- @token = token
95
- connection.headers['Authorization'] = "Basic realm=\"Neo4j\" #{token_hash(token)}"
96
- end
91
+ # Stores an authentication token in the properly-formatted header.
92
+ # This does not do any checking that what it has been given is a token. Whatever param is given will be base64 encoded and used as the header.
93
+ # @param [String] token The authentication token provided by the database.
94
+ def add_auth_headers(token)
95
+ @token = token
96
+ connection.headers['Authorization'] = "Basic realm=\"Neo4j\" #{token_hash(token)}"
97
+ end
97
98
 
98
- private
99
+ private
99
100
 
100
- # Makes testing easier, we can stub this method to simulate different responses
101
- def auth_connection(url)
102
- connection.get(url)
103
- end
101
+ # Makes testing easier, we can stub this method to simulate different responses
102
+ def auth_connection(url)
103
+ connection.get(url)
104
+ end
104
105
 
105
- def self.new_connection
106
- conn = Faraday.new do |b|
107
- b.request :json
108
- b.response :json, :content_type => "application/json"
109
- b.use Faraday::Adapter::NetHttpPersistent
106
+ def self.new_connection
107
+ conn = Faraday.new do |b|
108
+ b.request :json
109
+ b.response :json, content_type: 'application/json'
110
+ b.use Faraday::Adapter::NetHttpPersistent
111
+ end
112
+ conn.headers = {'Content-Type' => 'application/json'}
113
+ conn
110
114
  end
111
- conn.headers = { 'Content-Type' => 'application/json' }
112
- conn
113
- end
114
115
 
115
- def new_connection
116
- self.class.new_connection
117
- end
116
+ def new_connection
117
+ self.class.new_connection
118
+ end
118
119
 
119
- def token_hash(token)
120
- ::Base64.strict_encode64(":#{token}")
120
+ def token_hash(token)
121
+ ::Base64.strict_encode64(":#{token}")
122
+ end
121
123
  end
122
124
  end
123
- end
125
+ end
@@ -1,32 +1,34 @@
1
- module Neo4j::Server
2
- class CypherLabel < Neo4j::Label
3
- extend Forwardable
4
- def_delegator :@session, :query_cypher_for
5
- attr_reader :name
1
+ module Neo4j
2
+ module Server
3
+ class CypherLabel < Neo4j::Label
4
+ extend Forwardable
5
+ def_delegator :@session, :query_cypher_for
6
+ attr_reader :name
6
7
 
7
- def initialize(session, name)
8
- @name = name
9
- @session = session
10
- end
8
+ def initialize(session, name)
9
+ @name = name
10
+ @session = session
11
+ end
11
12
 
12
- def create_index(*properties)
13
- response = @session._query("CREATE INDEX ON :`#{@name}`(#{properties.join(',')})")
14
- response.raise_error if response.error?
15
- end
13
+ def create_index(*properties)
14
+ response = @session._query("CREATE INDEX ON :`#{@name}`(#{properties.join(',')})")
15
+ response.raise_error if response.error?
16
+ end
16
17
 
17
- def drop_index(*properties)
18
- properties.each do |property|
19
- response = @session._query("DROP INDEX ON :`#{@name}`(#{property})")
20
- response.raise_error if response.error? && !response.error_msg.match(/No such INDEX ON/)
18
+ def drop_index(*properties)
19
+ properties.each do |property|
20
+ response = @session._query("DROP INDEX ON :`#{@name}`(#{property})")
21
+ response.raise_error if response.error? && !response.error_msg.match(/No such INDEX ON/)
22
+ end
21
23
  end
22
- end
23
24
 
24
- def indexes
25
- @session.indexes(@name)
26
- end
25
+ def indexes
26
+ @session.indexes(@name)
27
+ end
27
28
 
28
- def uniqueness_constraints
29
- @session.uniqueness_constraints(@name)
29
+ def uniqueness_constraints
30
+ @session.uniqueness_constraints(@name)
31
+ end
30
32
  end
31
33
  end
32
34
  end
@@ -1,218 +1,221 @@
1
- module Neo4j::Server
2
- class CypherNode < Neo4j::Node
3
- include Neo4j::Server::Resource
4
- include Neo4j::Core::CypherTranslator
5
- include Neo4j::Core::ActiveEntity
6
-
7
- def initialize(session, value)
8
- @session = session
9
-
10
- @id = if value.is_a?(Hash)
11
- hash = value['data']
12
- @props = Hash[hash.map{ |k, v| [k.to_sym, v] }]
13
- @labels = value['metadata']['labels'].map(&:to_sym) if value['metadata']
14
- value['id'] # value['self'].match(/\d+$/)[0].to_i
15
- else
16
- value
1
+ module Neo4j
2
+ module Server
3
+ class CypherNode < Neo4j::Node
4
+ include Neo4j::Server::Resource
5
+ include Neo4j::Core::CypherTranslator
6
+ include Neo4j::Core::ActiveEntity
7
+
8
+ def initialize(session, value)
9
+ @session = session
10
+
11
+ @neo_id = if value.is_a?(Hash)
12
+ hash = value['data']
13
+ @props = Hash[hash.map { |k, v| [k.to_sym, v] }]
14
+ @labels = value['metadata']['labels'].map!(&:to_sym) if value['metadata']
15
+ value['id'] # value['self'].match(/\d+$/)[0].to_i
16
+ else
17
+ value
18
+ end
17
19
  end
18
- end
19
20
 
20
- def neo_id
21
- @id
22
- end
21
+ attr_reader :neo_id
23
22
 
24
- def inspect
25
- "CypherNode #{neo_id} (#{object_id})"
26
- end
23
+ def inspect
24
+ "CypherNode #{neo_id} (#{object_id})"
25
+ end
27
26
 
28
- # TODO, needed by neo4j-cypher
29
- def _java_node
30
- self
31
- end
27
+ # TODO, needed by neo4j-cypher
28
+ def _java_node
29
+ self
30
+ end
32
31
 
33
- # (see Neo4j::Node#create_rel)
34
- def create_rel(type, other_node, props = nil)
35
- id = @session._query_or_fail(rel_string(type, other_node, props), true, cypher_prop_list(props))
36
- data_hash = { 'type' => type, 'data' => props, 'start' => self.neo_id.to_s, 'end' => other_node.neo_id.to_s, 'id' => id }
37
- CypherRelationship.new(@session, data_hash)
38
- end
32
+ # (see Neo4j::Node#create_rel)
33
+ def create_rel(type, other_node, props = nil)
34
+ ids_hash = {start_neo_id: neo_id, end_neo_id: other_node.neo_id}
35
+ props_with_ids = props.nil? ? ids_hash : cypher_prop_list(props).merge(ids_hash)
36
+ id = @session._query_or_fail(rel_string(type, other_node, props), true, props_with_ids)
37
+ data_hash = {'type' => type, 'data' => props, 'start' => neo_id, 'end' => other_node.neo_id, 'id' => id}
38
+ CypherRelationship.new(@session, data_hash)
39
+ end
39
40
 
40
- def rel_string(type, other_node, props)
41
- "MATCH (a), (b) WHERE ID(a) = #{neo_id} AND ID(b) = #{other_node.neo_id} CREATE (a)-[r:`#{type}` #{prop_identifier(props)}]->(b) RETURN ID(r)"
42
- end
41
+ def rel_string(type, other_node, props)
42
+ "MATCH (a), (b) WHERE ID(a) = {start_neo_id} AND ID(b) = {end_neo_id} CREATE (a)-[r:`#{type}` #{prop_identifier(props)}]->(b) RETURN ID(r)"
43
+ end
43
44
 
44
- # (see Neo4j::Node#props)
45
- def props
46
- if @props
47
- @props
48
- else
49
- hash = @session._query_entity_data("#{match_start} RETURN n", nil, neo_id: neo_id)
50
- @props = Hash[hash['data'].map{ |k, v| [k.to_sym, v] }]
45
+ # (see Neo4j::Node#props)
46
+ def props
47
+ if @props
48
+ @props
49
+ else
50
+ hash = @session._query_entity_data("#{match_start} RETURN n", nil, neo_id: neo_id)
51
+ @props = Hash[hash['data'].map { |k, v| [k.to_sym, v] }]
52
+ end
51
53
  end
52
- end
53
54
 
54
- def refresh
55
- @props = nil
56
- end
55
+ def refresh
56
+ @props = nil
57
+ end
57
58
 
58
- # (see Neo4j::Node#remove_property)
59
- def remove_property(key)
60
- refresh
61
- @session._query_or_fail("#{match_start} REMOVE n.`#{key}`", false, { neo_id: neo_id })
62
- end
59
+ # (see Neo4j::Node#remove_property)
60
+ def remove_property(key)
61
+ refresh
62
+ @session._query_or_fail("#{match_start} REMOVE n.`#{key}`", false, neo_id: neo_id)
63
+ end
63
64
 
64
- # (see Neo4j::Node#set_property)
65
- def set_property(key,value)
66
- refresh
67
- @session._query_or_fail("#{match_start} SET n.`#{key}` = { value }", false, { value: value, neo_id: neo_id })
68
- value
69
- end
65
+ # (see Neo4j::Node#set_property)
66
+ def set_property(key, value)
67
+ refresh
68
+ @session._query_or_fail("#{match_start} SET n.`#{key}` = { value }", false, value: value, neo_id: neo_id)
69
+ value
70
+ end
70
71
 
71
- # (see Neo4j::Node#props=)
72
- def props=(properties)
73
- refresh
74
- @session._query_or_fail("#{match_start} SET n = { props }", false, { props: properties, neo_id: neo_id })
75
- properties
76
- end
72
+ # (see Neo4j::Node#props=)
73
+ def props=(properties)
74
+ refresh
75
+ @session._query_or_fail("#{match_start} SET n = { props }", false, props: properties, neo_id: neo_id)
76
+ properties
77
+ end
77
78
 
78
- def remove_properties(properties)
79
- refresh
80
- q = "#{match_start} REMOVE " + properties.map do |k|
81
- "n.`#{k}`"
82
- end.join(', ')
83
- @session._query_or_fail(q, false, neo_id: neo_id)
84
- end
79
+ def remove_properties(properties)
80
+ refresh
81
+ q = "#{match_start} REMOVE " + properties.map do |k|
82
+ "n.`#{k}`"
83
+ end.join(', ')
84
+ @session._query_or_fail(q, false, neo_id: neo_id)
85
+ end
85
86
 
86
- # (see Neo4j::Node#update_props)
87
- def update_props(properties)
88
- refresh
89
- return if properties.empty?
90
-
91
- removed_keys = properties.keys.select{|k| properties[k].nil?}
92
- remove_properties(removed_keys) unless removed_keys.empty?
93
- properties_to_set = properties.keys - removed_keys
94
- return if properties_to_set.empty?
95
- props_list = cypher_prop_list(properties)[:props].merge({ neo_id: neo_id })
96
- @session._query_or_fail("#{match_start} SET #{cypher_properties(properties_to_set)}", false, props_list)
97
- properties
98
- end
87
+ # (see Neo4j::Node#update_props)
88
+ def update_props(properties)
89
+ refresh
90
+ return if properties.empty?
91
+
92
+ removed_keys = properties.keys.select { |k| properties[k].nil? }
93
+ remove_properties(removed_keys) unless removed_keys.empty?
94
+ properties_to_set = properties.keys - removed_keys
95
+ return if properties_to_set.empty?
96
+ props_list = cypher_prop_list(properties)[:props].merge(neo_id: neo_id)
97
+ @session._query_or_fail("#{match_start} SET #{cypher_properties(properties_to_set)}", false, props_list)
98
+ properties
99
+ end
99
100
 
100
- # (see Neo4j::Node#get_property)
101
- def get_property(key)
102
- @props ? @props[key.to_sym] : @session._query_or_fail("#{match_start} RETURN n.`#{key}`", true, neo_id: neo_id)
103
- end
101
+ # (see Neo4j::Node#get_property)
102
+ def get_property(key)
103
+ @props ? @props[key.to_sym] : @session._query_or_fail("#{match_start} RETURN n.`#{key}`", true, neo_id: neo_id)
104
+ end
104
105
 
105
- # (see Neo4j::Node#labels)
106
- def labels
107
- @labels ||= @session._query_or_fail("#{match_start} RETURN labels(n) as labels", true, neo_id: neo_id)
108
- @labels.map(&:to_sym)
109
- end
106
+ # (see Neo4j::Node#labels)
107
+ def labels
108
+ @labels ||= @session._query_or_fail("#{match_start} RETURN labels(n) as labels", true, neo_id: neo_id).map!(&:to_sym)
109
+ end
110
110
 
111
- def _cypher_label_list(labels)
112
- ':' + labels.map{|label| "`#{label}`"}.join(':')
113
- end
111
+ def _cypher_label_list(labels_list)
112
+ ':' + labels_list.map { |label| "`#{label}`" }.join(':')
113
+ end
114
114
 
115
- def add_label(*labels)
116
- @session._query_or_fail("#{match_start} SET n #{_cypher_label_list(labels)}", false, neo_id: neo_id)
117
- end
115
+ def add_label(*new_labels)
116
+ @session._query_or_fail("#{match_start} SET n #{_cypher_label_list(new_labels)}", false, neo_id: neo_id)
117
+ new_labels.each { |label| labels << label }
118
+ end
118
119
 
119
- def remove_label(*labels)
120
- @session._query_or_fail("#{match_start} REMOVE n #{_cypher_label_list(labels)}", false, neo_id: neo_id)
121
- end
120
+ def remove_label(*target_labels)
121
+ @session._query_or_fail("#{match_start} REMOVE n #{_cypher_label_list(target_labels)}", false, neo_id: neo_id)
122
+ target_labels.each { |label| labels.delete(label) } unless labels.nil?
123
+ end
122
124
 
123
- def set_label(*label_names)
124
- q = "#{match_start} #{remove_labels_if_needed} #{set_labels_if_needed(label_names)}"
125
- @session._query_or_fail(q, false, neo_id: neo_id)
126
- end
125
+ def set_label(*label_names)
126
+ q = "#{match_start} #{remove_labels_if_needed} #{set_labels_if_needed(label_names)}"
127
+ @session._query_or_fail(q, false, neo_id: neo_id)
128
+ end
127
129
 
128
- # (see Neo4j::Node#del)
129
- def del
130
- @session._query_or_fail("#{match_start} OPTIONAL MATCH n-[r]-() DELETE n, r", false, neo_id: neo_id)
131
- end
130
+ # (see Neo4j::Node#del)
131
+ def del
132
+ @session._query_or_fail("#{match_start} OPTIONAL MATCH n-[r]-() DELETE n, r", false, neo_id: neo_id)
133
+ end
132
134
 
133
- alias_method :delete, :del
134
- alias_method :destroy, :del
135
+ alias_method :delete, :del
136
+ alias_method :destroy, :del
135
137
 
136
- # (see Neo4j::Node#exist?)
137
- def exist?
138
- @session._query("#{match_start} RETURN ID(n)", neo_id: neo_id).data.empty? ? false : true
139
- end
138
+ # (see Neo4j::Node#exist?)
139
+ def exist?
140
+ @session._query("#{match_start} RETURN ID(n)", neo_id: neo_id).data.empty? ? false : true
141
+ end
140
142
 
141
- # (see Neo4j::Node#node)
142
- def node(match={})
143
- ensure_single_relationship { match(CypherNode, "p as result LIMIT 2", match) }
144
- end
143
+ # (see Neo4j::Node#node)
144
+ def node(match = {})
145
+ ensure_single_relationship { match(CypherNode, 'p as result LIMIT 2', match) }
146
+ end
145
147
 
146
- # (see Neo4j::Node#rel)
147
- def rel(match={})
148
- ensure_single_relationship { match(CypherRelationship, "r as result LIMIT 2", match) }
149
- end
148
+ # (see Neo4j::Node#rel)
149
+ def rel(match = {})
150
+ ensure_single_relationship { match(CypherRelationship, 'r as result LIMIT 2', match) }
151
+ end
150
152
 
151
- # (see Neo4j::Node#rel?)
152
- def rel?(match={})
153
- result = match(CypherRelationship, "r as result", match)
154
- !!result.first
155
- end
153
+ # (see Neo4j::Node#rel?)
154
+ def rel?(match = {})
155
+ result = match(CypherRelationship, 'r as result', match)
156
+ !!result.first
157
+ end
156
158
 
157
- # (see Neo4j::Node#nodes)
158
- def nodes(match={})
159
- match(CypherNode, "p as result", match)
160
- end
159
+ # (see Neo4j::Node#nodes)
160
+ def nodes(match = {})
161
+ match(CypherNode, 'p as result', match)
162
+ end
161
163
 
162
- # (see Neo4j::Node#rels)
163
- def rels(match = {dir: :both})
164
- match(CypherRelationship, "r as result", match)
165
- end
164
+ # (see Neo4j::Node#rels)
165
+ def rels(match = {dir: :both})
166
+ match(CypherRelationship, 'r as result', match)
167
+ end
166
168
 
167
- # @private
168
- def match(clazz, returns, match={})
169
- to_dir = {outgoing: ->(rel) {"-#{rel}->"},
170
- incoming: ->(rel) {"<-#{rel}-"},
171
- both: ->(rel) {"-#{rel}-"} }
172
-
173
- cypher_rel = match[:type] ? "[r:`#{match[:type]}`]" : '[r]'
174
- between_id = match[:between] ? "MATCH (p) WHERE ID(p) = #{match[:between].neo_id}" : ""
175
- dir_func = to_dir[match[:dir] || :both]
176
- cypher = "#{match_start} #{between_id} MATCH (n)#{dir_func.call(cypher_rel)}(p) RETURN #{returns}"
177
- r = @session._query(cypher, neo_id: neo_id)
178
- r.raise_error if r.error?
179
- _map_result(r)
180
- end
169
+ # @private
170
+ def match(clazz, returns, match = {})
171
+ to_dir = {outgoing: ->(rel) { "-#{rel}->" },
172
+ incoming: ->(rel) { "<-#{rel}-" },
173
+ both: ->(rel) { "-#{rel}-" }}
174
+
175
+ cypher_rel = match[:type] ? "[r:`#{match[:type]}`]" : '[r]'
176
+ between_id = match[:between] ? "MATCH (p) WHERE ID(p) = #{match[:between].neo_id}" : ''
177
+ dir_func = to_dir[match[:dir] || :both]
178
+ cypher = "#{match_start} #{between_id} MATCH (n)#{dir_func.call(cypher_rel)}(p) RETURN #{returns}"
179
+ r = @session._query(cypher, neo_id: neo_id)
180
+ r.raise_error if r.error?
181
+ _map_result(r)
182
+ end
181
183
 
182
- def _map_result(r)
183
- r.to_node_enumeration.map { |rel| rel.result }
184
- end
184
+ def _map_result(r)
185
+ r.to_node_enumeration.map(&:result)
186
+ end
185
187
 
186
- private
188
+ private
187
189
 
188
- def cypher_properties(properties_to_set)
189
- properties_to_set.map! { |k| "n.`#{k}` = {`#{k}`}"}.join(',')
190
- end
190
+ def cypher_properties(properties_to_set)
191
+ properties_to_set.map! { |k| "n.`#{k}` = {`#{k}`}" }.join(',')
192
+ end
191
193
 
192
- def remove_labels_if_needed
193
- if labels.empty?
194
- ""
195
- else
196
- " REMOVE n #{_cypher_label_list(labels)}"
194
+ def remove_labels_if_needed
195
+ if labels.empty?
196
+ ''
197
+ else
198
+ " REMOVE n #{_cypher_label_list(labels)}"
199
+ end
197
200
  end
198
- end
199
201
 
200
- def set_labels_if_needed(label_names)
201
- if label_names.empty?
202
- ""
203
- else
204
- " SET n #{_cypher_label_list(label_names.map(&:to_sym).uniq)}"
202
+ def set_labels_if_needed(label_names)
203
+ if label_names.empty?
204
+ ''
205
+ else
206
+ " SET n #{_cypher_label_list(label_names.map(&:to_sym).uniq)}"
207
+ end
205
208
  end
206
- end
207
209
 
208
- def ensure_single_relationship(&block)
209
- result = yield
210
- raise "Expected to only find one relationship from node #{neo_id} matching #{match.inspect} but found #{result.count}" if result.count > 1
211
- result.first
212
- end
210
+ def ensure_single_relationship(&block)
211
+ result = yield
212
+ fail "Expected to only find one relationship from node #{neo_id} matching #{match.inspect} but found #{result.count}" if result.count > 1
213
+ result.first
214
+ end
213
215
 
214
- def match_start(identifier = 'n')
215
- "MATCH (#{identifier}) WHERE ID(#{identifier}) = {neo_id}"
216
+ def match_start(identifier = 'n')
217
+ "MATCH (#{identifier}) WHERE ID(#{identifier}) = {neo_id}"
218
+ end
216
219
  end
217
220
  end
218
221
  end