neo4j 4.0.0 → 4.1.0

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +10 -0
  3. data/Gemfile +6 -16
  4. data/README.md +5 -2
  5. data/bin/neo4j-jars +6 -6
  6. data/lib/neo4j.rb +13 -9
  7. data/lib/neo4j/active_node.rb +9 -9
  8. data/lib/neo4j/active_node/dependent.rb +11 -0
  9. data/lib/neo4j/active_node/dependent/association_methods.rb +28 -0
  10. data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +48 -0
  11. data/lib/neo4j/active_node/has_n.rb +124 -112
  12. data/lib/neo4j/active_node/has_n/association.rb +45 -30
  13. data/lib/neo4j/active_node/id_property.rb +22 -19
  14. data/lib/neo4j/active_node/initialize.rb +2 -4
  15. data/lib/neo4j/active_node/labels.rb +23 -22
  16. data/lib/neo4j/active_node/node_wrapper.rb +5 -8
  17. data/lib/neo4j/active_node/orm_adapter.rb +2 -4
  18. data/lib/neo4j/active_node/persistence.rb +5 -10
  19. data/lib/neo4j/active_node/property.rb +3 -4
  20. data/lib/neo4j/active_node/query.rb +27 -6
  21. data/lib/neo4j/active_node/query/query_proxy.rb +65 -110
  22. data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +67 -0
  23. data/lib/neo4j/active_node/query/query_proxy_find_in_batches.rb +0 -1
  24. data/lib/neo4j/active_node/query/query_proxy_methods.rb +29 -28
  25. data/lib/neo4j/active_node/query_methods.rb +6 -6
  26. data/lib/neo4j/active_node/reflection.rb +3 -2
  27. data/lib/neo4j/active_node/rels.rb +1 -1
  28. data/lib/neo4j/active_node/scope.rb +13 -8
  29. data/lib/neo4j/active_node/validations.rb +5 -6
  30. data/lib/neo4j/active_rel.rb +1 -2
  31. data/lib/neo4j/active_rel/callbacks.rb +3 -3
  32. data/lib/neo4j/active_rel/persistence.rb +9 -7
  33. data/lib/neo4j/active_rel/property.rb +12 -4
  34. data/lib/neo4j/active_rel/query.rb +6 -8
  35. data/lib/neo4j/active_rel/rel_wrapper.rb +0 -2
  36. data/lib/neo4j/active_rel/related_node.rb +4 -5
  37. data/lib/neo4j/active_rel/types.rb +4 -6
  38. data/lib/neo4j/active_rel/validations.rb +0 -1
  39. data/lib/neo4j/config.rb +11 -23
  40. data/lib/neo4j/core/query.rb +1 -1
  41. data/lib/neo4j/migration.rb +17 -18
  42. data/lib/neo4j/paginated.rb +4 -4
  43. data/lib/neo4j/railtie.rb +19 -19
  44. data/lib/neo4j/shared.rb +7 -3
  45. data/lib/neo4j/shared/callbacks.rb +15 -4
  46. data/lib/neo4j/shared/identity.rb +2 -2
  47. data/lib/neo4j/shared/persistence.rb +10 -21
  48. data/lib/neo4j/shared/property.rb +17 -30
  49. data/lib/neo4j/shared/rel_type_converters.rb +1 -3
  50. data/lib/neo4j/shared/type_converters.rb +13 -25
  51. data/lib/neo4j/shared/validations.rb +3 -3
  52. data/lib/neo4j/tasks/migration.rake +7 -7
  53. data/lib/neo4j/type_converters.rb +1 -1
  54. data/lib/neo4j/version.rb +1 -1
  55. data/lib/rails/generators/neo4j/model/model_generator.rb +16 -12
  56. data/lib/rails/generators/neo4j_generator.rb +18 -18
  57. data/neo4j.gemspec +22 -18
  58. metadata +103 -1
@@ -5,16 +5,16 @@ module Neo4j
5
5
  module HasN
6
6
  class Association
7
7
  include Neo4j::Shared::RelTypeConverters
8
- attr_reader :type, :name, :relationship, :direction
8
+ include Neo4j::ActiveNode::Dependent::AssociationMethods
9
+ attr_reader :type, :name, :relationship, :direction, :dependent
9
10
 
10
11
  def initialize(type, direction, name, options = {})
11
- check_valid_type_and_dir(type, direction)
12
+ validate_init_arguments(type, direction, options)
12
13
  @type = type.to_sym
13
14
  @name = name
14
15
  @direction = direction.to_sym
15
16
  @target_class_name_from_name = name.to_s.classify
16
-
17
- set_vars_from_options(options)
17
+ apply_vars_from_options(options)
18
18
  end
19
19
 
20
20
  def target_class_option(options)
@@ -42,7 +42,11 @@ module Neo4j
42
42
  end
43
43
 
44
44
  def target_class_name
45
- @target_class_option.to_s if @target_class_option
45
+ @target_class_name ||= @target_class_option.to_s if @target_class_option
46
+ end
47
+
48
+ def target_class_name_or_nil
49
+ @target_class_name_or_nil ||= target_class_name || 'nil'
46
50
  end
47
51
 
48
52
  def target_class
@@ -75,9 +79,7 @@ module Neo4j
75
79
  end
76
80
  end
77
81
 
78
- def relationship_class
79
- @relationship_class
80
- end
82
+ attr_reader :relationship_class
81
83
 
82
84
  def relationship_class_type
83
85
  @relationship_class = @relationship_class.constantize if @relationship_class.class == String || @relationship_class == Symbol
@@ -104,6 +106,10 @@ module Neo4j
104
106
  properties
105
107
  end
106
108
 
109
+ def unique?
110
+ @origin ? origin_association.unique? : !!@unique
111
+ end
112
+
107
113
  private
108
114
 
109
115
  def get_direction(relationship_cypher, create)
@@ -129,19 +135,24 @@ module Neo4j
129
135
  p.size == 0 ? '' : " {#{p}}"
130
136
  end
131
137
 
138
+ def origin_association
139
+ target_class.associations[@origin]
140
+ end
141
+
132
142
  def origin_type
133
- target_class.associations[@origin].relationship_type
143
+ origin_association.relationship_type
134
144
  end
135
145
 
136
146
  private
137
147
 
138
- def set_vars_from_options(options)
139
- validate_option_combinations(options)
148
+ def apply_vars_from_options(options)
140
149
  @target_class_option = target_class_option(options)
141
150
  @callbacks = {before: options[:before], after: options[:after]}
142
151
  @origin = options[:origin] && options[:origin].to_sym
143
152
  @relationship_class = options[:rel_class]
144
153
  @relationship_type = options[:type] && options[:type].to_sym
154
+ @dependent = options[:dependent]
155
+ @unique = options[:unique]
145
156
  end
146
157
 
147
158
  # Return basic details about association as declared in the model
@@ -151,15 +162,25 @@ module Neo4j
151
162
  "#{type} #{direction.inspect}, #{name.inspect}"
152
163
  end
153
164
 
165
+ def validate_init_arguments(type, direction, options)
166
+ validate_option_combinations(options)
167
+ check_valid_type_and_dir(type, direction)
168
+ end
169
+
154
170
  def check_valid_type_and_dir(type, direction)
155
- raise ArgumentError, "Invalid association type: #{type.inspect} (valid value: :has_many and :has_one)" if not [:has_many, :has_one].include?(type.to_sym)
156
- raise ArgumentError, "Invalid direction: #{direction.inspect} (valid value: :out, :in, and :both)" if not [:out, :in, :both].include?(direction.to_sym)
171
+ fail ArgumentError, "Invalid association type: #{type.inspect} (valid value: :has_many and :has_one)" if not [:has_many, :has_one].include?(type.to_sym)
172
+ fail ArgumentError, "Invalid direction: #{direction.inspect} (valid value: :out, :in, and :both)" if not [:out, :in, :both].include?(direction.to_sym)
157
173
  end
158
174
 
159
175
  def validate_option_combinations(options)
160
- raise ArgumentError, "Cannot specify both :type and :origin (#{base_declaration})" if options[:type] && options[:origin]
161
- raise ArgumentError, "Cannot specify both :type and :rel_class (#{base_declaration})" if options[:type] && options[:rel_class]
162
- raise ArgumentError, "Cannot specify both :origin and :rel_class (#{base_declaration}" if options[:origin] && options[:rel_class]
176
+ fail ArgumentError, "Cannot specify both :type and :origin (#{base_declaration})" if options[:type] && options[:origin]
177
+ fail ArgumentError, "Cannot specify both :type and :rel_class (#{base_declaration})" if options[:type] && options[:rel_class]
178
+ fail ArgumentError, "Cannot specify both :origin and :rel_class (#{base_declaration}" if options[:origin] && options[:rel_class]
179
+ end
180
+
181
+ VALID_DEPENDENT_TYPES = [:delete, :delete_orphans, :destroy_orphans, :destroy, nil]
182
+ def validate_dependent(value)
183
+ fail ArgumentError, "Invalid dependent value: #{value.inspect}" if not VALID_DEPENDENT_TYPES.include?(value)
163
184
  end
164
185
 
165
186
  # Determine if model class as derived from the association name would be different than the one specified via the model_class key
@@ -174,22 +195,16 @@ module Neo4j
174
195
  end
175
196
 
176
197
  def validate_origin!
177
- if @origin
178
- if target_class
179
- if association = target_class.associations[@origin]
180
- if @direction == association.direction
181
- raise ArgumentError, "Origin `#{@origin.inspect}` (specified in #{base_declaration}) has same direction `#{@direction}`)"
182
- end
183
- else
184
- raise ArgumentError, "Origin `#{@origin.inspect}` association not found for #{target_class} (specified in #{base_declaration})"
185
- end
186
- else
187
- raise ArgumentError, "Cannot use :origin without a model_class (implied or explicit)"
188
- end
189
- end
198
+ return if not @origin
199
+
200
+ fail ArgumentError, 'Cannot use :origin without a model_class (implied or explicit)' if not target_class
201
+
202
+ association = origin_association
203
+ fail ArgumentError, "Origin `#{@origin.inspect}` association not found for #{target_class} (specified in #{base_declaration})" if not association
204
+
205
+ fail ArgumentError, "Origin `#{@origin.inspect}` (specified in #{base_declaration}) has same direction `#{@direction}`)" if @direction == association.direction
190
206
  end
191
207
  end
192
208
  end
193
209
  end
194
210
  end
195
-
@@ -1,5 +1,4 @@
1
1
  module Neo4j::ActiveNode
2
-
3
2
  # This module makes it possible to use other IDs than the build it neo4j id (neo_id)
4
3
  #
5
4
  # @example using generated UUIDs
@@ -27,16 +26,16 @@ module Neo4j::ActiveNode
27
26
 
28
27
  module TypeMethods
29
28
  def define_id_methods(clazz, name, conf)
30
- raise "Expected a Hash, got #{conf.class} (#{conf.to_s}) for id_property" unless conf.is_a?(Hash)
29
+ fail "Expected a Hash, got #{conf.class} (#{conf}) for id_property" unless conf.is_a?(Hash)
31
30
  if conf[:on]
32
31
  define_custom_method(clazz, name, conf[:on])
33
32
  elsif conf[:auto]
34
- raise "only :uuid auto id_property allowed, got #{conf[:auto]}" unless conf[:auto] == :uuid
33
+ fail "only :uuid auto id_property allowed, got #{conf[:auto]}" unless conf[:auto] == :uuid
35
34
  define_uuid_method(clazz, name)
36
35
  elsif conf.empty?
37
36
  define_property_method(clazz, name)
38
37
  else
39
- raise "Illegal value #{conf.inspect} for id_property, expected :on or :auto"
38
+ fail "Illegal value #{conf.inspect} for id_property, expected :on or :auto"
40
39
  end
41
40
  end
42
41
 
@@ -45,23 +44,22 @@ module Neo4j::ActiveNode
45
44
  def define_property_method(clazz, name)
46
45
  clear_methods(clazz, name)
47
46
 
48
- clazz.module_eval(%Q{
47
+ clazz.module_eval(%(
49
48
  def id
50
- persisted? ? #{name.to_sym == :id ? 'attribute(\'id\')' : name} : nil
49
+ _persisted_obj ? #{name.to_sym == :id ? 'attribute(\'id\')' : name} : nil
51
50
  end
52
51
 
53
52
  validates_uniqueness_of :#{name}
54
53
 
55
54
  property :#{name}
56
- }, __FILE__, __LINE__)
57
-
55
+ ), __FILE__, __LINE__)
58
56
  end
59
57
 
60
58
 
61
59
  def define_uuid_method(clazz, name)
62
60
  clear_methods(clazz, name)
63
61
 
64
- clazz.module_eval(%Q{
62
+ clazz.module_eval(%(
65
63
  default_property :#{name} do
66
64
  ::SecureRandom.uuid
67
65
  end
@@ -71,13 +69,13 @@ module Neo4j::ActiveNode
71
69
  end
72
70
 
73
71
  alias_method :id, :#{name}
74
- }, __FILE__, __LINE__)
72
+ ), __FILE__, __LINE__)
75
73
  end
76
74
 
77
75
  def define_custom_method(clazz, name, on)
78
76
  clear_methods(clazz, name)
79
77
 
80
- clazz.module_eval(%Q{
78
+ clazz.module_eval(%{
81
79
  default_property :#{name} do |instance|
82
80
  raise "Specifying custom id_property #{name} on none existing method #{on}" unless instance.respond_to?(:#{on})
83
81
  instance.#{on}
@@ -93,15 +91,15 @@ module Neo4j::ActiveNode
93
91
 
94
92
  def clear_methods(clazz, name)
95
93
  if clazz.method_defined?(name)
96
- clazz.module_eval(%Q{
94
+ clazz.module_eval(%(
97
95
  undef_method :#{name}
98
- }, __FILE__, __LINE__)
96
+ ), __FILE__, __LINE__)
99
97
  end
100
98
 
101
99
  if clazz.attribute_names.include?(name.to_s)
102
- clazz.module_eval(%Q{
100
+ clazz.module_eval(%(
103
101
  undef_property :#{name}
104
- }, __FILE__, __LINE__)
102
+ ), __FILE__, __LINE__)
105
103
  end
106
104
  end
107
105
 
@@ -110,7 +108,6 @@ module Neo4j::ActiveNode
110
108
 
111
109
 
112
110
  module ClassMethods
113
-
114
111
  def find_by_neo_id(id)
115
112
  Neo4j::Node.load(id)
116
113
  end
@@ -125,7 +122,7 @@ module Neo4j::ActiveNode
125
122
 
126
123
  def id_property(name, conf = {})
127
124
  begin
128
- if has_id_property?
125
+ if id_property?
129
126
  unless mapped_label.uniqueness_constraints[:property_keys].include?([name])
130
127
  drop_constraint(id_property_name, type: :unique)
131
128
  end
@@ -142,7 +139,15 @@ module Neo4j::ActiveNode
142
139
  end
143
140
  end
144
141
 
142
+ # rubocop:disable Style/PredicateName
145
143
  def has_id_property?
144
+ ActiveSupport::Deprecation.warn 'has_id_property? is deprecated and may be removed from future releases, use id_property? instead.', caller
145
+
146
+ id_property?
147
+ end
148
+ # rubocop:enable Style/PredicateName
149
+
150
+ def id_property?
146
151
  id_property_info && !id_property_info.empty?
147
152
  end
148
153
 
@@ -155,8 +160,6 @@ module Neo4j::ActiveNode
155
160
  end
156
161
 
157
162
  alias_method :primary_key, :id_property_name
158
-
159
163
  end
160
164
  end
161
-
162
165
  end
@@ -1,6 +1,7 @@
1
1
  module Neo4j::ActiveNode::Initialize
2
2
  extend ActiveSupport::Concern
3
3
  include Neo4j::Shared::TypeConverters
4
+ attr_reader :called_by
4
5
 
5
6
  # called when loading the node from the database
6
7
  # @param [Neo4j::Node] persisted_node the node this class wraps
@@ -11,7 +12,7 @@ module Neo4j::ActiveNode::Initialize
11
12
  @association_cache = {}
12
13
  changed_attributes && changed_attributes.clear
13
14
  @attributes = attributes.merge(properties.stringify_keys)
14
- self.default_properties=properties
15
+ self.default_properties = properties
15
16
  @attributes = convert_properties_to :ruby, @attributes
16
17
  end
17
18
 
@@ -21,7 +22,4 @@ module Neo4j::ActiveNode::Initialize
21
22
  def wrapper
22
23
  self
23
24
  end
24
-
25
25
  end
26
-
27
-
@@ -1,7 +1,5 @@
1
1
  module Neo4j
2
2
  module ActiveNode
3
-
4
-
5
3
  # Provides a mapping between neo4j labels and Ruby classes
6
4
  module Labels
7
5
  extend ActiveSupport::Concern
@@ -52,7 +50,7 @@ module Neo4j
52
50
  # Only for testing purpose
53
51
  # @private
54
52
  def self._wrapped_labels=(wl)
55
- @_wrapped_labels=(wl)
53
+ @_wrapped_labels = (wl)
56
54
  end
57
55
 
58
56
  def self._wrapped_labels
@@ -74,25 +72,24 @@ module Neo4j
74
72
  # Returns the object with the specified neo4j id.
75
73
  # @param [String,Fixnum] id of node to find
76
74
  def find(id)
77
- map_id = Proc.new {|object| object.respond_to?(:id) ? object.send(:id) : object }
75
+ map_id = proc { |object| object.respond_to?(:id) ? object.send(:id) : object }
78
76
 
79
77
  if id.is_a?(Array)
80
- find_by_ids(id.map {|o| map_id.call(o) })
78
+ find_by_ids(id.map { |o| map_id.call(o) })
81
79
  else
82
80
  find_by_id(map_id.call(id))
83
81
  end
84
82
  end
85
83
 
86
84
  # Finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself.
87
- # @param [Hash] args of arguments to find
88
- def find_by(*args)
89
- self.query_as(:n).where(n: eval(args.join)).limit(1).pluck(:n).first
85
+ # @param Hash args of arguments to find
86
+ def find_by(values)
87
+ self.query_as(:n).where(n: values).limit(1).pluck(:n).first
90
88
  end
91
89
 
92
90
  # Like find_by, except that if no record is found, raises a RecordNotFound error.
93
- def find_by!(*args)
94
- a = eval(args.join)
95
- find_by(args) or raise RecordNotFound, "#{self.query_as(:n).where(n: a).limit(1).to_cypher} returned no results"
91
+ def find_by!(values)
92
+ find_by(values) || fail(RecordNotFound, "#{self.query_as(:n).where(n: values).limit(1).to_cypher} returned no results")
96
93
  end
97
94
 
98
95
  # Deletes all nodes and connected relationships from Cypher.
@@ -104,7 +101,7 @@ module Neo4j
104
101
  # 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
105
102
  # one database query per node in the database, more if callbacks require them.
106
103
  def destroy_all
107
- self.all.each { |n| n.destroy }
104
+ self.all.each(&:destroy)
108
105
  end
109
106
 
110
107
  # Creates a Neo4j index on given property
@@ -141,14 +138,14 @@ module Neo4j
141
138
  # @example
142
139
  # Person.constraint :name, type: :unique
143
140
  #
144
- def constraint(property, constraints, session = Neo4j::Session.current)
141
+ def constraint(property, constraints)
145
142
  Neo4j::Session.on_session_available do |session|
146
143
  label = Neo4j::Label.create(mapped_label_name)
147
144
  label.create_constraint(property, constraints, session)
148
145
  end
149
146
  end
150
147
 
151
- def drop_constraint(property, constraint, session = Neo4j::Session.current)
148
+ def drop_constraint(property, constraint)
152
149
  Neo4j::Session.on_session_available do |session|
153
150
  label = Neo4j::Label.create(mapped_label_name)
154
151
  label.drop_constraint(property, constraint, session)
@@ -166,7 +163,7 @@ module Neo4j
166
163
 
167
164
  # @return [Symbol] the label that this class has which corresponds to a Ruby class
168
165
  def mapped_label_name
169
- @_label_name || (self.name.nil? ? object_id.to_s.to_sym : self.name.to_sym)
166
+ @mapped_label_name || (self.name.nil? ? object_id.to_s.to_sym : self.name.to_sym)
170
167
  end
171
168
 
172
169
  # @return [Neo4j::Label] the label for this class
@@ -180,7 +177,7 @@ module Neo4j
180
177
 
181
178
  def base_class
182
179
  unless self < Neo4j::ActiveNode
183
- raise "#{name} doesn't belong in a hierarchy descending from ActiveNode"
180
+ fail "#{name} doesn't belong in a hierarchy descending from ActiveNode"
184
181
  end
185
182
 
186
183
  if superclass == Object
@@ -204,21 +201,25 @@ module Neo4j
204
201
  else
205
202
  label.create_index(property) unless existing.flatten.include?(property)
206
203
  end
207
-
208
204
  end
209
205
  end
210
206
 
211
207
  def mapped_labels
212
- mapped_label_names.map{|label_name| Neo4j::Label.create(label_name)}
208
+ mapped_label_names.map { |label_name| Neo4j::Label.create(label_name) }
213
209
  end
214
210
 
215
- def set_mapped_label_name(name)
216
- @_label_name = name.to_sym
211
+ def mapped_label_name=(name)
212
+ @mapped_label_name = name.to_sym
217
213
  end
218
214
 
219
- end
215
+ # rubocop:disable Style/AccessorMethodName
216
+ def set_mapped_label_name(name)
217
+ ActiveSupport::Deprecation.warn 'set_mapped_label_name is deprecated and may be removed from future releases, use self.mapped_label_name= instead.', caller
220
218
 
219
+ self.mapped_label_name = name
220
+ end
221
+ # rubocop:enable Style/AccessorMethodName
222
+ end
221
223
  end
222
-
223
224
  end
224
225
  end
@@ -1,7 +1,6 @@
1
1
  class Neo4j::Node
2
2
  # The wrapping process is what transforms a raw CypherNode or EmbeddedNode from Neo4j::Core into a healthy ActiveNode (or ActiveRel) object.
3
3
  module Wrapper
4
-
5
4
  # this is a plugin in the neo4j-core so that the Ruby wrapper will be wrapped around the Neo4j::Node objects
6
5
  def wrapper
7
6
  self.props.symbolize_keys!
@@ -26,22 +25,20 @@ class Neo4j::Node
26
25
 
27
26
  # Makes the determination of whether to use <tt>_classname</tt> (or whatever is defined by config) or the node's labels.
28
27
  def sorted_wrapper_classes
29
- if self.props.is_a?(Hash) && self.props.has_key?(Neo4j::Config.class_name_property)
28
+ if self.props.is_a?(Hash) && self.props.key?(Neo4j::Config.class_name_property)
30
29
  self.props[Neo4j::Config.class_name_property].constantize
31
30
  else
32
31
  wrappers = _class_wrappers
33
32
  return self if wrappers.nil?
34
- wrapper_classes = wrappers.map{|w| Neo4j::ActiveNode::Labels._wrapped_labels[w]}
33
+ wrapper_classes = wrappers.map { |w| Neo4j::ActiveNode::Labels._wrapped_labels[w] }
35
34
  wrapper_classes.sort.first
36
35
  end
37
36
  end
38
37
 
39
38
  def load_class_from_label(label_name)
40
- begin
41
- label_name.to_s.split("::").inject(Kernel) {|container, name| container.const_get(name.to_s) }
42
- rescue NameError
43
- nil
44
- end
39
+ label_name.to_s.split('::').inject(Kernel) { |container, name| container.const_get(name.to_s) }
40
+ rescue NameError
41
+ nil
45
42
  end
46
43
 
47
44
  def _class_wrappers