neo4j 7.2.3 → 8.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
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