neo4j 4.1.5 → 5.0.0.rc.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +584 -0
- data/CONTRIBUTORS +7 -28
- data/Gemfile +6 -1
- data/README.md +54 -8
- data/lib/neo4j.rb +5 -0
- data/lib/neo4j/active_node.rb +1 -0
- data/lib/neo4j/active_node/dependent/association_methods.rb +35 -17
- data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +21 -19
- data/lib/neo4j/active_node/has_n.rb +377 -132
- data/lib/neo4j/active_node/has_n/association.rb +77 -38
- data/lib/neo4j/active_node/id_property.rb +46 -28
- data/lib/neo4j/active_node/initialize.rb +18 -6
- data/lib/neo4j/active_node/labels.rb +69 -35
- data/lib/neo4j/active_node/node_wrapper.rb +37 -30
- data/lib/neo4j/active_node/orm_adapter.rb +5 -4
- data/lib/neo4j/active_node/persistence.rb +53 -10
- data/lib/neo4j/active_node/property.rb +13 -5
- data/lib/neo4j/active_node/query.rb +11 -10
- data/lib/neo4j/active_node/query/query_proxy.rb +126 -153
- data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +15 -25
- data/lib/neo4j/active_node/query/query_proxy_link.rb +89 -0
- data/lib/neo4j/active_node/query/query_proxy_methods.rb +72 -19
- data/lib/neo4j/active_node/query_methods.rb +3 -1
- data/lib/neo4j/active_node/scope.rb +17 -21
- data/lib/neo4j/active_node/validations.rb +8 -2
- data/lib/neo4j/active_rel/initialize.rb +1 -2
- data/lib/neo4j/active_rel/persistence.rb +21 -33
- data/lib/neo4j/active_rel/property.rb +4 -2
- data/lib/neo4j/active_rel/types.rb +20 -8
- data/lib/neo4j/config.rb +16 -6
- data/lib/neo4j/core/query.rb +2 -2
- data/lib/neo4j/errors.rb +10 -0
- data/lib/neo4j/migration.rb +57 -46
- data/lib/neo4j/paginated.rb +3 -1
- data/lib/neo4j/railtie.rb +26 -14
- data/lib/neo4j/shared.rb +7 -1
- data/lib/neo4j/shared/declared_property.rb +62 -0
- data/lib/neo4j/shared/declared_property_manager.rb +150 -0
- data/lib/neo4j/shared/persistence.rb +15 -8
- data/lib/neo4j/shared/property.rb +64 -49
- data/lib/neo4j/shared/rel_type_converters.rb +13 -12
- data/lib/neo4j/shared/serialized_properties.rb +0 -15
- data/lib/neo4j/shared/type_converters.rb +53 -47
- data/lib/neo4j/shared/typecaster.rb +49 -0
- data/lib/neo4j/version.rb +1 -1
- data/lib/rails/generators/neo4j/model/model_generator.rb +3 -3
- data/lib/rails/generators/neo4j_generator.rb +5 -12
- data/neo4j.gemspec +4 -3
- metadata +30 -11
- data/CHANGELOG +0 -545
@@ -9,13 +9,12 @@ module Neo4j
|
|
9
9
|
# The <tt>node</tt> and <tt>rel</tt> params are typically used by those other methods but there's nothing stopping you from
|
10
10
|
# using `your_node.each(true, true)` instead of `your_node.each_with_rel`.
|
11
11
|
# @return [Enumerable] An enumerable containing some combination of nodes and rels.
|
12
|
-
def each(node = true, rel = nil, &
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
12
|
+
def each(node = true, rel = nil, &block)
|
13
|
+
pluck_vars = []
|
14
|
+
pluck_vars << identity if node
|
15
|
+
pluck_vars << @rel_var if rel
|
16
|
+
|
17
|
+
pluck(*pluck_vars).each(&block)
|
19
18
|
end
|
20
19
|
|
21
20
|
# When called at the end of a QueryProxy chain, it will return the resultant relationship objects intead of nodes.
|
@@ -41,25 +40,16 @@ module Neo4j
|
|
41
40
|
|
42
41
|
# For getting variables which have been defined as part of the association chain
|
43
42
|
def pluck(*args)
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
# @param [String,Symbol] node The string or symbol of the node to return from the database.
|
52
|
-
# @param [String,Symbol] rel The string or symbol of a relationship to return from the database.
|
53
|
-
def enumerable_query(node, rel = nil)
|
54
|
-
pluck_this = rel.nil? ? [node] : [node, rel]
|
55
|
-
return self.pluck(*pluck_this) if @association.nil? || caller.nil?
|
56
|
-
cypher_string = self.to_cypher_with_params(pluck_this)
|
57
|
-
association_collection = caller.association_instance_get(cypher_string, @association)
|
58
|
-
if association_collection.nil?
|
59
|
-
association_collection = self.pluck(*pluck_this)
|
60
|
-
caller.association_instance_set(cypher_string, association_collection, @association) unless association_collection.empty?
|
43
|
+
transformable_attributes = (model ? model.attribute_names : []) + %w(uuid neo_id)
|
44
|
+
arg_list = args.map do |arg|
|
45
|
+
if transformable_attributes.include?(arg.to_s)
|
46
|
+
{identity => arg}
|
47
|
+
else
|
48
|
+
arg
|
49
|
+
end
|
61
50
|
end
|
62
|
-
|
51
|
+
|
52
|
+
self.query.pluck(*arg_list)
|
63
53
|
end
|
64
54
|
end
|
65
55
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module ActiveNode
|
3
|
+
module Query
|
4
|
+
class QueryProxy
|
5
|
+
class Link
|
6
|
+
attr_reader :clause
|
7
|
+
|
8
|
+
def initialize(clause, arg, args = [])
|
9
|
+
@clause = clause
|
10
|
+
@arg = arg
|
11
|
+
@args = args
|
12
|
+
end
|
13
|
+
|
14
|
+
def args(var, rel_var)
|
15
|
+
@arg.respond_to?(:call) ? @arg.call(var, rel_var) : [@arg, @args].flatten
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
def for_clause(clause, arg, model, *args)
|
20
|
+
method_to_call = "for_#{clause}_clause"
|
21
|
+
|
22
|
+
send(method_to_call, arg, model, *args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def for_where_clause(arg, model, *args)
|
26
|
+
node_num = 1
|
27
|
+
result = []
|
28
|
+
if arg.is_a?(Hash)
|
29
|
+
arg.each do |key, value|
|
30
|
+
if model && model.association?(key)
|
31
|
+
result += for_association(key, value, "n#{node_num}", model)
|
32
|
+
|
33
|
+
node_num += 1
|
34
|
+
else
|
35
|
+
result << new(:where, ->(v, _) { {v => {key => value}} })
|
36
|
+
end
|
37
|
+
end
|
38
|
+
elsif arg.is_a?(String)
|
39
|
+
result << new(:where, arg, args)
|
40
|
+
end
|
41
|
+
result
|
42
|
+
end
|
43
|
+
alias_method :for_node_where_clause, :for_where_clause
|
44
|
+
|
45
|
+
def for_association(name, value, n_string, model)
|
46
|
+
neo_id = value.try(:neo_id) || value
|
47
|
+
fail ArgumentError, "Invalid value for '#{name}' condition" if not neo_id.is_a?(Integer)
|
48
|
+
|
49
|
+
dir = model.associations[name].direction
|
50
|
+
|
51
|
+
arrow = dir == :out ? '-->' : '<--'
|
52
|
+
[
|
53
|
+
new(:match, ->(v, _) { "#{v}#{arrow}(#{n_string})" }),
|
54
|
+
new(:where, ->(_, _) { {"ID(#{n_string})" => neo_id.to_i} })
|
55
|
+
]
|
56
|
+
end
|
57
|
+
|
58
|
+
# We don't accept strings here. If you want to use a string, just use where.
|
59
|
+
def for_rel_where_clause(arg, _)
|
60
|
+
arg.each_with_object([]) do |(key, value), result|
|
61
|
+
result << new(:where, ->(_, rel_var) { {rel_var => {key => value}} })
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def for_order_clause(arg, _)
|
66
|
+
[new(:order, ->(v, _) { arg.is_a?(String) ? arg : {v => arg} })]
|
67
|
+
end
|
68
|
+
|
69
|
+
def for_args(model, clause, args)
|
70
|
+
if clause == :where && args[0].is_a?(String) # Better way?
|
71
|
+
[for_arg(model, :where, args[0], *args[1..-1])]
|
72
|
+
else
|
73
|
+
args.map { |arg| for_arg(model, clause, arg) }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def for_arg(model, clause, arg, *args)
|
78
|
+
default = [Link.new(clause, arg, *args)]
|
79
|
+
|
80
|
+
Link.for_clause(clause, arg, model, *args) || default
|
81
|
+
rescue NoMethodError
|
82
|
+
default
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -3,31 +3,56 @@ module Neo4j
|
|
3
3
|
module Query
|
4
4
|
module QueryProxyMethods
|
5
5
|
class InvalidParameterError < StandardError; end
|
6
|
+
FIRST = 'HEAD'
|
7
|
+
LAST = 'LAST'
|
6
8
|
|
7
9
|
def first(target = nil)
|
8
|
-
|
10
|
+
first_and_last(FIRST, target)
|
9
11
|
end
|
10
12
|
|
11
13
|
def last(target = nil)
|
12
|
-
|
14
|
+
first_and_last(LAST, target)
|
15
|
+
end
|
16
|
+
|
17
|
+
def first_and_last(func, target)
|
18
|
+
new_query, pluck_proc = if self.query.clause?(:order)
|
19
|
+
new_query = self.query.with(identity)
|
20
|
+
pluck_proc = proc { |var| "#{func}(COLLECT(#{var})) as #{var}" }
|
21
|
+
[new_query, pluck_proc]
|
22
|
+
else
|
23
|
+
new_query = self.order(order).limit(1)
|
24
|
+
pluck_proc = proc { |var| var }
|
25
|
+
[new_query, pluck_proc]
|
26
|
+
end
|
27
|
+
result = query_with_target(target) do |var|
|
28
|
+
final_pluck = pluck_proc.call(var)
|
29
|
+
new_query.pluck(final_pluck)
|
30
|
+
end
|
31
|
+
result.first
|
13
32
|
end
|
14
33
|
|
15
|
-
|
16
|
-
self.order(order).limit(1).pluck(target).first
|
17
|
-
end
|
34
|
+
private :first_and_last
|
18
35
|
|
19
36
|
# @return [Integer] number of nodes of this class
|
20
37
|
def count(distinct = nil, target = nil)
|
21
38
|
fail(InvalidParameterError, ':count accepts `distinct` or nil as a parameter') unless distinct.nil? || distinct == :distinct
|
22
39
|
query_with_target(target) do |var|
|
23
40
|
q = distinct.nil? ? var : "DISTINCT #{var}"
|
24
|
-
self.query.
|
41
|
+
limited_query = self.query.clause?(:limit) ? self.query.with(var) : self.query.reorder
|
42
|
+
limited_query.pluck("count(#{q}) AS #{var}").first
|
25
43
|
end
|
26
44
|
end
|
27
45
|
|
28
46
|
alias_method :size, :count
|
29
47
|
alias_method :length, :count
|
30
48
|
|
49
|
+
# TODO: update this with public API methods if/when they are exposed
|
50
|
+
def limit_value
|
51
|
+
return unless self.query.clause?(:limit)
|
52
|
+
limit_clause = self.query.send(:clauses).select { |clause| clause.is_a?(Neo4j::Core::QueryClauses::LimitClause) }.first
|
53
|
+
limit_clause.instance_variable_get(:@arg)
|
54
|
+
end
|
55
|
+
|
31
56
|
def empty?(target = nil)
|
32
57
|
query_with_target(target) { |var| !self.exists?(nil, var) }
|
33
58
|
end
|
@@ -59,7 +84,7 @@ module Neo4j
|
|
59
84
|
rescue Neo4j::Session::CypherError
|
60
85
|
self.query.delete(target).exec
|
61
86
|
end
|
62
|
-
|
87
|
+
clear_source_object_cache
|
63
88
|
end
|
64
89
|
end
|
65
90
|
|
@@ -71,12 +96,11 @@ module Neo4j
|
|
71
96
|
# @param [#neo_id, String, Enumerable] node A node, a string representing a node's ID, or an enumerable of nodes or IDs.
|
72
97
|
# @return [Neo4j::ActiveNode::Query::QueryProxy] A QueryProxy object upon which you can build.
|
73
98
|
def match_to(node)
|
74
|
-
|
75
|
-
|
99
|
+
first_node = node.is_a?(Array) ? node.first : node
|
100
|
+
where_arg = if first_node.respond_to?(:neo_id)
|
101
|
+
{neo_id: node.is_a?(Array) ? node.map(&:neo_id) : node}
|
76
102
|
elsif !node.nil?
|
77
|
-
|
78
|
-
node = ids_array(node) if node.is_a?(Array)
|
79
|
-
{id_key => node}
|
103
|
+
{association_id_key => node.is_a?(Array) ? ids_array(node) : node}
|
80
104
|
else
|
81
105
|
# support for null object pattern
|
82
106
|
'1 = 2'
|
@@ -84,6 +108,7 @@ module Neo4j
|
|
84
108
|
self.where(where_arg)
|
85
109
|
end
|
86
110
|
|
111
|
+
|
87
112
|
# Gives you the first relationship between the last link of a QueryProxy chain and a given node
|
88
113
|
# Shorthand for `MATCH (start)-[r]-(other_node) WHERE ID(other_node) = #{other_node.neo_id} RETURN r`
|
89
114
|
# @param [#neo_id, String, Enumerable] node An object to be sent to `match_to`. See params for that method.
|
@@ -103,25 +128,53 @@ module Neo4j
|
|
103
128
|
# Deletes the relationship between a node and its last link in the QueryProxy chain. Executed in the database, callbacks will not run.
|
104
129
|
def delete(node)
|
105
130
|
self.match_to(node).query.delete(rel_var).exec
|
106
|
-
|
131
|
+
clear_source_object_cache
|
132
|
+
end
|
133
|
+
|
134
|
+
# Deletes the relationships between all nodes for the last step in the QueryProxy chain. Executed in the database, callbacks will not be run.
|
135
|
+
def delete_all_rels
|
136
|
+
self.query.delete(rel_var).exec
|
137
|
+
end
|
138
|
+
|
139
|
+
# Deletes the relationships between all nodes for the last step in the QueryProxy chain and replaces them with relationships to the given nodes.
|
140
|
+
# Executed in the database, callbacks will not be run.
|
141
|
+
def replace_with(node_or_nodes)
|
142
|
+
nodes = Array(node_or_nodes)
|
143
|
+
|
144
|
+
self.delete_all_rels
|
145
|
+
nodes.each { |node| self << node }
|
107
146
|
end
|
108
147
|
|
109
148
|
# Returns all relationships between a node and its last link in the QueryProxy chain, destroys them in Ruby. Callbacks will be run.
|
110
149
|
def destroy(node)
|
111
150
|
self.rels_to(node).map!(&:destroy)
|
112
|
-
|
151
|
+
clear_source_object_cache
|
113
152
|
end
|
114
153
|
|
115
154
|
# A shortcut for attaching a new, optional match to the end of a QueryProxy chain.
|
116
|
-
# TODO: It's silly that we have to call constantize here. There should be a better way of finding the target class of the destination.
|
117
155
|
def optional(association, node_var = nil, rel_var = nil)
|
118
|
-
self.send(association, node_var, rel_var,
|
156
|
+
self.send(association, node_var, rel_var, optional: true)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Takes an Array of ActiveNode models and applies the appropriate WHERE clause
|
160
|
+
# So for a `Teacher` model inheriting from a `Person` model and an `Article` model
|
161
|
+
# if you called .as_models([Teacher, Article])
|
162
|
+
# The where clause would look something like:
|
163
|
+
# WHERE (node_var:Teacher:Person OR node_var:Article)
|
164
|
+
def as_models(models)
|
165
|
+
where_clause = models.map do |model|
|
166
|
+
"`#{identity}`:" + model.mapped_label_names.map do |mapped_label_name|
|
167
|
+
"`#{mapped_label_name}`"
|
168
|
+
end.join(':')
|
169
|
+
end.join(' OR ')
|
170
|
+
|
171
|
+
where("(#{where_clause})")
|
119
172
|
end
|
120
173
|
|
121
174
|
private
|
122
175
|
|
123
|
-
def
|
124
|
-
self.
|
176
|
+
def clear_source_object_cache
|
177
|
+
self.source_object.clear_association_cache if self.source_object.respond_to?(:clear_association_cache)
|
125
178
|
end
|
126
179
|
|
127
180
|
# @return [String] The primary key of a the current QueryProxy's model or target class
|
@@ -132,7 +185,7 @@ module Neo4j
|
|
132
185
|
# @param [Enumerable] node An enumerable of nodes or ids.
|
133
186
|
# @return [Array] An array after having `id` called on each object
|
134
187
|
def ids_array(node)
|
135
|
-
node.first.respond_to?(:id) ? node.map
|
188
|
+
node.first.respond_to?(:id) ? node.map(&:id) : node
|
136
189
|
end
|
137
190
|
|
138
191
|
def query_with_target(target)
|
@@ -4,7 +4,9 @@ module Neo4j
|
|
4
4
|
class InvalidParameterError < StandardError; end
|
5
5
|
|
6
6
|
def exists?(node_condition = nil)
|
7
|
-
|
7
|
+
unless node_condition.is_a?(Integer) || node_condition.is_a?(Hash) || node_condition.nil?
|
8
|
+
fail(InvalidParameterError, ':exists? only accepts ids or conditions')
|
9
|
+
end
|
8
10
|
query_start = exists_query_start(node_condition)
|
9
11
|
start_q = query_start.respond_to?(:query_as) ? query_start.query_as(:n) : query_start
|
10
12
|
start_q.return('COUNT(n) AS count').first.count > 0
|
@@ -12,7 +12,7 @@ module Neo4j::ActiveNode
|
|
12
12
|
# include Neo4j::ActiveNode
|
13
13
|
# property :name
|
14
14
|
# property :score
|
15
|
-
# has_many :out, :friends, model_class: self
|
15
|
+
# has_many :out, :friends, type: :has_friend, model_class: self
|
16
16
|
# scope :top_students, -> { where(score: 42)}") }
|
17
17
|
# end
|
18
18
|
# Person.top_students.to_a
|
@@ -28,7 +28,7 @@ module Neo4j::ActiveNode
|
|
28
28
|
# include Neo4j::ActiveNode
|
29
29
|
# property :name
|
30
30
|
# property :score
|
31
|
-
# has_many :out, :friends, model_class: self
|
31
|
+
# has_many :out, :friends, type: :has_friend, model_class: self
|
32
32
|
# scope :great_students, ->(identifier) { where("#{identifier}.score > 41") }
|
33
33
|
# end
|
34
34
|
# Person.as(:all_people).great_students(:all_people).to_a
|
@@ -37,21 +37,18 @@ module Neo4j::ActiveNode
|
|
37
37
|
def scope(name, proc)
|
38
38
|
_scope[name.to_sym] = proc
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
proc = self.class._scope[:"#{name}"]
|
44
|
-
self.class._call_scope_context(eval_context, query_params, proc)
|
45
|
-
end
|
46
|
-
}, __FILE__, __LINE__)
|
40
|
+
define_method(name) do |query_params = nil, some_var = nil|
|
41
|
+
self.class.send(name, query_params, some_var, current_scope)
|
42
|
+
end
|
47
43
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
44
|
+
klass = class << self; self; end
|
45
|
+
klass.instance_eval do
|
46
|
+
define_method(name) do |query_params = nil, _ = nil|
|
47
|
+
eval_context = ScopeEvalContext.new(self, current_scope || self.query_proxy)
|
48
|
+
proc = _scope[name.to_sym]
|
52
49
|
_call_scope_context(eval_context, query_params, proc)
|
53
50
|
end
|
54
|
-
|
51
|
+
end
|
55
52
|
end
|
56
53
|
|
57
54
|
# rubocop:disable Style/PredicateName
|
@@ -87,12 +84,11 @@ module Neo4j::ActiveNode
|
|
87
84
|
ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope)
|
88
85
|
end
|
89
86
|
|
90
|
-
|
91
|
-
def all
|
87
|
+
def all(var = :n)
|
92
88
|
if current_scope
|
93
|
-
current_scope.
|
89
|
+
current_scope.new_link(var)
|
94
90
|
else
|
95
|
-
self.as(
|
91
|
+
self.as(var)
|
96
92
|
end
|
97
93
|
end
|
98
94
|
end
|
@@ -140,9 +136,9 @@ module Neo4j::ActiveNode
|
|
140
136
|
private
|
141
137
|
|
142
138
|
def raise_invalid_scope_type!(scope_type)
|
143
|
-
if
|
144
|
-
|
145
|
-
|
139
|
+
return if VALID_SCOPE_TYPES.include?(scope_type)
|
140
|
+
|
141
|
+
fail ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
|
146
142
|
end
|
147
143
|
end
|
148
144
|
end
|
@@ -26,6 +26,12 @@ module Neo4j
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def validate_each(record, attribute, value)
|
29
|
+
return unless found(record, attribute, value).exists?
|
30
|
+
|
31
|
+
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(value: value))
|
32
|
+
end
|
33
|
+
|
34
|
+
def found(record, attribute, value)
|
29
35
|
conditions = scope_conditions(record)
|
30
36
|
|
31
37
|
# TODO: Added as find(:name => nil) throws error
|
@@ -34,8 +40,8 @@ module Neo4j
|
|
34
40
|
conditions[attribute] = options[:case_sensitive] ? value : /^#{Regexp.escape(value.to_s)}$/i
|
35
41
|
|
36
42
|
found = record.class.as(:result).where(conditions)
|
37
|
-
found = found.where('
|
38
|
-
|
43
|
+
found = found.where('ID(result) <> {record_neo_id}').params(record_neo_id: record.neo_id) if record.persisted?
|
44
|
+
found
|
39
45
|
end
|
40
46
|
|
41
47
|
def message(instance)
|
@@ -1,7 +1,6 @@
|
|
1
1
|
module Neo4j::ActiveRel
|
2
2
|
module Initialize
|
3
3
|
extend ActiveSupport::Concern
|
4
|
-
include Neo4j::Shared::TypeConverters
|
5
4
|
|
6
5
|
attr_reader :_persisted_obj
|
7
6
|
|
@@ -17,7 +16,7 @@ module Neo4j::ActiveRel
|
|
17
16
|
@attributes = attributes.merge(persisted_rel.props.stringify_keys)
|
18
17
|
load_nodes(from_node_id, to_node_id)
|
19
18
|
self.default_properties = persisted_rel.props
|
20
|
-
@attributes = convert_properties_to :ruby, @attributes
|
19
|
+
@attributes = self.class.declared_property_manager.convert_properties_to(self, :ruby, @attributes)
|
21
20
|
end
|
22
21
|
|
23
22
|
# Implements the Neo4j::Node#wrapper and Neo4j::Relationship#wrapper method
|
@@ -7,7 +7,10 @@ module Neo4j::ActiveRel
|
|
7
7
|
class ModelClassInvalidError < RuntimeError; end
|
8
8
|
class RelCreateFailedError < RuntimeError; end
|
9
9
|
|
10
|
-
|
10
|
+
# Should probably find a way to not need this
|
11
|
+
def association_proxy_cache
|
12
|
+
{}
|
13
|
+
end
|
11
14
|
|
12
15
|
def save(*)
|
13
16
|
update_magic_properties
|
@@ -19,10 +22,10 @@ module Neo4j::ActiveRel
|
|
19
22
|
end
|
20
23
|
|
21
24
|
def create_model(*)
|
22
|
-
|
25
|
+
validate_node_classes!
|
23
26
|
create_magic_properties
|
24
27
|
set_timestamps
|
25
|
-
properties = convert_properties_to :db, props
|
28
|
+
properties = self.class.declared_property_manager.convert_properties_to(self, :db, props)
|
26
29
|
rel = _create_rel(from_node, to_node, properties)
|
27
30
|
return self unless rel.respond_to?(:_persisted_obj)
|
28
31
|
init_on_load(rel._persisted_obj, from_node, to_node, @rel_type)
|
@@ -50,11 +53,14 @@ module Neo4j::ActiveRel
|
|
50
53
|
|
51
54
|
private
|
52
55
|
|
53
|
-
def
|
56
|
+
def validate_node_classes!
|
54
57
|
[from_node, to_node].each do |node|
|
55
58
|
type = from_node == node ? :_from_class : :_to_class
|
56
|
-
|
57
|
-
|
59
|
+
type_class = self.class.send(type)
|
60
|
+
|
61
|
+
next if [:any, false].include?(type_class)
|
62
|
+
|
63
|
+
fail ModelClassInvalidError, "Node class was #{node.class}, expected #{type_class}" unless node.is_a?(type_class.to_s.constantize)
|
58
64
|
end
|
59
65
|
end
|
60
66
|
|
@@ -62,40 +68,22 @@ module Neo4j::ActiveRel
|
|
62
68
|
props = self.class.default_property_values(self)
|
63
69
|
props.merge!(args[0]) if args[0].is_a?(Hash)
|
64
70
|
set_classname(props, true)
|
65
|
-
_rel_creation_query(from_node, to_node, props)
|
66
|
-
end
|
67
71
|
|
68
|
-
|
69
|
-
|
70
|
-
case given_class
|
71
|
-
when String
|
72
|
-
given_class.constantize
|
73
|
-
when Symbol
|
74
|
-
given_class.to_s.constantize
|
75
|
-
else
|
76
|
-
given_class
|
72
|
+
if from_node.id.nil? || to_node.id.nil?
|
73
|
+
fail RelCreateFailedError, "Unable to create relationship (id is nil). from_node: #{from_node}, to_node: #{to_node}"
|
77
74
|
end
|
78
|
-
|
79
|
-
|
80
|
-
def allows_any_class?(type)
|
81
|
-
self.class.send(type) == :any || self.class.send(type) == false
|
75
|
+
_rel_creation_query(from_node, to_node, props)
|
82
76
|
end
|
83
77
|
|
84
78
|
private
|
85
79
|
|
80
|
+
N1_N2_STRING = 'n1, n2'
|
81
|
+
ACTIVEREL_NODE_MATCH_STRING = 'ID(n1) = {n1_neo_id} AND ID(n2) = {n2_neo_id}'
|
86
82
|
def _rel_creation_query(from_node, to_node, props)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
.where("n1.#{from_class.primary_key} = {from_node_id}")
|
92
|
-
.where("n2.#{to_class.primary_key} = {to_node_id}")
|
93
|
-
.params(from_node_id: from_node.id, to_node_id: to_node.id)
|
94
|
-
.send(create_method, ("(n1)-[r:`#{type}`]->(n2)"))
|
95
|
-
.with('r').set(r: props).return(:r).first.r
|
96
|
-
rescue NoMethodError => e
|
97
|
-
raise RelCreateFailedError, "Unable to create relationship. from_node: #{from_node}, to_node: #{to_node}, error: #{e}"
|
98
|
-
end
|
83
|
+
Neo4j::Session.query.match(N1_N2_STRING)
|
84
|
+
.where(ACTIVEREL_NODE_MATCH_STRING).params(n1_neo_id: from_node.neo_id, n2_neo_id: to_node.neo_id).break
|
85
|
+
.send(create_method, "n1-[r:`#{type}`]->n2")
|
86
|
+
.with('r').set(r: props).pluck(:r).first
|
99
87
|
end
|
100
88
|
|
101
89
|
def create_method
|