neo4j 4.1.5 → 5.0.0.rc.1
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 +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
|