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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +584 -0
  3. data/CONTRIBUTORS +7 -28
  4. data/Gemfile +6 -1
  5. data/README.md +54 -8
  6. data/lib/neo4j.rb +5 -0
  7. data/lib/neo4j/active_node.rb +1 -0
  8. data/lib/neo4j/active_node/dependent/association_methods.rb +35 -17
  9. data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +21 -19
  10. data/lib/neo4j/active_node/has_n.rb +377 -132
  11. data/lib/neo4j/active_node/has_n/association.rb +77 -38
  12. data/lib/neo4j/active_node/id_property.rb +46 -28
  13. data/lib/neo4j/active_node/initialize.rb +18 -6
  14. data/lib/neo4j/active_node/labels.rb +69 -35
  15. data/lib/neo4j/active_node/node_wrapper.rb +37 -30
  16. data/lib/neo4j/active_node/orm_adapter.rb +5 -4
  17. data/lib/neo4j/active_node/persistence.rb +53 -10
  18. data/lib/neo4j/active_node/property.rb +13 -5
  19. data/lib/neo4j/active_node/query.rb +11 -10
  20. data/lib/neo4j/active_node/query/query_proxy.rb +126 -153
  21. data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +15 -25
  22. data/lib/neo4j/active_node/query/query_proxy_link.rb +89 -0
  23. data/lib/neo4j/active_node/query/query_proxy_methods.rb +72 -19
  24. data/lib/neo4j/active_node/query_methods.rb +3 -1
  25. data/lib/neo4j/active_node/scope.rb +17 -21
  26. data/lib/neo4j/active_node/validations.rb +8 -2
  27. data/lib/neo4j/active_rel/initialize.rb +1 -2
  28. data/lib/neo4j/active_rel/persistence.rb +21 -33
  29. data/lib/neo4j/active_rel/property.rb +4 -2
  30. data/lib/neo4j/active_rel/types.rb +20 -8
  31. data/lib/neo4j/config.rb +16 -6
  32. data/lib/neo4j/core/query.rb +2 -2
  33. data/lib/neo4j/errors.rb +10 -0
  34. data/lib/neo4j/migration.rb +57 -46
  35. data/lib/neo4j/paginated.rb +3 -1
  36. data/lib/neo4j/railtie.rb +26 -14
  37. data/lib/neo4j/shared.rb +7 -1
  38. data/lib/neo4j/shared/declared_property.rb +62 -0
  39. data/lib/neo4j/shared/declared_property_manager.rb +150 -0
  40. data/lib/neo4j/shared/persistence.rb +15 -8
  41. data/lib/neo4j/shared/property.rb +64 -49
  42. data/lib/neo4j/shared/rel_type_converters.rb +13 -12
  43. data/lib/neo4j/shared/serialized_properties.rb +0 -15
  44. data/lib/neo4j/shared/type_converters.rb +53 -47
  45. data/lib/neo4j/shared/typecaster.rb +49 -0
  46. data/lib/neo4j/version.rb +1 -1
  47. data/lib/rails/generators/neo4j/model/model_generator.rb +3 -3
  48. data/lib/rails/generators/neo4j_generator.rb +5 -12
  49. data/neo4j.gemspec +4 -3
  50. metadata +30 -11
  51. 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, &_block)
13
- if node && rel
14
- enumerable_query(identity, rel_var).each { |returned_node, returned_rel| yield returned_node, returned_rel }
15
- else
16
- pluck_this = !rel ? identity : @rel_var
17
- enumerable_query(pluck_this).each { |returned_node| yield returned_node }
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
- self.query.pluck(*args)
45
- end
46
-
47
- private
48
-
49
- # Executes the query against the database if the results are not already present in a node's association cache. This method is
50
- # shared by <tt>each</tt>, <tt>each_rel</tt>, and <tt>each_with_rel</tt>.
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
- association_collection
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
- query_with_target(target) { |var| first_and_last("ID(#{var})", var) }
10
+ first_and_last(FIRST, target)
9
11
  end
10
12
 
11
13
  def last(target = nil)
12
- query_with_target(target) { |var| first_and_last("ID(#{var}) DESC", var) }
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
- def first_and_last(order, target)
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.reorder.pluck("count(#{q}) AS #{var}").first
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
- clear_caller_cache
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
- where_arg = if node.respond_to?(:neo_id)
75
- {neo_id: node.neo_id}
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
- id_key = association_id_key
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
- clear_caller_cache
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
- clear_caller_cache
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, nil, optional: true)
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 clear_caller_cache
124
- self.caller.clear_association_cache if self.caller.respond_to?(:clear_association_cache)
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!(&:id) : node
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
- fail(InvalidParameterError, ':exists? only accepts ids or conditions') unless node_condition.is_a?(Integer) || node_condition.is_a?(Hash) || node_condition.nil?
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
- module_eval(%{
41
- def #{name}(query_params=nil, _=nil, query_proxy=nil)
42
- eval_context = ScopeEvalContext.new(self, query_proxy || self.class.query_proxy)
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
- instance_eval(%{
49
- def #{name}(query_params=nil, _=nil, query_proxy=nil)
50
- eval_context = ScopeEvalContext.new(self, query_proxy || self.query_proxy)
51
- proc = _scope[:"#{name}"]
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
- }, __FILE__, __LINE__)
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.clone
89
+ current_scope.new_link(var)
94
90
  else
95
- self.as(:n)
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 !VALID_SCOPE_TYPES.include?(scope_type)
144
- fail ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
145
- end
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('NOT ID(result) = {record_neo_id}').params(record_neo_id: record.neo_id) if record.persisted?
38
- record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(value: value)) if found.exists?
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
- def clear_association_cache; end
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
- confirm_node_classes
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 confirm_node_classes
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
- next if allows_any_class?(type)
57
- fail ModelClassInvalidError, "Node class was #{node.class}, expected #{self.class.send(type)}" unless class_as_constant(type) == node.class || node.class.ancestors.include?(class_as_constant(type))
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
- def class_as_constant(type)
69
- given_class = self.class.send(type)
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
- end
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
- from_class = from_node.class
88
- to_class = to_node.class
89
- begin
90
- Neo4j::Session.query.match(n1: from_class.mapped_label_name, n2: to_class.mapped_label_name)
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