neo4j 7.2.3 → 8.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -46
  3. data/Gemfile +15 -14
  4. data/README.md +21 -14
  5. data/bin/neo4j-jars +1 -1
  6. data/lib/neo4j.rb +12 -1
  7. data/lib/neo4j/active_base.rb +68 -0
  8. data/lib/neo4j/active_base/session_registry.rb +12 -0
  9. data/lib/neo4j/active_node.rb +13 -21
  10. data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +6 -6
  11. data/lib/neo4j/active_node/enum.rb +3 -6
  12. data/lib/neo4j/active_node/has_n.rb +24 -19
  13. data/lib/neo4j/active_node/has_n/association.rb +6 -2
  14. data/lib/neo4j/active_node/has_n/association/rel_factory.rb +1 -1
  15. data/lib/neo4j/active_node/has_n/association/rel_wrapper.rb +1 -1
  16. data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +1 -1
  17. data/lib/neo4j/active_node/id_property.rb +52 -15
  18. data/lib/neo4j/active_node/labels.rb +32 -10
  19. data/lib/neo4j/active_node/labels/index.rb +5 -55
  20. data/lib/neo4j/active_node/node_list_formatter.rb +13 -0
  21. data/lib/neo4j/active_node/node_wrapper.rb +39 -37
  22. data/lib/neo4j/active_node/persistence.rb +27 -13
  23. data/lib/neo4j/active_node/query/query_proxy.rb +11 -9
  24. data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +4 -4
  25. data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +1 -0
  26. data/lib/neo4j/active_node/query/query_proxy_link.rb +13 -9
  27. data/lib/neo4j/active_node/query/query_proxy_methods.rb +76 -8
  28. data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +1 -1
  29. data/lib/neo4j/active_node/query_methods.rb +3 -3
  30. data/lib/neo4j/active_node/scope.rb +24 -7
  31. data/lib/neo4j/active_rel.rb +21 -3
  32. data/lib/neo4j/active_rel/initialize.rb +2 -2
  33. data/lib/neo4j/active_rel/persistence.rb +32 -6
  34. data/lib/neo4j/active_rel/persistence/query_factory.rb +3 -3
  35. data/lib/neo4j/active_rel/property.rb +9 -9
  36. data/lib/neo4j/active_rel/query.rb +6 -4
  37. data/lib/neo4j/active_rel/rel_wrapper.rb +24 -16
  38. data/lib/neo4j/active_rel/related_node.rb +5 -1
  39. data/lib/neo4j/active_rel/types.rb +2 -2
  40. data/lib/neo4j/config.rb +0 -1
  41. data/lib/neo4j/errors.rb +3 -0
  42. data/lib/neo4j/migration.rb +90 -71
  43. data/lib/neo4j/migrations.rb +10 -0
  44. data/lib/neo4j/migrations/base.rb +44 -0
  45. data/lib/neo4j/migrations/helpers.rb +101 -0
  46. data/lib/neo4j/migrations/helpers/id_property.rb +75 -0
  47. data/lib/neo4j/migrations/helpers/relationships.rb +66 -0
  48. data/lib/neo4j/migrations/helpers/schema.rb +53 -0
  49. data/lib/neo4j/migrations/migration_file.rb +24 -0
  50. data/lib/neo4j/migrations/runner.rb +110 -0
  51. data/lib/neo4j/migrations/schema_migration.rb +9 -0
  52. data/lib/neo4j/model_schema.rb +100 -0
  53. data/lib/neo4j/railtie.rb +29 -110
  54. data/lib/neo4j/schema/operation.rb +24 -13
  55. data/lib/neo4j/session_manager.rb +137 -0
  56. data/lib/neo4j/shared.rb +20 -11
  57. data/lib/neo4j/shared/attributes.rb +10 -16
  58. data/lib/neo4j/shared/callbacks.rb +3 -3
  59. data/lib/neo4j/shared/cypher.rb +1 -1
  60. data/lib/neo4j/shared/declared_properties.rb +1 -1
  61. data/lib/neo4j/shared/declared_property.rb +1 -1
  62. data/lib/neo4j/shared/enum.rb +6 -18
  63. data/lib/neo4j/shared/identity.rb +27 -21
  64. data/lib/neo4j/shared/persistence.rb +26 -17
  65. data/lib/neo4j/shared/property.rb +5 -2
  66. data/lib/neo4j/shared/query_factory.rb +4 -5
  67. data/lib/neo4j/shared/type_converters.rb +8 -9
  68. data/lib/neo4j/shared/validations.rb +1 -5
  69. data/lib/neo4j/tasks/migration.rake +83 -2
  70. data/lib/neo4j/version.rb +1 -1
  71. data/lib/rails/generators/neo4j/migration/migration_generator.rb +14 -0
  72. data/lib/rails/generators/neo4j/migration/templates/migration.erb +9 -0
  73. data/lib/rails/generators/neo4j/model/model_generator.rb +1 -3
  74. data/lib/rails/generators/neo4j_generator.rb +1 -0
  75. data/neo4j.gemspec +3 -3
  76. metadata +58 -65
  77. data/bin/rake +0 -17
  78. data/lib/neo4j/shared/permitted_attributes.rb +0 -28
@@ -6,12 +6,9 @@ module Neo4j::ActiveNode
6
6
  module ClassMethods
7
7
  protected
8
8
 
9
- def build_property_options(enum_keys, options = {})
10
- if options[:_index]
11
- super.merge!(index: :exact)
12
- else
13
- super
14
- end
9
+ def define_property(property_name, *args)
10
+ super
11
+ Neo4j::ModelSchema.add_required_index(self, property_name)
15
12
  end
16
13
 
17
14
  def define_enum_methods(property_name, enum_keys, options)
@@ -23,11 +23,8 @@ module Neo4j::ActiveNode
23
23
  # States:
24
24
  # Default
25
25
  def inspect
26
- if @cached_result
27
- result_nodes.inspect
28
- else
29
- "#<AssociationProxy @query_proxy=#{@query_proxy.inspect}>"
30
- end
26
+ formatted_nodes = ::Neo4j::ActiveNode::NodeListFormatter.new(result_nodes)
27
+ "#<AssociationProxy #{@query_proxy.context} #{formatted_nodes.inspect}>"
31
28
  end
32
29
 
33
30
  extend Forwardable
@@ -212,7 +209,9 @@ module Neo4j::ActiveNode
212
209
  end
213
210
  end
214
211
 
212
+ # rubocop:disable Metrics/ModuleLength
215
213
  module ClassMethods
214
+ # rubocop:enable Style/PredicateName
216
215
  # rubocop:disable Style/PredicateName
217
216
 
218
217
  # :nocov:
@@ -229,21 +228,18 @@ module Neo4j::ActiveNode
229
228
  !!associations[name.to_sym]
230
229
  end
231
230
 
231
+ def parent_associations
232
+ superclass == Object ? {} : superclass.associations
233
+ end
234
+
232
235
  def associations
233
- @associations ||= {}
236
+ (@associations ||= parent_associations.dup)
234
237
  end
235
238
 
236
239
  def associations_keys
237
240
  @associations_keys ||= associations.keys
238
241
  end
239
242
 
240
- # make sure the inherited classes inherit the <tt>_decl_rels</tt> hash
241
- def inherited(klass)
242
- klass.instance_variable_set(:@associations, associations.clone)
243
- @associations_keys = klass.associations_keys.clone
244
- super
245
- end
246
-
247
243
  # For defining an "has many" association on a model. This defines a set of methods on
248
244
  # your model instances. For instance, if you define the association on a Person model:
249
245
  #
@@ -379,7 +375,7 @@ module Neo4j::ActiveNode
379
375
 
380
376
  clear_deferred_nodes_for_association(name)
381
377
 
382
- Neo4j::Transaction.run { association_proxy(name).replace_with(other_nodes) }
378
+ self.class.run_transaction { association_proxy(name).replace_with(other_nodes) }
383
379
  end
384
380
  end
385
381
 
@@ -455,7 +451,7 @@ module Neo4j::ActiveNode
455
451
  if persisted?
456
452
  other_node.save if other_node.respond_to?(:persisted?) && !other_node.persisted?
457
453
  association_proxy_cache.clear # TODO: Should probably just clear for this association...
458
- Neo4j::Transaction.run { association_proxy(name).replace_with(other_node) }
454
+ self.class.run_transaction { association_proxy(name).replace_with(other_node) }
459
455
  # handle_non_persisted_node(other_node)
460
456
  else
461
457
  defer_create(name, other_node, clear: true)
@@ -509,24 +505,33 @@ module Neo4j::ActiveNode
509
505
 
510
506
  def default_association_query_proxy
511
507
  Neo4j::ActiveNode::Query::QueryProxy.new("::#{self.name}".constantize, nil,
512
- session: neo4j_session, query_proxy: nil, context: "#{self.name}")
508
+ session: neo4j_session, query_proxy: nil, context: self.name.to_s)
513
509
  end
514
510
 
515
511
  def build_association(macro, direction, name, options)
516
512
  options[:model_class] = options[:model_class].name if options[:model_class] == self
517
513
  Neo4j::ActiveNode::HasN::Association.new(macro, direction, name, options).tap do |association|
518
- @associations ||= {}
519
- @associations[name] = association
514
+ add_association(name, association)
520
515
  create_reflection(macro, name, association, self)
521
516
  end
522
517
 
523
- associations_keys << name
518
+ @associations_keys = nil
524
519
 
525
520
  # Re-raise any exception with added class name and association name to
526
521
  # make sure error message is helpful
527
522
  rescue StandardError => e
528
523
  raise e.class, "#{e.message} (#{self.class}##{name})"
529
524
  end
525
+
526
+ def add_association(name, association_object)
527
+ fail "Association `#{name}` defined for a second time. "\
528
+ 'Associations can only be defined once' if duplicate_association?(name)
529
+ associations[name] = association_object
530
+ end
531
+
532
+ def duplicate_association?(name)
533
+ associations.key?(name) && parent_associations[name] != associations[name]
534
+ end
530
535
  end
531
536
  end
532
537
  end
@@ -67,6 +67,10 @@ module Neo4j
67
67
  end
68
68
  end
69
69
 
70
+ def inverse_of?(other)
71
+ origin_association == other
72
+ end
73
+
70
74
  def target_classes
71
75
  ClassArguments.constantize_argument(target_class_names)
72
76
  end
@@ -151,7 +155,7 @@ module Neo4j
151
155
  def relationship_class?
152
156
  !!relationship_class
153
157
  end
154
- alias_method :rel_class?, :relationship_class?
158
+ alias rel_class? relationship_class?
155
159
 
156
160
  private
157
161
 
@@ -216,7 +220,7 @@ module Neo4j
216
220
  message = case
217
221
  when (message = type_keys_error_message(options.keys))
218
222
  message
219
- when (unknown_keys = options.keys - VALID_ASSOCIATION_OPTION_KEYS).size > 0
223
+ when !(unknown_keys = options.keys - VALID_ASSOCIATION_OPTION_KEYS).empty?
220
224
  "Unknown option(s) specified: #{unknown_keys.join(', ')}"
221
225
  end
222
226
 
@@ -44,7 +44,7 @@ module Neo4j::ActiveNode::HasN
44
44
 
45
45
  def _match_query(other_node, wrapper)
46
46
  nodes = _nodes_for_create(other_node, wrapper.from_node_identifier, wrapper.to_node_identifier)
47
- Neo4j::Session.current.query.match_nodes(nodes)
47
+ Neo4j::ActiveBase.new_query.match_nodes(nodes)
48
48
  end
49
49
 
50
50
  def _nodes_for_create(other_node, from_node_id, to_node_id)
@@ -7,7 +7,7 @@ class Neo4j::ActiveNode::HasN::Association
7
7
  attr_reader :type, :association
8
8
  attr_accessor :properties
9
9
  private :association
10
- alias_method :props_for_create, :properties
10
+ alias props_for_create properties
11
11
 
12
12
  def initialize(association, properties = {})
13
13
  @association = association
@@ -39,7 +39,7 @@ module Neo4j
39
39
  p = properties.map do |key, value|
40
40
  "#{key}: #{value.inspect}"
41
41
  end.join(', ')
42
- p.size == 0 ? '' : " {#{p}}"
42
+ p.empty? ? '' : " {#{p}}"
43
43
  end
44
44
 
45
45
  VALID_REL_LENGTH_SYMBOLS = {
@@ -40,6 +40,8 @@ module Neo4j::ActiveNode
40
40
  def define_id_methods(clazz, name, conf)
41
41
  validate_conf!(conf)
42
42
 
43
+ return if name == :neo_id
44
+
43
45
  if conf[:on]
44
46
  define_custom_method(clazz, name, conf[:on])
45
47
  elsif conf[:auto]
@@ -125,7 +127,7 @@ module Neo4j::ActiveNode
125
127
  attr_accessor :manual_id_property
126
128
 
127
129
  def find_by_neo_id(id)
128
- Neo4j::Node.load(id)
130
+ find_by(neo_id: id)
129
131
  end
130
132
 
131
133
  def find_by_id(id)
@@ -136,13 +138,11 @@ module Neo4j::ActiveNode
136
138
  all.where(id_property_name => ids).to_a
137
139
  end
138
140
 
139
- def id_property(name, conf = {})
141
+ def id_property(name, conf = {}, inherited = false)
140
142
  self.manual_id_property = true
141
- Neo4j::Session.on_next_session_available do |_|
142
- @id_property_info = {name: name, type: conf}
143
- TypeMethods.define_id_methods(self, name, conf)
144
- constraint(name, type: :unique) unless conf[:constraint] == false
145
- end
143
+
144
+ @id_property_info = {name: name, type: conf, inherited: inherited}
145
+ TypeMethods.define_id_methods(self, name, conf)
146
146
  end
147
147
 
148
148
  # rubocop:disable Style/PredicateName
@@ -158,6 +158,8 @@ module Neo4j::ActiveNode
158
158
  end
159
159
 
160
160
  def id_property_info
161
+ ensure_id_property_info!
162
+
161
163
  @id_property_info ||= {}
162
164
  end
163
165
 
@@ -169,18 +171,53 @@ module Neo4j::ActiveNode
169
171
  !!manual_id_property
170
172
  end
171
173
 
172
- alias_method :primary_key, :id_property_name
174
+ alias primary_key id_property_name
175
+
176
+ # Since there's no way to know when a class is done being described, we wait until the id_property
177
+ # information is requested and use that as the opportunity to set up the defaults if no others are specified
178
+ def ensure_id_property_info!
179
+ if !manual_id_property? && !@id_property_info
180
+ name, type, value = id_property_name_type_value
181
+ id_property(name, type => value)
182
+ end
183
+
184
+ handle_model_schema!
185
+ end
173
186
 
174
187
  private
175
188
 
176
- def id_property_constraint(name)
177
- if id_property?
178
- unless mapped_label.uniqueness_constraints[:property_keys].include?([name])
179
- # Neo4j Embedded throws a crazy error when a constraint can't be dropped
180
- drop_constraint(id_property_name, type: :unique) if constraint?(mapped_label_name, id_property_name)
181
- end
189
+ def handle_model_schema!
190
+ id_property_name = @id_property_info[:name]
191
+ if @id_property_info[:type][:constraint] == false &&
192
+ !@id_property_info[:inherited] &&
193
+ !@id_property_info[:warned_of_constraint]
194
+ @id_property_info[:warned_of_constraint] = true
195
+ warn_constraint_option_false!(id_property_name)
196
+ return
182
197
  end
183
- rescue Neo4j::Server::CypherResponse::ResponseError, Java::OrgNeo4jCypher::CypherExecutionException
198
+
199
+ return if id_property_name == :neo_id || @id_property_info[:inherited]
200
+
201
+ Neo4j::ModelSchema.add_defined_constraint(self, id_property_name)
202
+ end
203
+
204
+ def warn_constraint_option_false!(id_property_name)
205
+ Neo4j::ActiveBase.logger.warn <<MSG
206
+ WARNING: The constraint option for id_property is no longer supported (Used on #{self.name}.#{id_property_name}).
207
+ Since you specified `constraint: false` this option can simply be removed.
208
+ MSG
209
+ end
210
+
211
+ def id_property_name_type_value
212
+ name, type, value = Neo4j::Config.to_hash.values_at(*%w(id_property id_property_type id_property_type_value))
213
+
214
+ if !(name && type && value)
215
+ name = :uuid
216
+ type = :auto
217
+ value = :uuid
218
+ end
219
+
220
+ [name, type, value]
184
221
  end
185
222
  end
186
223
  end
@@ -1,3 +1,5 @@
1
+ require 'neo4j/core/label'
2
+
1
3
  module Neo4j
2
4
  module ActiveNode
3
5
  # Provides a mapping between neo4j labels and Ruby classes
@@ -11,7 +13,11 @@ module Neo4j
11
13
  MODELS_FOR_LABELS_CACHE.clear
12
14
 
13
15
  included do |model|
14
- Neo4j::ActiveNode::Labels.clear_wrapped_models
16
+ def self.inherited(model)
17
+ add_wrapped_class(model)
18
+
19
+ super
20
+ end
15
21
 
16
22
  Neo4j::ActiveNode::Labels.add_wrapped_class(model) unless Neo4j::ActiveNode::Labels._wrapped_classes.include?(model)
17
23
  end
@@ -31,15 +37,32 @@ module Neo4j
31
37
 
32
38
  # adds one or more labels
33
39
  # @see Neo4j-core
34
- def add_label(*label)
35
- @_persisted_obj.add_label(*label)
40
+ def add_labels(*labels)
41
+ labels.inject(query_as(:n)) do |query, label|
42
+ query.set("n:`#{label}`")
43
+ end.exec
44
+ @_persisted_obj.labels.concat(labels)
45
+ @_persisted_obj.labels.uniq!
46
+ end
47
+
48
+ # Remove this method in 9.0.0
49
+ def add_label(*_labels)
50
+ fail 'add_label has been removed in favor of `add_labels`'
36
51
  end
37
52
 
38
53
  # Removes one or more labels
39
54
  # Be careful, don't remove the label representing the Ruby class.
40
55
  # @see Neo4j-core
41
- def remove_label(*label)
42
- @_persisted_obj.remove_label(*label)
56
+ def remove_labels(*labels)
57
+ labels.inject(query_as(:n)) do |query, label|
58
+ query.remove("n:`#{label}`")
59
+ end.exec
60
+ labels.each(&@_persisted_obj.labels.method(:delete))
61
+ end
62
+
63
+ # Remove this method in 9.0.0
64
+ def remove_label(*_labels)
65
+ fail 'remove_label has been removed in favor of `remove_labels`'
43
66
  end
44
67
 
45
68
  def self._wrapped_classes
@@ -56,7 +79,7 @@ module Neo4j
56
79
  return MODELS_FOR_LABELS_CACHE[labels] if MODELS_FOR_LABELS_CACHE[labels]
57
80
 
58
81
  models = WRAPPED_CLASSES.select do |model|
59
- (model.mapped_label_names - labels).size == 0
82
+ (model.mapped_label_names - labels).empty?
60
83
  end
61
84
 
62
85
  MODELS_FOR_LABELS_CACHE[labels] = models.max_by do |model|
@@ -66,7 +89,7 @@ module Neo4j
66
89
 
67
90
  def self.clear_wrapped_models
68
91
  MODELS_FOR_LABELS_CACHE.clear
69
- Neo4j::Node::Wrapper::CONSTANTS_FOR_LABELS_CACHE.clear
92
+ Neo4j::NodeWrapping::CONSTANTS_FOR_LABELS_CACHE.clear
70
93
  end
71
94
 
72
95
  module ClassMethods
@@ -100,8 +123,7 @@ module Neo4j
100
123
 
101
124
  # Deletes all nodes and connected relationships from Cypher.
102
125
  def delete_all
103
- self.neo4j_session._query("MATCH (n:`#{mapped_label_name}`) OPTIONAL MATCH (n)-[r]-() DELETE n,r")
104
- self.neo4j_session._query("MATCH (n:`#{mapped_label_name}`) DELETE n")
126
+ neo4j_query("MATCH (n:`#{mapped_label_name}`) OPTIONAL MATCH (n)-[r]-() DELETE n,r")
105
127
  end
106
128
 
107
129
  # Returns each node to Ruby and calls `destroy`. Be careful, as this can be a very slow operation if you have many nodes. It will generate at least
@@ -122,7 +144,7 @@ module Neo4j
122
144
 
123
145
  # @return [Neo4j::Label] the label for this class
124
146
  def mapped_label
125
- Neo4j::Label.create(mapped_label_name)
147
+ Neo4j::Core::Label.new(mapped_label_name, neo4j_session)
126
148
  end
127
149
 
128
150
  def base_class
@@ -20,67 +20,17 @@ module Neo4j::ActiveNode::Labels
20
20
  # index :name
21
21
  # end
22
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
23
+ return if Neo4j::ModelSchema.defined_constraint?(self, property)
24
+
25
+ Neo4j::ModelSchema.add_defined_index(self, property)
27
26
  end
28
27
 
29
28
  # Creates a neo4j constraint on this class for given property
30
29
  #
31
30
  # @example
32
31
  # 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)
32
+ def constraint(property, _constraints = {type: :unique})
33
+ Neo4j::ModelSchema.add_defined_constraint(self, property)
84
34
  end
85
35
  end
86
36
  end
@@ -0,0 +1,13 @@
1
+ module Neo4j::ActiveNode
2
+ class NodeListFormatter
3
+ def initialize(list, max_elements = 5)
4
+ @list = list
5
+ @max_elements = max_elements
6
+ end
7
+
8
+ def inspect
9
+ return @list.inspect if !@max_elements || @list.length <= @max_elements
10
+ "[#{@list.take(5).map!(&:inspect).join(', ')}, ...]"
11
+ end
12
+ end
13
+ end