neo4j 5.2.15 → 6.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -19
  3. data/Gemfile +1 -1
  4. data/lib/neo4j.rb +4 -1
  5. data/lib/neo4j/active_node.rb +6 -15
  6. data/lib/neo4j/active_node/has_n.rb +52 -21
  7. data/lib/neo4j/active_node/has_n/association.rb +1 -1
  8. data/lib/neo4j/active_node/id_property.rb +1 -1
  9. data/lib/neo4j/active_node/id_property/accessor.rb +1 -1
  10. data/lib/neo4j/active_node/labels.rb +7 -101
  11. data/lib/neo4j/active_node/labels/index.rb +87 -0
  12. data/lib/neo4j/active_node/persistence.rb +3 -2
  13. data/lib/neo4j/active_node/query.rb +1 -1
  14. data/lib/neo4j/active_node/query/query_proxy.rb +7 -9
  15. data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +0 -1
  16. data/lib/neo4j/active_node/query/query_proxy_link.rb +12 -4
  17. data/lib/neo4j/active_node/query/query_proxy_methods.rb +4 -6
  18. data/lib/neo4j/active_node/query/query_proxy_unpersisted.rb +4 -8
  19. data/lib/neo4j/active_node/unpersisted.rb +12 -10
  20. data/lib/neo4j/active_node/validations.rb +2 -2
  21. data/lib/neo4j/active_rel.rb +7 -4
  22. data/lib/neo4j/active_rel/persistence.rb +13 -4
  23. data/lib/neo4j/active_rel/query.rb +8 -0
  24. data/lib/neo4j/active_rel/related_node.rb +1 -27
  25. data/lib/neo4j/errors.rb +2 -0
  26. data/lib/neo4j/schema/operation.rb +91 -0
  27. data/lib/neo4j/shared.rb +3 -3
  28. data/lib/neo4j/shared/callbacks.rb +2 -7
  29. data/lib/neo4j/shared/{declared_property_manager.rb → declared_properties.rb} +34 -2
  30. data/lib/neo4j/shared/declared_property.rb +19 -0
  31. data/lib/neo4j/shared/declared_property/index.rb +37 -0
  32. data/lib/neo4j/shared/initialize.rb +2 -2
  33. data/lib/neo4j/shared/persistence.rb +3 -25
  34. data/lib/neo4j/shared/property.rb +24 -10
  35. data/lib/neo4j/shared/type_converters.rb +131 -6
  36. data/lib/neo4j/tasks/migration.rake +3 -3
  37. data/lib/neo4j/type_converters.rb +1 -1
  38. data/lib/neo4j/version.rb +1 -1
  39. data/neo4j.gemspec +2 -2
  40. metadata +13 -10
@@ -0,0 +1,87 @@
1
+ module Neo4j::ActiveNode::Labels
2
+ module Index
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ extend Forwardable
7
+
8
+ def_delegators :declared_properties, :indexed_properties
9
+
10
+ # Creates a Neo4j index on given property
11
+ #
12
+ # This can also be done on the property directly, see Neo4j::ActiveNode::Property::ClassMethods#property.
13
+ #
14
+ # @param [Symbol] property the property we want a Neo4j index on
15
+ #
16
+ # @example
17
+ # class Person
18
+ # include Neo4j::ActiveNode
19
+ # property :name
20
+ # index :name
21
+ # end
22
+ def index(property)
23
+ Neo4j::Session.on_next_session_available do |_|
24
+ declared_properties.index_or_fail!(property, id_property_name)
25
+ schema_create_operation(:index, property)
26
+ end
27
+ end
28
+
29
+ # Creates a neo4j constraint on this class for given property
30
+ #
31
+ # @example
32
+ # Person.constraint :name, type: :unique
33
+ def constraint(property, constraints = {type: :unique})
34
+ Neo4j::Session.on_next_session_available do
35
+ declared_properties.constraint_or_fail!(property, id_property_name)
36
+ schema_create_operation(:constraint, property, constraints)
37
+ end
38
+ end
39
+
40
+ # @param [Symbol] property The name of the property index to be dropped
41
+ def drop_index(property, options = {})
42
+ Neo4j::Session.on_next_session_available do
43
+ declared_properties[property].unindex! if declared_properties[property]
44
+ schema_drop_operation(:index, property, options)
45
+ end
46
+ end
47
+
48
+ # @param [Symbol] property The name of the property constraint to be dropped
49
+ # @param [Hash] constraint The constraint type to be dropped.
50
+ def drop_constraint(property, constraint = {type: :unique})
51
+ Neo4j::Session.on_next_session_available do
52
+ declared_properties[property].unconstraint! if declared_properties[property]
53
+ schema_drop_operation(:constraint, property, constraint)
54
+ end
55
+ end
56
+
57
+ def index?(property)
58
+ mapped_label.indexes[:property_keys].include?([property])
59
+ end
60
+
61
+ def constraint?(property)
62
+ mapped_label.unique_constraints[:property_keys].include?([property])
63
+ end
64
+
65
+ private
66
+
67
+ def schema_create_operation(type, property, options = {})
68
+ new_schema_class(type, property, options).create!
69
+ end
70
+
71
+ def schema_drop_operation(type, property, options = {})
72
+ new_schema_class(type, property, options).drop!
73
+ end
74
+
75
+ def new_schema_class(type, property, options)
76
+ case type
77
+ when :index
78
+ Neo4j::Schema::ExactIndexOperation
79
+ when :constraint
80
+ Neo4j::Schema::UniqueConstraintOperation
81
+ else
82
+ fail "Unknown Schema Operation class #{type}"
83
+ end.new(mapped_label_name, property, options)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -40,7 +40,7 @@ module Neo4j::ActiveNode
40
40
  # @see Neo4j::Rails::Validations Neo4j::Rails::Validations - for the :validate parameter
41
41
  # @see Neo4j::Rails::Callbacks Neo4j::Rails::Callbacks - for callbacks
42
42
  def save!(*args)
43
- fail RecordInvalidError, self unless save(*args)
43
+ save(*args) or fail(RecordInvalidError, self) # rubocop:disable Style/AndOr
44
44
  end
45
45
 
46
46
  # Creates a model with values matching those of the instance attributes and returns its id.
@@ -130,8 +130,9 @@ module Neo4j::ActiveNode
130
130
 
131
131
  def find_or_create(find_attributes, set_attributes = {})
132
132
  on_create_attributes = set_attributes.merge(on_create_props(find_attributes))
133
+ on_match_attributes = set_attributes.merge(on_match_props)
133
134
  neo4j_session.query.merge(n: {self.mapped_label_names => find_attributes})
134
- .on_create_set(n: on_create_attributes)
135
+ .on_create_set(n: on_create_attributes).on_match_set(n: on_match_attributes)
135
136
  .pluck(:n).first
136
137
  end
137
138
 
@@ -68,7 +68,7 @@ module Neo4j
68
68
  # @param [String, Symbol] node_var A string or symbol to use as the starting identifier.
69
69
  # @return [Neo4j::ActiveNode::Query::QueryProxy]
70
70
  def as(node_var)
71
- query_proxy(node: node_var)
71
+ query_proxy(node: node_var, context: self.name)
72
72
  end
73
73
  end
74
74
  end
@@ -149,13 +149,9 @@ module Neo4j
149
149
  alias_method :order_by, :order
150
150
 
151
151
  # Cypher string for the QueryProxy's query. This will not include params. For the full output, see <tt>to_cypher_with_params</tt>.
152
- def to_cypher(*args)
153
- query.to_cypher(*args)
154
- end
152
+ delegate :to_cypher, to: :query
155
153
 
156
- def print_cypher
157
- query.print_cypher
158
- end
154
+ delegate :print_cypher, to: :query
159
155
 
160
156
  # Returns a string of the cypher query with return objects and params
161
157
  # @param [Array] columns array containing symbols of identifiers used in the query
@@ -167,7 +163,7 @@ module Neo4j
167
163
 
168
164
  # To add a relationship for the node for the association on this QueryProxy
169
165
  def <<(other_node)
170
- @start_object._persisted_obj ? create(other_node, {}) : defer_create(other_node, {}, :<<)
166
+ @start_object._persisted_obj ? create(other_node, {}) : defer_create(other_node)
171
167
  self
172
168
  end
173
169
 
@@ -288,7 +284,9 @@ module Neo4j
288
284
  end
289
285
 
290
286
  def _session
291
- @session || (@model && @model.neo4j_session)
287
+ (@session || (@model && @model.neo4j_session)).tap do |session|
288
+ fail 'No session found!' if session.nil?
289
+ end
292
290
  end
293
291
 
294
292
  def _association_arrow(properties = {}, create = false)
@@ -337,7 +335,7 @@ module Neo4j
337
335
 
338
336
  def build_deeper_query_proxy(method, args)
339
337
  new_link.tap do |new_query_proxy|
340
- Link.for_args(@model, method, args).each { |link| new_query_proxy._add_links(link) }
338
+ Link.for_args(@model, method, args, association).each { |link| new_query_proxy._add_links(link) }
341
339
  end
342
340
  end
343
341
  end
@@ -45,7 +45,6 @@ module Neo4j
45
45
 
46
46
  query.optional_match("#{identity}#{association.arrow_cypher}#{association_name}")
47
47
  .where(association.target_where_clause)
48
- .break
49
48
  end
50
49
  end
51
50
  end
@@ -55,7 +55,7 @@ module Neo4j
55
55
  elsif key == model.id_property_name && value.is_a?(Neo4j::ActiveNode)
56
56
  value.id
57
57
  else
58
- model.declared_property_manager.value_for_db(key, value)
58
+ converted_value(model, key, value)
59
59
  end
60
60
 
61
61
  new(:where, ->(v, _) { {v => {key => val}} })
@@ -72,9 +72,11 @@ module Neo4j
72
72
  end
73
73
 
74
74
  # We don't accept strings here. If you want to use a string, just use where.
75
- def for_rel_where_clause(arg, _)
75
+ def for_rel_where_clause(arg, _, association)
76
76
  arg.each_with_object([]) do |(key, value), result|
77
- result << new(:where, ->(_, rel_var) { {rel_var => {key => value}} })
77
+ rel_class = association.relationship_class if association.relationship_class
78
+ val = rel_class ? converted_value(rel_class, key, value) : value
79
+ result << new(:where, ->(_, rel_var) { {rel_var => {key => val}} })
78
80
  end
79
81
  end
80
82
 
@@ -82,9 +84,11 @@ module Neo4j
82
84
  [new(:order, ->(v, _) { arg.is_a?(String) ? arg : {v => arg} })]
83
85
  end
84
86
 
85
- def for_args(model, clause, args)
87
+ def for_args(model, clause, args, association = nil)
86
88
  if [:where, :where_not].include?(clause) && args[0].is_a?(String) # Better way?
87
89
  [for_arg(model, clause, args[0], *args[1..-1])]
90
+ elsif clause == :rel_where
91
+ args.map { |arg| for_arg(model, clause, arg, association) }
88
92
  else
89
93
  args.map { |arg| for_arg(model, clause, arg) }
90
94
  end
@@ -97,6 +101,10 @@ module Neo4j
97
101
  rescue NoMethodError
98
102
  default
99
103
  end
104
+
105
+ def converted_value(model, key, value)
106
+ model.declared_properties.value_for_where(key, value)
107
+ end
100
108
  end
101
109
  end
102
110
  end
@@ -32,13 +32,11 @@ module Neo4j
32
32
 
33
33
  def first_and_last(func, target)
34
34
  new_query, pluck_proc = if self.query.clause?(:order)
35
- new_query = self.query.with(identity)
36
- pluck_proc = proc { |var| "#{func}(COLLECT(#{var})) as #{var}" }
37
- [new_query, pluck_proc]
35
+ [self.query.with(identity),
36
+ proc { |var| "#{func}(COLLECT(#{var})) as #{var}" }]
38
37
  else
39
- new_query = self.order(order_property).limit(1)
40
- pluck_proc = proc { |var| var }
41
- [new_query, pluck_proc]
38
+ [self.order(order_property).limit(1),
39
+ proc { |var| var }]
42
40
  end
43
41
  result = query_with_target(target) do |var|
44
42
  final_pluck = pluck_proc.call(var)
@@ -2,14 +2,10 @@ module Neo4j
2
2
  module ActiveNode
3
3
  module Query
4
4
  module QueryProxyUnpersisted
5
- def defer_create(other_nodes, _properties, operator)
6
- key = [@association.name, [nil, nil, nil]].hash
7
- @start_object.pending_associations[key] = [@association.name, operator]
8
- if @start_object.association_proxy_cache[key]
9
- @start_object.association_proxy_cache[key] << other_nodes
10
- else
11
- @start_object.association_proxy_cache[key] = [other_nodes]
12
- end
5
+ def defer_create(other_node)
6
+ @start_object.pending_associations << @association.name
7
+
8
+ @start_object.association_proxy(@association.name).add_to_cache(other_node)
13
9
  end
14
10
  end
15
11
  end
@@ -2,7 +2,7 @@ module Neo4j
2
2
  module ActiveNode
3
3
  module Unpersisted
4
4
  def pending_associations
5
- @pending_associations ||= {}
5
+ @pending_associations ||= []
6
6
  end
7
7
 
8
8
  def pending_associations?
@@ -17,34 +17,36 @@ module Neo4j
17
17
  def pending_associations_with_nodes
18
18
  return unless pending_associations?
19
19
  {}.tap do |deferred_nodes|
20
- pending_associations.each_pair do |cache_key, (association_name, operator)|
21
- nodes_for_creation = self.persisted? ? association_proxy_cache[cache_key].select { |n| !n.persisted? } : association_proxy_cache[cache_key]
22
- deferred_nodes[association_name] = [nodes_for_creation, operator]
20
+ pending_associations.uniq.each do |association_name|
21
+ nodes_for_creation = association_proxy(association_name)
22
+ nodes_for_creation = nodes_for_creation.reject(&:persisted?) if self.persisted?
23
+
24
+ deferred_nodes[association_name] = nodes_for_creation
23
25
  end
24
26
  end
25
27
  end
26
28
 
27
29
  # @param [Hash] deferred_nodes A hash created by :pending_associations_with_nodes
28
30
  def process_unpersisted_nodes!(deferred_nodes)
29
- deferred_nodes.each_pair do |k, (v, o)|
30
- save_and_associate_queue(k, v, o)
31
+ deferred_nodes.each_pair do |k, v|
32
+ save_and_associate_queue(k, v)
31
33
  end
32
34
  end
33
35
 
34
36
 
35
- def save_and_associate_queue(association_name, node_queue, operator)
36
- association_proc = proc { |node| save_and_associate_node(association_name, node, operator) }
37
+ def save_and_associate_queue(association_name, node_queue)
38
+ association_proc = proc { |node| save_and_associate_node(association_name, node) }
37
39
  node_queue.each do |element|
38
40
  element.is_a?(Array) ? element.each { |node| association_proc.call(node) } : association_proc.call(element)
39
41
  end
40
42
  end
41
43
 
42
- def save_and_associate_node(association_name, node, operator)
44
+ def save_and_associate_node(association_name, node)
43
45
  if node.respond_to?(:changed?)
44
46
  node.save if node.changed? || !node.persisted?
45
47
  fail "Unable to defer node persistence, could not save #{node.inspect}" unless node.persisted?
46
48
  end
47
- operator == :<< ? send(association_name).send(operator, node) : send(:"#{association_name}=", node)
49
+ association_proxy(association_name) << node
48
50
  end
49
51
  end
50
52
  end
@@ -37,10 +37,10 @@ module Neo4j
37
37
  # TODO: Added as find(:name => nil) throws error
38
38
  value = '' if value.nil?
39
39
 
40
- conditions[attribute] = options[:case_sensitive] ? value : /^#{Regexp.escape(value.to_s)}$/i
40
+ conditions[attribute] = options[:case_sensitive] ? value : /#{Regexp.escape(value.to_s)}/i
41
41
 
42
42
  found = record.class.as(:result).where(conditions)
43
- found = found.where('ID(result) <> {record_neo_id}').params(record_neo_id: record.neo_id) if record._persisted_obj
43
+ found = found.where_not(neo_id: record.neo_id) if record._persisted_obj
44
44
  found
45
45
  end
46
46
 
@@ -27,13 +27,16 @@ module Neo4j
27
27
  attribute_descriptions = attribute_pairs.join(', ')
28
28
  separator = ' ' unless attribute_descriptions.empty?
29
29
 
30
- cypher_representation = "#{node_cypher_representation(:from)}-[:#{type}]->#{node_cypher_representation(:to)}"
30
+ cypher_representation = "#{node_cypher_representation(from_node)}-[:#{type}]->#{node_cypher_representation(to_node)}"
31
31
  "#<#{self.class.name} #{cypher_representation}#{separator}#{attribute_descriptions}>"
32
32
  end
33
33
 
34
- def node_cypher_representation(direction)
35
- node = send(:"#{direction}_node")
36
- node.cypher_representation(self.class.send(:"#{direction}_class"))
34
+ def node_cypher_representation(node)
35
+ node_class = node.class
36
+ id_name = node_class.id_property_name
37
+ labels = ':' + node_class.mapped_label_names.join(':')
38
+
39
+ "(#{labels} {#{id_name}: #{node.id.inspect}})"
37
40
  end
38
41
 
39
42
  def neo4j_obj
@@ -12,7 +12,7 @@ module Neo4j::ActiveRel
12
12
  end
13
13
 
14
14
  def save!(*args)
15
- fail RelInvalidError, self unless save(*args)
15
+ save(*args) or fail(RelInvalidError, self) # rubocop:disable Style/AndOr
16
16
  end
17
17
 
18
18
  def create_model
@@ -60,14 +60,23 @@ module Neo4j::ActiveRel
60
60
  type = from_node == node ? :_from_class : :_to_class
61
61
  type_class = self.class.send(type)
62
62
 
63
- next if [:any, false].include?(type_class)
64
-
65
- unless node.class.mapped_label_names.include?(type_class.to_s.constantize.mapped_label_name)
63
+ unless valid_type?(type_class, node)
66
64
  fail ModelClassInvalidError, type_validation_error_message(node, type_class)
67
65
  end
68
66
  end
69
67
  end
70
68
 
69
+ def valid_type?(type_object, node)
70
+ case type_object
71
+ when false, :any
72
+ true
73
+ when Array
74
+ type_object.any? { |c| valid_type?(c, node) }
75
+ else
76
+ node.class.mapped_label_names.include?(type_object.to_s.constantize.mapped_label_name)
77
+ end
78
+ end
79
+
71
80
  def type_validation_error_message(node, type_class)
72
81
  "Node class was #{node.class} (#{node.class.object_id}), expected #{type_class} (#{type_class.object_id})"
73
82
  end
@@ -41,11 +41,17 @@ module Neo4j::ActiveRel
41
41
 
42
42
  private
43
43
 
44
+ def deprecation_warning!
45
+ ActiveSupport::Deprecation.warn 'The Neo4j::ActiveRel::Query module has been deprecated and will be removed in a future version of the gem.', caller
46
+ end
47
+
44
48
  def where_query
49
+ deprecation_warning!
45
50
  Neo4j::Session.query.match("#{cypher_string(:outbound)}-[r1:`#{self._type}`]->#{cypher_string(:inbound)}")
46
51
  end
47
52
 
48
53
  def all_query
54
+ deprecation_warning!
49
55
  Neo4j::Session.query.match("#{cypher_string}-[r1:`#{self._type}`]->#{cypher_string(:inbound)}")
50
56
  end
51
57
 
@@ -71,6 +77,8 @@ module Neo4j::ActiveRel
71
77
  given_class.constantize
72
78
  when Symbol
73
79
  given_class.to_s.constantize
80
+ when Array
81
+ fail "ActiveRel query methods are being deprecated and do not support Array (from|to)_class options. Current value: #{given_class}"
74
82
  else
75
83
  given_class
76
84
  end
@@ -4,7 +4,6 @@ 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
8
7
 
9
8
  # ActiveRel's related nodes can be initialized with nothing, an integer, or a fully wrapped node.
10
9
  #
@@ -31,41 +30,20 @@ module Neo4j::ActiveRel
31
30
 
32
31
  # Loads a node from the database or returns the node if already laoded
33
32
  def loaded
34
- fail NilRelatedNodeError, 'Node not set, cannot load' if @node.nil?
35
33
  @node = @node.respond_to?(:neo_id) ? @node : Neo4j::Node.load(@node)
36
34
  end
37
35
 
38
- # @param [String, Symbol, Array] clazz An alternate label to use in the event the node is not present or loaded
39
- def cypher_representation(clazz)
40
- case
41
- when !set?
42
- "(#{formatted_label_list(clazz)})"
43
- when set? && !loaded?
44
- "(Node with neo_id #{@node})"
45
- else
46
- node_class = self.class
47
- id_name = node_class.id_property_name
48
- labels = ':' + node_class.mapped_label_names.join(':')
49
-
50
- "(#{labels} {#{id_name}: #{@node.id.inspect}})"
51
- end
52
- end
53
-
54
36
  # @return [Boolean] indicates whether a node has or has not been fully loaded from the database
55
37
  def loaded?
56
38
  @node.respond_to?(:neo_id)
57
39
  end
58
40
 
59
- def set?
60
- !@node.nil?
61
- end
62
-
63
41
  def method_missing(*args, &block)
64
42
  loaded.send(*args, &block)
65
43
  end
66
44
 
67
45
  def respond_to_missing?(method_name, include_private = false)
68
- loaded if @node.is_a?(Numeric)
46
+ loaded if @node.is_a?(Integer)
69
47
  @node.respond_to?(method_name) ? true : super
70
48
  end
71
49
 
@@ -75,10 +53,6 @@ module Neo4j::ActiveRel
75
53
 
76
54
  private
77
55
 
78
- def formatted_label_list(list)
79
- list.is_a?(Array) ? list.join(' || ') : list
80
- end
81
-
82
56
  def valid_node_param?(node)
83
57
  node.nil? || node.is_a?(Integer) || node.respond_to?(:neo_id)
84
58
  end