neo4j 6.0.0.alpha.9 → 6.0.0.alpha.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/lib/neo4j.rb +2 -0
- data/lib/neo4j/active_node.rb +0 -9
- data/lib/neo4j/active_node/property.rb +13 -0
- data/lib/neo4j/active_node/query/query_proxy.rb +24 -16
- data/lib/neo4j/active_rel.rb +13 -12
- data/lib/neo4j/active_rel/persistence.rb +25 -25
- data/lib/neo4j/active_rel/persistence/query_factory.rb +95 -0
- data/lib/neo4j/active_rel/property.rb +4 -0
- data/lib/neo4j/active_rel/related_node.rb +27 -3
- data/lib/neo4j/shared/callbacks.rb +8 -0
- data/lib/neo4j/shared/declared_properties.rb +7 -0
- data/lib/neo4j/shared/persistence.rb +4 -9
- data/lib/neo4j/shared/property.rb +20 -4
- data/lib/neo4j/shared/query_factory.rb +91 -0
- data/lib/neo4j/version.rb +1 -1
- data/neo4j.gemspec +1 -1
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cbd147ccfe5710b9dd99035774e8ccac71f1a2fe
|
4
|
+
data.tar.gz: 0afb13bbc63f47ce1658313f13541586cdbaca8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba9b1a7bed89c5e0d3be9349a527b496d0cd92a025b0602e68a89b38e4bcae5e45de4a667249abeb23c7fa63be7fcf26f152e48b117108eebcb6f38fe061729c
|
7
|
+
data.tar.gz: ccb77857f67f3bc4ac45087b10d71ef2117b6170ed437aae1593a77f4ff35549a709b76d9046430dd429419efc6cb829cd76e80e1e790b5faad49a454e6d88dd
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,27 @@ All notable changes to this project will be documented in this file.
|
|
3
3
|
This file should follow the standards specified on [http://keepachangelog.com/]
|
4
4
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
5
5
|
|
6
|
+
## [6.0.0.alpha.11] - 11-3-2015
|
7
|
+
|
8
|
+
### Fixed
|
9
|
+
- Regression RE: properties being overwritten with their defaults on save in alpha.10.
|
10
|
+
|
11
|
+
### Changed
|
12
|
+
- `#<<` and `#create` methods on associations now create with the `rel_class` when available so that validations/callbacks/defaults are all used as expected
|
13
|
+
- Allow calling of `#method=` methods via model `new` method `Hash` argument
|
14
|
+
|
15
|
+
### Added
|
16
|
+
- Alternate `ActiveRel` init syntax: `RelClass.new(from_node, to_node, args)`. This is optional, so giving a single hash with props with or without nodes is still possible.
|
17
|
+
|
18
|
+
## [6.0.0.alpha.10] - 11-2-2015
|
19
|
+
|
20
|
+
### Fixed
|
21
|
+
- Long properties in `ActiveNode`/`ActiveRel` `#inspect` are truncated
|
22
|
+
- Property defaults are set initially when an instance of a model is loaded, then checked again before save to ensure `valid?` works.
|
23
|
+
|
24
|
+
### Added
|
25
|
+
- `ActiveRel` `create` actions can now handle unpersisted nodes.
|
26
|
+
|
6
27
|
## [6.0.0.alpha.9] - 10-27-2015
|
7
28
|
|
8
29
|
### Fixed
|
data/lib/neo4j.rb
CHANGED
@@ -41,11 +41,13 @@ require 'neo4j/shared/identity'
|
|
41
41
|
require 'neo4j/shared/serialized_properties'
|
42
42
|
require 'neo4j/shared/typecaster'
|
43
43
|
require 'neo4j/shared/initialize'
|
44
|
+
require 'neo4j/shared/query_factory'
|
44
45
|
require 'neo4j/shared'
|
45
46
|
|
46
47
|
require 'neo4j/active_rel/callbacks'
|
47
48
|
require 'neo4j/active_rel/initialize'
|
48
49
|
require 'neo4j/active_rel/property'
|
50
|
+
require 'neo4j/active_rel/persistence/query_factory'
|
49
51
|
require 'neo4j/active_rel/persistence'
|
50
52
|
require 'neo4j/active_rel/validations'
|
51
53
|
require 'neo4j/active_rel/query'
|
data/lib/neo4j/active_node.rb
CHANGED
@@ -45,15 +45,6 @@ module Neo4j
|
|
45
45
|
_persisted_obj || fail('Tried to access native neo4j object on a non persisted object')
|
46
46
|
end
|
47
47
|
|
48
|
-
def inspect
|
49
|
-
id_property_name = self.class.id_property_name.to_s
|
50
|
-
attribute_pairs = attributes.except(id_property_name).sort.map { |key, value| "#{key}: #{value.inspect}" }
|
51
|
-
attribute_pairs.unshift("#{id_property_name}: #{self.send(id_property_name).inspect}")
|
52
|
-
attribute_descriptions = attribute_pairs.join(', ')
|
53
|
-
separator = ' ' unless attribute_descriptions.empty?
|
54
|
-
"#<#{self.class.name}#{separator}#{attribute_descriptions}>"
|
55
|
-
end
|
56
|
-
|
57
48
|
included do
|
58
49
|
include Neo4j::Timestamps if Neo4j::Config[:record_timestamps]
|
59
50
|
|
@@ -44,5 +44,18 @@ module Neo4j::ActiveNode
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def inspect_attributes
|
51
|
+
id_property_name = self.class.id_property_name.to_s
|
52
|
+
|
53
|
+
attribute_pairs = attributes.except(id_property_name).sort.map do |key, value|
|
54
|
+
[key, (value.is_a?(String) && value.size > 100) ? value.dup.first(100) : value]
|
55
|
+
end
|
56
|
+
|
57
|
+
attribute_pairs.unshift([id_property_name, self.send(id_property_name)])
|
58
|
+
attribute_pairs
|
59
|
+
end
|
47
60
|
end
|
48
61
|
end
|
@@ -208,9 +208,21 @@ module Neo4j
|
|
208
208
|
end
|
209
209
|
|
210
210
|
def _create_relationship(other_node_or_nodes, properties)
|
211
|
-
|
212
|
-
|
213
|
-
|
211
|
+
if association.relationship_class
|
212
|
+
_create_relationship_with_rel_class(other_node_or_nodes, properties)
|
213
|
+
else
|
214
|
+
_session.query(context: @options[:context])
|
215
|
+
.match(:start, :end).match_nodes(start: @start_object, end: other_node_or_nodes)
|
216
|
+
.send(association.create_method, "start#{_association_arrow(properties, true)}end").exec
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def _create_relationship_with_rel_class(other_node_or_nodes, properties)
|
221
|
+
Array(other_node_or_nodes).each do |other_node|
|
222
|
+
node_props = (association.direction == :in) ? {from_node: other_node, to_node: @start_object} : {from_node: @start_object, to_node: other_node}
|
223
|
+
|
224
|
+
association.relationship_class.create(properties.except(:_classname).merge(node_props))
|
225
|
+
end
|
214
226
|
end
|
215
227
|
|
216
228
|
def read_attribute_for_serialization(*args)
|
@@ -298,21 +310,20 @@ module Neo4j
|
|
298
310
|
end
|
299
311
|
|
300
312
|
def _association_chain_var
|
313
|
+
fail 'Crazy error' if !(start_object || @query_proxy)
|
314
|
+
|
301
315
|
if start_object
|
302
316
|
:"#{start_object.class.name.gsub('::', '_').downcase}#{start_object.neo_id}"
|
303
|
-
elsif @query_proxy
|
304
|
-
@query_proxy.node_var || :"node#{_chain_level}"
|
305
317
|
else
|
306
|
-
|
318
|
+
@query_proxy.node_var || :"node#{_chain_level}"
|
307
319
|
end
|
308
320
|
end
|
309
321
|
|
310
322
|
def _association_query_start(var)
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
end
|
323
|
+
# TODO: Better error
|
324
|
+
fail 'Crazy error' if !(object = (start_object || @query_proxy))
|
325
|
+
|
326
|
+
object.query_as(var)
|
316
327
|
end
|
317
328
|
|
318
329
|
def _rel_chain_var
|
@@ -326,11 +337,8 @@ module Neo4j
|
|
326
337
|
def instance_vars_from_options!(options)
|
327
338
|
@node_var, @session, @source_object, @starting_query, @optional,
|
328
339
|
@start_object, @query_proxy, @chain_level, @association_labels,
|
329
|
-
@rel_length = options.values_at(:node, :session, :source_object,
|
330
|
-
:
|
331
|
-
:start_object, :query_proxy,
|
332
|
-
:chain_level, :association_labels,
|
333
|
-
:rel_length)
|
340
|
+
@rel_length = options.values_at(:node, :session, :source_object, :starting_query, :optional,
|
341
|
+
:start_object, :query_proxy, :chain_level, :association_labels, :rel_length)
|
334
342
|
end
|
335
343
|
|
336
344
|
def build_deeper_query_proxy(method, args)
|
data/lib/neo4j/active_rel.rb
CHANGED
@@ -17,18 +17,9 @@ module Neo4j
|
|
17
17
|
|
18
18
|
class FrozenRelError < StandardError; end
|
19
19
|
|
20
|
-
def initialize(
|
21
|
-
load_nodes
|
22
|
-
super
|
23
|
-
end
|
24
|
-
|
25
|
-
def inspect
|
26
|
-
attribute_pairs = attributes.sort.map { |key, value| "#{key}: #{value.inspect}" }
|
27
|
-
attribute_descriptions = attribute_pairs.join(', ')
|
28
|
-
separator = ' ' unless attribute_descriptions.empty?
|
29
|
-
|
30
|
-
cypher_representation = "#{node_cypher_representation(from_node)}-[:#{type}]->#{node_cypher_representation(to_node)}"
|
31
|
-
"#<#{self.class.name} #{cypher_representation}#{separator}#{attribute_descriptions}>"
|
20
|
+
def initialize(from_node = nil, to_node = nil, args = nil)
|
21
|
+
load_nodes(node_or_nil(from_node), node_or_nil(to_node))
|
22
|
+
super(hash_or_nil(from_node, args))
|
32
23
|
end
|
33
24
|
|
34
25
|
def node_cypher_representation(node)
|
@@ -52,5 +43,15 @@ module Neo4j
|
|
52
43
|
end
|
53
44
|
|
54
45
|
ActiveSupport.run_load_hooks(:active_rel, self)
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def node_or_nil(node)
|
50
|
+
node.is_a?(Neo4j::ActiveNode) || node.is_a?(Integer) ? node : nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def hash_or_nil(node_or_hash, hash_or_nil)
|
54
|
+
node_or_hash.is_a?(Hash) ? node_or_hash : hash_or_nil
|
55
|
+
end
|
55
56
|
end
|
56
57
|
end
|
@@ -3,10 +3,24 @@ module Neo4j::ActiveRel
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
include Neo4j::Shared::Persistence
|
5
5
|
|
6
|
+
attr_writer :from_node_identifier, :to_node_identifier
|
7
|
+
|
6
8
|
class RelInvalidError < RuntimeError; end
|
7
9
|
class ModelClassInvalidError < RuntimeError; end
|
8
10
|
class RelCreateFailedError < RuntimeError; end
|
9
11
|
|
12
|
+
def from_node_identifier
|
13
|
+
@from_node_identifier || :from_node
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_node_identifier
|
17
|
+
@to_node_identifier || :to_node
|
18
|
+
end
|
19
|
+
|
20
|
+
def cypher_identifier
|
21
|
+
@cypher_identifier || :rel
|
22
|
+
end
|
23
|
+
|
10
24
|
def save(*)
|
11
25
|
create_or_update
|
12
26
|
end
|
@@ -17,9 +31,9 @@ module Neo4j::ActiveRel
|
|
17
31
|
|
18
32
|
def create_model
|
19
33
|
validate_node_classes!
|
20
|
-
rel = _create_rel
|
21
|
-
return self unless rel.respond_to?(:
|
22
|
-
init_on_load(rel
|
34
|
+
rel = _create_rel
|
35
|
+
return self unless rel.respond_to?(:props)
|
36
|
+
init_on_load(rel, from_node, to_node, @rel_type)
|
23
37
|
true
|
24
38
|
end
|
25
39
|
|
@@ -53,6 +67,10 @@ module Neo4j::ActiveRel
|
|
53
67
|
end
|
54
68
|
end
|
55
69
|
|
70
|
+
def create_method
|
71
|
+
self.class.create_method
|
72
|
+
end
|
73
|
+
|
56
74
|
private
|
57
75
|
|
58
76
|
def validate_node_classes!
|
@@ -81,28 +99,10 @@ module Neo4j::ActiveRel
|
|
81
99
|
"Node class was #{node.class} (#{node.class.object_id}), expected #{type_class} (#{type_class.object_id})"
|
82
100
|
end
|
83
101
|
|
84
|
-
def _create_rel
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
messages << 'to_node ID is nil' if to_node.id.nil?
|
89
|
-
|
90
|
-
fail RelCreateFailedError, "Unable to create relationship (#{messages.join(' / ')})"
|
91
|
-
end
|
92
|
-
_rel_creation_query(from_node, to_node, props)
|
93
|
-
end
|
94
|
-
|
95
|
-
N1_N2_STRING = 'n1, n2'
|
96
|
-
ACTIVEREL_NODE_MATCH_STRING = 'ID(n1) = {n1_neo_id} AND ID(n2) = {n2_neo_id}'
|
97
|
-
def _rel_creation_query(from_node, to_node, props)
|
98
|
-
Neo4j::Session.query.match(N1_N2_STRING)
|
99
|
-
.where(ACTIVEREL_NODE_MATCH_STRING).params(n1_neo_id: from_node.neo_id, n2_neo_id: to_node.neo_id).break
|
100
|
-
.send(create_method, "n1-[r:`#{type}`]->n2")
|
101
|
-
.with('r').set(r: props).pluck(:r).first
|
102
|
-
end
|
103
|
-
|
104
|
-
def create_method
|
105
|
-
self.class.create_method
|
102
|
+
def _create_rel
|
103
|
+
factory = QueryFactory.new(from_node, to_node, self)
|
104
|
+
factory.build!
|
105
|
+
factory.unwrapped_rel
|
106
106
|
end
|
107
107
|
end
|
108
108
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Neo4j::ActiveRel::Persistence
|
2
|
+
# This class builds and executes a Cypher query, using information from the graph objects to determine
|
3
|
+
# whether they need to be created simultaneously.
|
4
|
+
# It keeps the rel instance from being responsible for inspecting the nodes or talking with Shared::QueryFactory.
|
5
|
+
class QueryFactory
|
6
|
+
NODE_SYMBOLS = [:from_node, :to_node]
|
7
|
+
attr_reader :from_node, :to_node, :rel, :unwrapped_rel
|
8
|
+
|
9
|
+
def initialize(from_node, to_node, rel)
|
10
|
+
@from_node = from_node
|
11
|
+
@to_node = to_node
|
12
|
+
@rel = rel
|
13
|
+
end
|
14
|
+
|
15
|
+
# TODO: This feels like it should also wrap the rel, but that is handled in Neo4j::ActiveRel::Persistence at the moment.
|
16
|
+
# Builds and executes the query using the objects giving during init.
|
17
|
+
# It holds the process:
|
18
|
+
# * Execute node callbacks if needed
|
19
|
+
# * Create and execute the query
|
20
|
+
# * Mix the query response into the unpersisted objects given during init
|
21
|
+
def build!
|
22
|
+
node_before_callbacks! do
|
23
|
+
res = query_factory(rel, rel_id, iterative_query).query.unwrapped.return(*unpersisted_return_ids).first
|
24
|
+
node_symbols.each { |n| wrap!(send(n), res, n) }
|
25
|
+
@unwrapped_rel = res.send(rel_id)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def rel_id
|
32
|
+
@rel_id ||= rel.cypher_identifier
|
33
|
+
end
|
34
|
+
|
35
|
+
# Node callbacks only need to be executed if the node is not persisted. We let the `conditional_callback` method do the work,
|
36
|
+
# we only have to give it the type of callback we expect to be run and the condition which, if true, will prevent it from executing.
|
37
|
+
def node_before_callbacks!
|
38
|
+
validate_unpersisted_nodes!
|
39
|
+
from_node.conditional_callback(:create, from_node.persisted?) do
|
40
|
+
to_node.conditional_callback(:create, to_node.persisted?) do
|
41
|
+
yield
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate_unpersisted_nodes!
|
47
|
+
node_symbols.each do |s|
|
48
|
+
obj = send(s)
|
49
|
+
next if obj.persisted?
|
50
|
+
fail RelCreateFailedError, "Cannot create rel with unpersisted, invalid #{s}" unless obj.valid?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Each node must be either created or matched before the relationshp can be created. This class does not know or care about
|
55
|
+
# how that happens, it just knows that it needs a usable Neo4j::Core::Query object to do that.
|
56
|
+
# This method is "iterative" because it creates one factory for each node but the second builds upon the first.
|
57
|
+
def iterative_query
|
58
|
+
node_symbols.inject(false) do |iterative_query, sym|
|
59
|
+
obj = send(sym)
|
60
|
+
query_factory(obj, sym, iterative_query)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Isolates the dependency to the shared class. This has an awareness of Neo4j::Core::Query and will match or create
|
65
|
+
# based on the current state of the object passed in.
|
66
|
+
def query_factory(obj, sym, factory = false)
|
67
|
+
Neo4j::Shared::QueryFactory.create(obj, sym).tap do |factory_instance|
|
68
|
+
factory_instance.base_query = factory.blank? ? false : factory.query
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [Array<Symbol>] The Cypher identifiers that will be returned from the query.
|
73
|
+
# We only need to return objects from our query that were created during it, otherwise we impact performance.
|
74
|
+
def unpersisted_return_ids
|
75
|
+
[rel_id].tap do |result|
|
76
|
+
node_symbols.each { |k| result << k unless send(k).persisted? }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# @param [Neo4j::ActiveNode] node A node, persisted or unpersisted
|
81
|
+
# @param [Struct] res The result of calling `return` on a Neo4j::Core::Query object. It responds to the same keys
|
82
|
+
# as our graph objects. If the object is unpersisted and was created during the query, the unwrapped node is mixed
|
83
|
+
# in, making the object reflect as "persisted".
|
84
|
+
# @param [Symbol] key :from_node or :to_node, the object to request from the response.
|
85
|
+
def wrap!(node, res, key)
|
86
|
+
return if node.persisted? || !res.respond_to?(key)
|
87
|
+
unwrapped = res.send(key)
|
88
|
+
node.init_on_load(unwrapped, unwrapped.props)
|
89
|
+
end
|
90
|
+
|
91
|
+
def node_symbols
|
92
|
+
self.class::NODE_SYMBOLS
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -4,6 +4,7 @@ module Neo4j::ActiveRel
|
|
4
4
|
# will result in a query to load the node if the node is not already loaded.
|
5
5
|
class RelatedNode
|
6
6
|
class InvalidParameterError < StandardError; end
|
7
|
+
class UnsetRelatedNodeError < StandardError; end
|
7
8
|
|
8
9
|
# ActiveRel's related nodes can be initialized with nothing, an integer, or a fully wrapped node.
|
9
10
|
#
|
@@ -11,8 +12,6 @@ module Neo4j::ActiveRel
|
|
11
12
|
#
|
12
13
|
# Initialization with an integer happens when a relationship is loaded from the database. It loads using the ID
|
13
14
|
# because that is provided by the Cypher response and does not require an extra query.
|
14
|
-
#
|
15
|
-
# Initialization with a node doesn't appear to happen in the code. TODO: maybe find out why this is an option.
|
16
15
|
def initialize(node = nil)
|
17
16
|
@node = valid_node_param?(node) ? node : (fail InvalidParameterError, 'RelatedNode must be initialized with either a node ID or node')
|
18
17
|
end
|
@@ -30,20 +29,41 @@ module Neo4j::ActiveRel
|
|
30
29
|
|
31
30
|
# Loads a node from the database or returns the node if already laoded
|
32
31
|
def loaded
|
32
|
+
fail NilRelatedNodeError, 'Node not set, cannot load' if @node.nil?
|
33
33
|
@node = @node.respond_to?(:neo_id) ? @node : Neo4j::Node.load(@node)
|
34
34
|
end
|
35
35
|
|
36
|
+
# @param [String, Symbol, Array] clazz An alternate label to use in the event the node is not present or loaded
|
37
|
+
def cypher_representation(clazz)
|
38
|
+
case
|
39
|
+
when !set?
|
40
|
+
"(#{formatted_label_list(clazz)})"
|
41
|
+
when set? && !loaded?
|
42
|
+
"(Node with neo_id #{@node})"
|
43
|
+
else
|
44
|
+
node_class = self.class
|
45
|
+
id_name = node_class.id_property_name
|
46
|
+
labels = ':' + node_class.mapped_label_names.join(':')
|
47
|
+
|
48
|
+
"(#{labels} {#{id_name}: #{@node.id.inspect}})"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
36
52
|
# @return [Boolean] indicates whether a node has or has not been fully loaded from the database
|
37
53
|
def loaded?
|
38
54
|
@node.respond_to?(:neo_id)
|
39
55
|
end
|
40
56
|
|
57
|
+
def set?
|
58
|
+
!@node.nil?
|
59
|
+
end
|
60
|
+
|
41
61
|
def method_missing(*args, &block)
|
42
62
|
loaded.send(*args, &block)
|
43
63
|
end
|
44
64
|
|
45
65
|
def respond_to_missing?(method_name, include_private = false)
|
46
|
-
loaded if @node.is_a?(
|
66
|
+
loaded if @node.is_a?(Numeric)
|
47
67
|
@node.respond_to?(method_name) ? true : super
|
48
68
|
end
|
49
69
|
|
@@ -53,6 +73,10 @@ module Neo4j::ActiveRel
|
|
53
73
|
|
54
74
|
private
|
55
75
|
|
76
|
+
def formatted_label_list(list)
|
77
|
+
list.is_a?(Array) ? list.join(' || ') : list
|
78
|
+
end
|
79
|
+
|
56
80
|
def valid_node_param?(node)
|
57
81
|
node.nil? || node.is_a?(Integer) || node.respond_to?(:neo_id)
|
58
82
|
end
|
@@ -34,6 +34,14 @@ module Neo4j
|
|
34
34
|
run_callbacks(:touch) { super }
|
35
35
|
end
|
36
36
|
|
37
|
+
# Allows you to perform a callback if a condition is not satisfied.
|
38
|
+
# @param [Symbol] kind The callback type to execute unless the guard is true
|
39
|
+
# @param [TrueClass,FalseClass] guard When this value is true, the block is yielded without executing callbacks.
|
40
|
+
def conditional_callback(kind, guard)
|
41
|
+
return yield if guard
|
42
|
+
run_callbacks(kind) { yield }
|
43
|
+
end
|
44
|
+
|
37
45
|
private
|
38
46
|
|
39
47
|
def create_or_update #:nodoc:
|
@@ -155,6 +155,13 @@ module Neo4j::Shared
|
|
155
155
|
convert_property(key, value, :to_ruby)
|
156
156
|
end
|
157
157
|
|
158
|
+
def inject_defaults!(object, props)
|
159
|
+
declared_property_defaults.each_pair do |k, v|
|
160
|
+
props[k.to_sym] = v if object.send(k).nil? && props[k.to_sym].nil?
|
161
|
+
end
|
162
|
+
props
|
163
|
+
end
|
164
|
+
|
158
165
|
protected
|
159
166
|
|
160
167
|
# Prevents repeated calls to :_attribute_type, which isn't free and never changes.
|
@@ -25,9 +25,9 @@ module Neo4j::Shared
|
|
25
25
|
# @return [Hash]
|
26
26
|
def props_for_create
|
27
27
|
inject_timestamps!
|
28
|
-
|
28
|
+
props_with_defaults = inject_defaults!(props)
|
29
|
+
converted_props = props_for_db(props_with_defaults)
|
29
30
|
inject_classname!(converted_props)
|
30
|
-
inject_defaults!(converted_props)
|
31
31
|
return converted_props unless self.class.respond_to?(:default_property_values)
|
32
32
|
inject_primary_key!(converted_props)
|
33
33
|
end
|
@@ -37,8 +37,8 @@ module Neo4j::Shared
|
|
37
37
|
update_magic_properties
|
38
38
|
changed_props = attributes.select { |k, _| changed_attributes.include?(k) }
|
39
39
|
changed_props.symbolize_keys!
|
40
|
-
props_for_db(changed_props)
|
41
40
|
inject_defaults!(changed_props)
|
41
|
+
props_for_db(changed_props)
|
42
42
|
end
|
43
43
|
|
44
44
|
# Convenience method to set attribute and #save at the same time
|
@@ -204,12 +204,7 @@ module Neo4j::Shared
|
|
204
204
|
self.updated_at ||= now if respond_to?(:updated_at=)
|
205
205
|
end
|
206
206
|
|
207
|
-
|
208
|
-
self.class.declared_properties.declared_property_defaults.each_pair do |k, v|
|
209
|
-
properties[k.to_sym] = v if send(k).nil?
|
210
|
-
end
|
211
|
-
properties
|
212
|
-
end
|
207
|
+
|
213
208
|
|
214
209
|
def set_timestamps
|
215
210
|
warning = 'This method has been replaced with `inject_timestamps!` and will be removed in a future version'.freeze
|
@@ -14,18 +14,32 @@ module Neo4j::Shared
|
|
14
14
|
|
15
15
|
attr_reader :_persisted_obj
|
16
16
|
|
17
|
+
def inspect
|
18
|
+
attribute_descriptions = inspect_attributes.map do |key, value|
|
19
|
+
"#{Neo4j::ANSI::CYAN}#{key}: #{Neo4j::ANSI::CLEAR}#{value.inspect}"
|
20
|
+
end.join(', ')
|
21
|
+
|
22
|
+
separator = ' ' unless attribute_descriptions.empty?
|
23
|
+
"#<#{Neo4j::ANSI::YELLOW}#{self.class.name}#{Neo4j::ANSI::CLEAR}#{separator}#{attribute_descriptions}>"
|
24
|
+
end
|
25
|
+
|
17
26
|
# TODO: Remove the commented :super entirely once this code is part of a release.
|
18
27
|
# It calls an init method in active_attr that has a very negative impact on performance.
|
19
28
|
def initialize(attributes = nil)
|
20
29
|
attributes = process_attributes(attributes)
|
21
30
|
@relationship_props = self.class.extract_association_attributes!(attributes)
|
22
|
-
|
23
|
-
validate_attributes!(
|
31
|
+
modded_attributes = inject_defaults!(attributes)
|
32
|
+
validate_attributes!(modded_attributes)
|
33
|
+
writer_method_props = extract_writer_methods!(modded_attributes)
|
24
34
|
send_props(writer_method_props)
|
25
|
-
|
26
35
|
@_persisted_obj = nil
|
27
36
|
end
|
28
37
|
|
38
|
+
def inject_defaults!(starting_props)
|
39
|
+
return starting_props if self.class.declared_properties.declared_property_defaults.empty?
|
40
|
+
self.class.declared_properties.inject_defaults!(self, starting_props || {})
|
41
|
+
end
|
42
|
+
|
29
43
|
# Returning nil when we get ActiveAttr::UnknownAttributeError from ActiveAttr
|
30
44
|
def read_attribute(name)
|
31
45
|
super(name)
|
@@ -36,7 +50,7 @@ module Neo4j::Shared
|
|
36
50
|
|
37
51
|
def send_props(hash)
|
38
52
|
return hash if hash.blank?
|
39
|
-
hash.each { |key, value|
|
53
|
+
hash.each { |key, value| send("#{key}=", value) }
|
40
54
|
end
|
41
55
|
|
42
56
|
protected
|
@@ -53,9 +67,11 @@ module Neo4j::Shared
|
|
53
67
|
|
54
68
|
# Changes attributes hash to remove relationship keys
|
55
69
|
# Raises an error if there are any keys left which haven't been defined as properties on the model
|
70
|
+
# TODO: use declared_properties instead of self.attributes
|
56
71
|
def validate_attributes!(attributes)
|
57
72
|
return attributes if attributes.blank?
|
58
73
|
invalid_properties = attributes.keys.map(&:to_s) - self.attributes.keys
|
74
|
+
invalid_properties.reject! { |name| self.respond_to?("#{name}=") }
|
59
75
|
fail UndefinedPropertyError, "Undefined properties: #{invalid_properties.join(',')}" if invalid_properties.size > 0
|
60
76
|
end
|
61
77
|
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Neo4j::Shared
|
2
|
+
# Acts as a bridge between the node and rel models and Neo4j::Core::Query.
|
3
|
+
# If the object is persisted, it returns a query matching; otherwise, it returns a query creating it.
|
4
|
+
# This class does not execute queries, so it keeps no record of what identifiers have been set or what has happened in previous factories.
|
5
|
+
class QueryFactory
|
6
|
+
attr_reader :graph_object, :identifier
|
7
|
+
|
8
|
+
def initialize(graph_object, identifier)
|
9
|
+
@graph_object = graph_object
|
10
|
+
@identifier = identifier.to_sym
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.create(graph_object, identifier)
|
14
|
+
factory = case graph_object
|
15
|
+
when Neo4j::ActiveNode
|
16
|
+
NodeQueryFactory
|
17
|
+
when Neo4j::ActiveRel
|
18
|
+
RelQueryFactory
|
19
|
+
else
|
20
|
+
fail "Unable to find factory for #{graph_object}"
|
21
|
+
end
|
22
|
+
factory.new(graph_object, identifier)
|
23
|
+
end
|
24
|
+
|
25
|
+
def query
|
26
|
+
graph_object.persisted? ? match_query : create_query
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param [Neo4j::Core::Query] query An instance of Neo4j::Core::Query upon which methods will be chained.
|
30
|
+
def base_query=(query)
|
31
|
+
return if query.blank?
|
32
|
+
@base_query = query
|
33
|
+
end
|
34
|
+
|
35
|
+
def base_query
|
36
|
+
@base_query || Neo4j::Session.current.query
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def create_query
|
42
|
+
fail 'Abstract class, not implemented'
|
43
|
+
end
|
44
|
+
|
45
|
+
def match_query
|
46
|
+
base_query
|
47
|
+
.match(match_string).where("ID(#{identifier}) = {#{identifier_id}}")
|
48
|
+
.params(identifier_id.to_sym => graph_object.neo_id)
|
49
|
+
end
|
50
|
+
|
51
|
+
def identifier_id
|
52
|
+
@identifier_id ||= "#{identifier}_id"
|
53
|
+
end
|
54
|
+
|
55
|
+
def identifier_params
|
56
|
+
@identifier_params ||= "#{identifier}_params"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class NodeQueryFactory < QueryFactory
|
61
|
+
protected
|
62
|
+
|
63
|
+
def match_string
|
64
|
+
"(#{identifier})"
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_query
|
68
|
+
return match_query if graph_object.persisted?
|
69
|
+
base_query.create(identifier => {graph_object.labels_for_create.join(':').to_sym => graph_object.props_for_create})
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class RelQueryFactory < QueryFactory
|
74
|
+
protected
|
75
|
+
|
76
|
+
def match_string
|
77
|
+
"(#{graph_object.from_node_identifier})-[#{identifier}]->()"
|
78
|
+
end
|
79
|
+
|
80
|
+
def create_query
|
81
|
+
return match_query if graph_object.persisted?
|
82
|
+
base_query.send(graph_object.create_method, query_string).params(identifier_params.to_sym => graph_object.props_for_create)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def query_string
|
88
|
+
"#{graph_object.from_node_identifier}-[#{identifier}:#{graph_object.type} {#{identifier_params}}]->#{graph_object.to_node_identifier}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/neo4j/version.rb
CHANGED
data/neo4j.gemspec
CHANGED
@@ -30,7 +30,7 @@ A Neo4j OGM (Object-Graph-Mapper) for use in Ruby on Rails and Rack frameworks h
|
|
30
30
|
s.add_dependency('activemodel', '~> 4')
|
31
31
|
s.add_dependency('activesupport', '~> 4')
|
32
32
|
s.add_dependency('active_attr', '~> 0.8')
|
33
|
-
s.add_dependency('neo4j-core', '~> 6.0.0.alpha.
|
33
|
+
s.add_dependency('neo4j-core', '~> 6.0.0.alpha.6')
|
34
34
|
s.add_dependency('neo4j-community', '~> 2.0') if RUBY_PLATFORM =~ /java/
|
35
35
|
s.add_development_dependency('railties', '~> 4')
|
36
36
|
s.add_development_dependency('pry')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: neo4j
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.0.0.alpha.
|
4
|
+
version: 6.0.0.alpha.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Ronge, Brian Underwood, Chris Grigg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: orm_adapter
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 6.0.0.alpha.
|
75
|
+
version: 6.0.0.alpha.6
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 6.0.0.alpha.
|
82
|
+
version: 6.0.0.alpha.6
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: railties
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -260,6 +260,7 @@ files:
|
|
260
260
|
- lib/neo4j/active_rel/callbacks.rb
|
261
261
|
- lib/neo4j/active_rel/initialize.rb
|
262
262
|
- lib/neo4j/active_rel/persistence.rb
|
263
|
+
- lib/neo4j/active_rel/persistence/query_factory.rb
|
263
264
|
- lib/neo4j/active_rel/property.rb
|
264
265
|
- lib/neo4j/active_rel/query.rb
|
265
266
|
- lib/neo4j/active_rel/rel_wrapper.rb
|
@@ -283,6 +284,7 @@ files:
|
|
283
284
|
- lib/neo4j/shared/persistence.rb
|
284
285
|
- lib/neo4j/shared/property.rb
|
285
286
|
- lib/neo4j/shared/property/default_property.rb
|
287
|
+
- lib/neo4j/shared/query_factory.rb
|
286
288
|
- lib/neo4j/shared/rel_type_converters.rb
|
287
289
|
- lib/neo4j/shared/serialized_properties.rb
|
288
290
|
- lib/neo4j/shared/type_converters.rb
|