neo4j 5.2.15 → 6.0.0.alpha.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 (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