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.
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