neon 0.1.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.
- checksums.yaml +7 -0
- data/Gemfile +6 -0
- data/README.md +432 -0
- data/lib/helpers/argument_helpers.rb +19 -0
- data/lib/helpers/transaction_helpers.rb +56 -0
- data/lib/neon.rb +23 -0
- data/lib/neon/Gemfile +8 -0
- data/lib/neon/node.rb +48 -0
- data/lib/neon/node/embedded.rb +31 -0
- data/lib/neon/node/rest.rb +109 -0
- data/lib/neon/property_container.rb +277 -0
- data/lib/neon/relationship.rb +38 -0
- data/lib/neon/relationship/embedded.rb +59 -0
- data/lib/neon/relationship/rest.rb +73 -0
- data/lib/neon/session.rb +68 -0
- data/lib/neon/session/embedded.rb +92 -0
- data/lib/neon/session/invalid_session.rb +6 -0
- data/lib/neon/session/rest.rb +53 -0
- data/lib/neon/transaction.rb +28 -0
- data/lib/neon/transaction/placebo.rb +38 -0
- data/lib/neon/transaction/rest.rb +47 -0
- data/lib/neon/version.rb +7 -0
- data/lib/tasks.rb +139 -0
- data/neon.gemspec +33 -0
- metadata +180 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Neon
|
|
2
|
+
module TransactionHelpers
|
|
3
|
+
module Rest
|
|
4
|
+
def run_rest_transaction(method, *args, &block)
|
|
5
|
+
if @session.auto_tx
|
|
6
|
+
yield
|
|
7
|
+
else
|
|
8
|
+
# Fetch the query for this method
|
|
9
|
+
query = query_for method, *args
|
|
10
|
+
tx = @session.begin_tx
|
|
11
|
+
result = tx.run_query query
|
|
12
|
+
tx.success
|
|
13
|
+
tx.close
|
|
14
|
+
result = parse_result(result, method, *args)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module Embedded
|
|
20
|
+
# Used by objects to run a block of code inside a fresh transaction associated
|
|
21
|
+
def run_in_transaction(&block)
|
|
22
|
+
# Retrieve appropriate session based on current type
|
|
23
|
+
# REST:
|
|
24
|
+
# Session: self
|
|
25
|
+
# Entity: @session
|
|
26
|
+
# Embedded:
|
|
27
|
+
# Session: self
|
|
28
|
+
# Entity: get_graph_database
|
|
29
|
+
if respond_to?(:get_graph_database)
|
|
30
|
+
begin
|
|
31
|
+
tx = get_graph_database.begin_tx
|
|
32
|
+
result = yield if block_given?
|
|
33
|
+
tx.success
|
|
34
|
+
tx.close
|
|
35
|
+
rescue Exception => e
|
|
36
|
+
# Roll back the transaction
|
|
37
|
+
tx.failure
|
|
38
|
+
tx.close
|
|
39
|
+
raise e # Let the exception bubble up
|
|
40
|
+
end
|
|
41
|
+
else
|
|
42
|
+
session = @session || self
|
|
43
|
+
result = if session.auto_tx
|
|
44
|
+
r = Transaction.run(session, &block)
|
|
45
|
+
r.pop
|
|
46
|
+
r = r.pop if r.length == 1
|
|
47
|
+
r
|
|
48
|
+
else
|
|
49
|
+
yield if block_given?
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
result
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/neon.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require "helpers/argument_helpers"
|
|
2
|
+
require "helpers/transaction_helpers"
|
|
3
|
+
require "neon/session"
|
|
4
|
+
require "neon/property_container"
|
|
5
|
+
require "neon/node"
|
|
6
|
+
require "neon/node/rest"
|
|
7
|
+
require "neon/relationship"
|
|
8
|
+
require "neon/relationship/rest"
|
|
9
|
+
require "neon/transaction"
|
|
10
|
+
require "neon/transaction/placebo"
|
|
11
|
+
require "neon/transaction/rest"
|
|
12
|
+
|
|
13
|
+
# If the platform is Java then load all java related files.
|
|
14
|
+
if RUBY_PLATFORM == 'java'
|
|
15
|
+
require "java"
|
|
16
|
+
require "neo4j-community"
|
|
17
|
+
require "neon/node/embedded"
|
|
18
|
+
require "neon/relationship/embedded"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @author Ujjwal Thaakar
|
|
22
|
+
module Neon
|
|
23
|
+
end
|
data/lib/neon/Gemfile
ADDED
data/lib/neon/node.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require "helpers/argument_helpers"
|
|
2
|
+
|
|
3
|
+
module Neon
|
|
4
|
+
module Node
|
|
5
|
+
extend ArgumentHelpers
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
# Creates a new Node in the database. All subsequent changes are immediately persisted.
|
|
9
|
+
#
|
|
10
|
+
# @overload new(attributes, labels, session)
|
|
11
|
+
# @param attributes [Hash] the properties to initialize the node with.
|
|
12
|
+
# @param labels [String, Symbol, Array<String, Symbol>] an optional list of labels or an array of labels. Labels can be strings or symbols.
|
|
13
|
+
# @param session [Session] an optional session can be provided as the last value to indicate the database where to create the node.
|
|
14
|
+
# If none is provided then the current session is assumed.
|
|
15
|
+
#
|
|
16
|
+
# @return [Node] a new node.
|
|
17
|
+
def new(attributes, *args)
|
|
18
|
+
session = extract_session(args)
|
|
19
|
+
labels = args.flatten
|
|
20
|
+
begin
|
|
21
|
+
session.create_node(attributes, labels)
|
|
22
|
+
rescue NoMethodError => e
|
|
23
|
+
_raise_invalid_session_error(session, e)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Loads an existing node with the given id
|
|
28
|
+
#
|
|
29
|
+
# @param id [Integer] the id of the node to be loaded and returned.
|
|
30
|
+
# @param session [Session] an optional session from where to load the node.
|
|
31
|
+
#
|
|
32
|
+
# @return [Node] an existing node with the given id and specified session. It returns nil if the node is not found.
|
|
33
|
+
def load(id, session = Neon::Session.current)
|
|
34
|
+
begin
|
|
35
|
+
session.load(id)
|
|
36
|
+
rescue NoMethodError => e
|
|
37
|
+
_raise_invalid_session_error(session, e)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
def _raise_invalid_session_error(session, e)
|
|
43
|
+
STDERR.puts e
|
|
44
|
+
raise Neon::Session::InvalidSessionTypeError.new(session.class)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Extend the Java NodeProxy
|
|
2
|
+
Java::OrgNeo4jKernelImplCore::NodeProxy.class_eval do
|
|
3
|
+
include Neon::PropertyContainer::Embedded
|
|
4
|
+
include Neon::TransactionHelpers
|
|
5
|
+
|
|
6
|
+
def to_s
|
|
7
|
+
"Embedded Node[#{getId}]"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create_rel_to(end_node, name, attributes = {})
|
|
11
|
+
run_in_transaction { _create_rel_to end_node, name, attributes }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
def _destroy
|
|
16
|
+
get_relationships.each { |r| r.delete }
|
|
17
|
+
delete
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def _create_rel_to(end_node, name, attributes)
|
|
21
|
+
return nil if get_graph_database != end_node.get_graph_database
|
|
22
|
+
type = Java::OrgNeo4jGraphdb::DynamicRelationshipType.with_name(name)
|
|
23
|
+
rel = create_relationship_to(end_node, type)
|
|
24
|
+
if (rel.isType(type))
|
|
25
|
+
rel.props = attributes
|
|
26
|
+
rel
|
|
27
|
+
else
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
require "neon/property_container"
|
|
2
|
+
|
|
3
|
+
module Neon
|
|
4
|
+
module Node
|
|
5
|
+
class Rest
|
|
6
|
+
include PropertyContainer::Rest
|
|
7
|
+
attr_reader :session # The Rest session this node belongs to.
|
|
8
|
+
attr_reader :id # The neo id of this node.
|
|
9
|
+
attr_reader :node # The neography hash containing information about the node.
|
|
10
|
+
|
|
11
|
+
# Initialize the node with a neography node and a REST session
|
|
12
|
+
#
|
|
13
|
+
# @param node [Hash] a neogrpahy node hash.
|
|
14
|
+
# @param session [Session::Rest] the session this node was initialized to.
|
|
15
|
+
#
|
|
16
|
+
# @return [Node::Rest] a new rest node.
|
|
17
|
+
def initialize(node, session)
|
|
18
|
+
@session = session # Set the session
|
|
19
|
+
@node = node # Set the node
|
|
20
|
+
@id = node["self"].split('/').last.to_i # Set the id
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_s
|
|
24
|
+
"REST Node[#{@id}]"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Create a unidirectional relationship starting from this node to another node.
|
|
28
|
+
#
|
|
29
|
+
# @param end_node [Node::Rest] the end node for the unidirectional relationship.
|
|
30
|
+
# @param type [String, Symbol] the type of this relationship.
|
|
31
|
+
# @param attributes [Hash] a hash of the initial property-value pairs.
|
|
32
|
+
#
|
|
33
|
+
# @return [Relationship::Rest] a new relationship between *start_node* and *end_node*.
|
|
34
|
+
def create_rel_to(end_node, type, attributes = {})
|
|
35
|
+
return nil if @session.url != end_node.session.url
|
|
36
|
+
attributes.delete_if { |key, value| value.nil? }
|
|
37
|
+
neo_rel = @session.neo.create_relationship(type, @node, end_node.node, attributes)
|
|
38
|
+
return nil if neo_rel.nil?
|
|
39
|
+
rel = Relationship::Rest.new(neo_rel, @session)
|
|
40
|
+
rescue NoMethodError => e
|
|
41
|
+
_raise_doesnt_exist_anymore_error(e)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Move to a separate file
|
|
45
|
+
QUERIES = {
|
|
46
|
+
:[] => lambda do |node, *keys|
|
|
47
|
+
query = "START n = node({id})\nWITH "
|
|
48
|
+
query << keys.map { |key| "n.#{key.to_s.strip} as #{key.to_s.strip}" }.join(", ")
|
|
49
|
+
query << "\nRETURN "
|
|
50
|
+
query << keys.map { |key| key.to_s.strip }.join(", ")
|
|
51
|
+
[query, {id: node.id}]
|
|
52
|
+
end
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def query_for(method, *args)
|
|
56
|
+
# Fetch appropriate query and covert the args to a hash corresponding the query parameters
|
|
57
|
+
QUERIES[method].call(self, *args)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Move to a seprate file
|
|
61
|
+
RESULT_PARSER = {
|
|
62
|
+
:[] => lambda do |result, *keys|
|
|
63
|
+
parsed_result = []
|
|
64
|
+
result = result.first
|
|
65
|
+
columns = result["columns"]
|
|
66
|
+
data = result["data"].first["row"].dup
|
|
67
|
+
raise "Corrupted result" if columns.length != keys.length
|
|
68
|
+
for i in 0...keys.length
|
|
69
|
+
parsed_result << if keys[i] == columns[i]
|
|
70
|
+
data.shift
|
|
71
|
+
else
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
if keys.length == 1
|
|
76
|
+
parsed_result.pop
|
|
77
|
+
else
|
|
78
|
+
parsed_result
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
def parse_result(result, method, *args)
|
|
84
|
+
RESULT_PARSER[method].call(result, *args)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
def _get_properties(*keys)
|
|
89
|
+
@session.neo.get_node_properties(@node, *keys)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def _reset_properties(attributes)
|
|
93
|
+
@session.neo.reset_node_properties(@node, attributes)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def _set_private_vars_to_nil
|
|
97
|
+
@node = @session = nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def _delete
|
|
101
|
+
@session.neo.delete_node(@node)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def _destroy
|
|
105
|
+
@session.neo.delete_node!(@node)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
module Neon
|
|
2
|
+
# A module to contain REST and Embedded implementation for a property container namely nodes and relationships
|
|
3
|
+
# @author Ujjwal Thaakar
|
|
4
|
+
module PropertyContainer
|
|
5
|
+
# Server implementation for a property container
|
|
6
|
+
module Rest
|
|
7
|
+
include TransactionHelpers::Rest
|
|
8
|
+
# Compares to anothe property container
|
|
9
|
+
#
|
|
10
|
+
# @param other [PropertyContainer] the other property container being compared to
|
|
11
|
+
#
|
|
12
|
+
# @return [Boolean] wether both are the same entities based on their ids and sessions
|
|
13
|
+
def ==(other)
|
|
14
|
+
@id == other.id && @session == other.session
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Fetch one or more properties e.g. node[:property, :another_Property]. Non existent keys return nil.
|
|
18
|
+
#
|
|
19
|
+
# @param keys [Array<String, Symbol>] the properties to return
|
|
20
|
+
#
|
|
21
|
+
# @return [Array<String>, String] an array of the values of the properties, sorted in the same order they were queried.
|
|
22
|
+
# In case only a single property is fetche e.g. node[ :property] it returns a String containing the corresponding value.
|
|
23
|
+
def [](*keys)
|
|
24
|
+
# Lesson Learnt
|
|
25
|
+
# ==============
|
|
26
|
+
# Don't change what doesn't belong to you
|
|
27
|
+
keys = keys.map(&:to_s)
|
|
28
|
+
run_in_transaction(:[], *keys) do
|
|
29
|
+
properties = props # Fetch all properties as this is more efficient than firing a HTTP request for every key
|
|
30
|
+
result = []
|
|
31
|
+
keys.each { |k| result << properties[k] }
|
|
32
|
+
# If a single key was asked then return it's value else return an array of values in the correct order
|
|
33
|
+
if keys.length == 1
|
|
34
|
+
result.first
|
|
35
|
+
else
|
|
36
|
+
result
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
rescue NoMethodError => e
|
|
40
|
+
_raise_doesnt_exist_anymore_error(e)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Set one or more properties e.g. node[:property, :another_property] = 5, "Neo4J". nil keys are ignored.
|
|
44
|
+
#
|
|
45
|
+
# @param keys [Array<String, Symbol>] the properties to set.
|
|
46
|
+
# @param values [Array<Numeric, String, Symbol, Array<Numeric, String, Symbol>>] the value to assign to the properties in the order specified.
|
|
47
|
+
#
|
|
48
|
+
# @return [void]
|
|
49
|
+
def []=(*keys, values)
|
|
50
|
+
# Flattent the values to 1 level. This creates an arrray of values in the case only a single value is provided.
|
|
51
|
+
values = [values].flatten(1)
|
|
52
|
+
keys = keys.map(&:to_s)
|
|
53
|
+
run_in_transaction(:[]=, *keys, values) do
|
|
54
|
+
properties = props
|
|
55
|
+
Hash[keys.zip(values)].each { |k, v| properties[k] = v unless k.nil? }
|
|
56
|
+
self.props = properties # Reset all the properties - write simple inefficient code until it proves inefficient
|
|
57
|
+
end
|
|
58
|
+
rescue NoMethodError => e
|
|
59
|
+
_raise_doesnt_exist_anymore_error(e)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Return all properties of the property container.
|
|
63
|
+
#
|
|
64
|
+
# @return [Hash] a hash of all properties and their values.
|
|
65
|
+
def props
|
|
66
|
+
run_in_transaction(:props) do
|
|
67
|
+
_get_properties || {}
|
|
68
|
+
end
|
|
69
|
+
rescue NoMethodError => e
|
|
70
|
+
_raise_doesnt_exist_anymore_error(e)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Reset all properties of the property container.
|
|
74
|
+
#
|
|
75
|
+
# @param attributes [Hash] a hash of the key-value pairs to set.
|
|
76
|
+
#
|
|
77
|
+
# @return [void]
|
|
78
|
+
def props=(attributes)
|
|
79
|
+
attributes.delete_if { |key, value| key.nil? || value.nil? } # Remove keys-value pairs where either is nil
|
|
80
|
+
run_in_transaction(:props=, attributes) do
|
|
81
|
+
_reset_properties(attributes)
|
|
82
|
+
return
|
|
83
|
+
end
|
|
84
|
+
rescue NoMethodError => e
|
|
85
|
+
_raise_doesnt_exist_anymore_error(e)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Delete this entity.
|
|
89
|
+
def del
|
|
90
|
+
run_in_transaction(:del) do
|
|
91
|
+
_delete
|
|
92
|
+
_set_private_vars_to_nil
|
|
93
|
+
end
|
|
94
|
+
rescue NoMethodError => e
|
|
95
|
+
_raise_doesnt_exist_anymore_error(e)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Destroy this entity i.e. delete it and it's associated entities e.g. relationships of a node
|
|
99
|
+
# and in case of relationships, both its nodes.
|
|
100
|
+
def destroy
|
|
101
|
+
run_in_transaction(:destroy) do
|
|
102
|
+
_destroy # Delete the entity after deleting connected entities
|
|
103
|
+
_set_private_vars_to_nil
|
|
104
|
+
end
|
|
105
|
+
rescue NoMethodError => e
|
|
106
|
+
_raise_doesnt_exist_anymore_error(e)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
def _raise_doesnt_exist_anymore_error(e)
|
|
111
|
+
unless @session.nil?
|
|
112
|
+
STDERR.puts e
|
|
113
|
+
raise e
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def _abstract
|
|
118
|
+
raise "No properties"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
alias :_get_properties :_abstract
|
|
122
|
+
alias :_reset_properties :_abstract
|
|
123
|
+
alias :_set_private_vars_to_nil :_abstract
|
|
124
|
+
alias :_delete :_abstract
|
|
125
|
+
alias :_destroy :_abstract
|
|
126
|
+
alias :query_for :_abstract
|
|
127
|
+
alias :parse_result :_abstract
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Embedded implementation for a property container
|
|
131
|
+
module Embedded
|
|
132
|
+
include TransactionHelpers::Embedded
|
|
133
|
+
def self.included(klazz)
|
|
134
|
+
raise "Cannot include PropertyContainer::Embedded without JRuby" unless RUBY_PLATFORM == 'java'
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# @return [FixNum] the id of the entity.
|
|
138
|
+
def id
|
|
139
|
+
get_id
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Compares to anothe property container
|
|
143
|
+
#
|
|
144
|
+
# @param other [PropertyContainer] the other property container being compared to
|
|
145
|
+
#
|
|
146
|
+
# @return [Boolean] wether both are the same entities based on their ids and sessions
|
|
147
|
+
def ==(other)
|
|
148
|
+
id == other.id
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Fetch one or more properties e.g. node[:property, :another_Property]. Non existent keys return nil.
|
|
152
|
+
#
|
|
153
|
+
# @param keys [Array<String, Symbol>] the properties to return
|
|
154
|
+
#
|
|
155
|
+
# @return [Array<String>, String] an array of the values of the properties, sorted in the same order they were queried.
|
|
156
|
+
# In case only a single property is fetche e.g. node[ :property] it returns a String containing the corresponding value.
|
|
157
|
+
def [](*keys)
|
|
158
|
+
run_in_transaction do
|
|
159
|
+
keys = _serialize(keys)
|
|
160
|
+
result = []
|
|
161
|
+
keys.each do |k|
|
|
162
|
+
# Result contains the values for the keys asked or nil if they key does not exist
|
|
163
|
+
result << if has_property(k)
|
|
164
|
+
get_property(k)
|
|
165
|
+
else
|
|
166
|
+
nil
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
# If a single key was asked then return it's value else return an array of values in the correct order
|
|
170
|
+
if keys.length == 1
|
|
171
|
+
result.first
|
|
172
|
+
else
|
|
173
|
+
result
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Set one or more properties e.g. node[:property, :another_property] = 5, "Neo4J". nil keys are ignored.
|
|
179
|
+
#
|
|
180
|
+
# @param keys [Array<String, Symbol>] the properties to set.
|
|
181
|
+
# @param values [Array<Numeric, String, Symbol, Array<Numeric, String, Symbol>>] the value to assign to the properties in the order specified.
|
|
182
|
+
#
|
|
183
|
+
# @return [void]
|
|
184
|
+
def []=(*keys, values)
|
|
185
|
+
run_in_transaction do
|
|
186
|
+
# Flattent the values to 1 level. This creates an arrray of values in the case only a single value is provided.
|
|
187
|
+
values = [values].flatten(1)
|
|
188
|
+
attributes = Hash[keys.zip values]
|
|
189
|
+
nil_values = lambda { |_, v| v.nil? } # Resusable lambda
|
|
190
|
+
keys_to_delete = attributes.select(&nil_values).keys # Get the keys to be removed
|
|
191
|
+
attributes.delete_if(&nil_values) # Now remove those keys from attributes
|
|
192
|
+
keys_to_delete.each { |k| remove_property(k) if has_property(k) } # Remove the keys to be deleted if they are valid
|
|
193
|
+
attributes = _serialize(attributes)
|
|
194
|
+
attributes.each { |k, v| set_property(k, v) } # Set key-value pairs for remaining attributes
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Return all properties of the property container.
|
|
199
|
+
#
|
|
200
|
+
# @return [Hash] a hash of all properties and their values.
|
|
201
|
+
def props
|
|
202
|
+
run_in_transaction do
|
|
203
|
+
result = {} # Initialize results
|
|
204
|
+
get_property_keys.each { |key| result[key] = get_property(key) } # Populate the hash with the container's key-value pairs
|
|
205
|
+
result # Return the result
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Reset all properties of the property container.
|
|
210
|
+
#
|
|
211
|
+
# @param attributes [Hash] a hash of the key-value pairs to set.
|
|
212
|
+
#
|
|
213
|
+
# @return [void]
|
|
214
|
+
def props=(attributes)
|
|
215
|
+
run_in_transaction do
|
|
216
|
+
attributes.delete_if { |key, value| key.nil? || value.nil? } # Remove keys-value pairs where either is nil before serialization
|
|
217
|
+
attributes = _serialize(attributes)
|
|
218
|
+
get_property_keys.each { |key| remove_property(key) } # Remove all properties
|
|
219
|
+
attributes.each { |key, value| set_property(key, value) } # Set key-value pairs
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Delete this entity
|
|
224
|
+
def del
|
|
225
|
+
run_in_transaction { delete }
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Destroy this entity i.e. delete it and it's associated entities e.g. relationships of a node
|
|
229
|
+
# and in case of relationships, both its nodes.
|
|
230
|
+
def destroy
|
|
231
|
+
run_in_transaction { _destroy }
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
private
|
|
235
|
+
# Serialize keys and values into approiate type for conversion to Java objects
|
|
236
|
+
def _serialize(*objects)
|
|
237
|
+
result = objects.map do |obj|
|
|
238
|
+
if obj.is_a?(Hash)
|
|
239
|
+
_serialize_hash(obj)
|
|
240
|
+
else
|
|
241
|
+
_appropriate_type_for obj
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
if objects.length == 1
|
|
245
|
+
result.first
|
|
246
|
+
else
|
|
247
|
+
result
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Convert all keys to strings and values to an appropriate type
|
|
252
|
+
def _serialize_hash(hash)
|
|
253
|
+
attributes = {}
|
|
254
|
+
hash.each { |key, value| attributes[key.to_s] = _appropriate_type_for value }
|
|
255
|
+
attributes
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def _appropriate_type_for(value)
|
|
259
|
+
case value
|
|
260
|
+
when String, Numeric, TrueClass, FalseClass
|
|
261
|
+
value # Will be converted by JRuby
|
|
262
|
+
when Array
|
|
263
|
+
# Convert each of the elements to an appropriate type
|
|
264
|
+
# Convert to a Java array of the first element's type. If the types of all elements don't match then the runtime raises an exception.
|
|
265
|
+
result = value.map { |v| _appropriate_type_for v }
|
|
266
|
+
result.to_java(result.first.class.to_s.downcase.to_sym)
|
|
267
|
+
else
|
|
268
|
+
value.to_s # Try and convert to a string
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def _destroy
|
|
273
|
+
raise "No properties"
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|