neo4j 6.0.0.alpha.9 → 6.0.0.alpha.11
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/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
|