neo4j 5.0.15 → 5.1.0.rc.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -5
  3. data/Gemfile +1 -1
  4. data/README.md +8 -0
  5. data/lib/neo4j.rb +4 -0
  6. data/lib/neo4j/active_node.rb +3 -1
  7. data/lib/neo4j/active_node/dependent/association_methods.rb +4 -2
  8. data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +3 -3
  9. data/lib/neo4j/active_node/has_n.rb +103 -36
  10. data/lib/neo4j/active_node/has_n/association.rb +10 -33
  11. data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +108 -0
  12. data/lib/neo4j/active_node/id_property.rb +19 -11
  13. data/lib/neo4j/active_node/id_property/accessor.rb +62 -0
  14. data/lib/neo4j/active_node/labels.rb +13 -2
  15. data/lib/neo4j/active_node/persistence.rb +19 -4
  16. data/lib/neo4j/active_node/property.rb +4 -3
  17. data/lib/neo4j/active_node/query/query_proxy.rb +29 -13
  18. data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +8 -0
  19. data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +7 -0
  20. data/lib/neo4j/active_node/query/query_proxy_link.rb +16 -6
  21. data/lib/neo4j/active_node/query/query_proxy_methods.rb +4 -0
  22. data/lib/neo4j/active_node/query/query_proxy_unpersisted.rb +17 -0
  23. data/lib/neo4j/active_node/unpersisted.rb +49 -0
  24. data/lib/neo4j/active_node/validations.rb +1 -1
  25. data/lib/neo4j/active_rel.rb +17 -0
  26. data/lib/neo4j/active_rel/persistence.rb +10 -5
  27. data/lib/neo4j/active_rel/property.rb +17 -5
  28. data/lib/neo4j/railtie.rb +2 -1
  29. data/lib/neo4j/shared/declared_property_manager.rb +10 -0
  30. data/lib/neo4j/shared/initialize.rb +3 -3
  31. data/lib/neo4j/shared/property.rb +7 -51
  32. data/lib/neo4j/shared/property/default_property.rb +0 -0
  33. data/lib/neo4j/shared/type_converters.rb +49 -6
  34. data/lib/neo4j/shared/typecaster.rb +22 -18
  35. data/lib/neo4j/shared/validations.rb +1 -1
  36. data/lib/neo4j/version.rb +1 -1
  37. data/neo4j.gemspec +1 -1
  38. metadata +12 -7
@@ -23,6 +23,14 @@ module Neo4j
23
23
  end
24
24
 
25
25
  def with_associations(*spec)
26
+ invalid_association_names = spec.reject do |association_name|
27
+ model.associations[association_name]
28
+ end
29
+
30
+ if invalid_association_names.size > 0
31
+ fail "Invalid associations: #{invalid_association_names.join(', ')}"
32
+ end
33
+
26
34
  new_link.tap do |new_query_proxy|
27
35
  new_spec = new_query_proxy.with_associations_spec + spec
28
36
  new_query_proxy.with_associations_spec.replace(new_spec)
@@ -37,7 +37,11 @@ module Neo4j
37
37
 
38
38
  # When called at the end of a QueryProxy chain, it will return the resultant relationship objects intead of nodes.
39
39
  # For example, to return the relationship between a given student and their lessons:
40
+ #
41
+ # .. code-block:: ruby
42
+ #
40
43
  # student.lessons.each_rel do |rel|
44
+ #
41
45
  # @return [Enumerable] An enumerable containing any number of applicable relationship objects.
42
46
  def each_rel(&block)
43
47
  block_given? ? each(false, true, &block) : to_enum(:each, false, true)
@@ -45,6 +49,9 @@ module Neo4j
45
49
 
46
50
  # When called at the end of a QueryProxy chain, it will return the nodes and relationships of the last link.
47
51
  # For example, to return a lesson and each relationship to a given student:
52
+ #
53
+ # .. code-block:: ruby
54
+ #
48
55
  # student.lessons.each_with_rel do |lesson, rel|
49
56
  def each_with_rel(&block)
50
57
  block_given? ? each(true, true, &block) : to_enum(:each, true, true)
@@ -29,10 +29,9 @@ module Neo4j
29
29
  arg.each do |key, value|
30
30
  if model && model.association?(key)
31
31
  result += for_association(key, value, "n#{node_num}", model)
32
-
33
32
  node_num += 1
34
33
  else
35
- result << new(:where, ->(v, _) { {v => {key => value}} })
34
+ result << new_for_key_and_value(model, key, value)
36
35
  end
37
36
  end
38
37
  elsif arg.is_a?(String)
@@ -42,15 +41,26 @@ module Neo4j
42
41
  end
43
42
  alias_method :for_node_where_clause, :for_where_clause
44
43
 
44
+ def new_for_key_and_value(model, key, value)
45
+ key = (key.to_sym == :id ? model.id_property_name : key)
46
+
47
+ val = if !model
48
+ value
49
+ elsif key == model.id_property_name && value.is_a?(Neo4j::ActiveNode)
50
+ value.id
51
+ else
52
+ model.declared_property_manager.value_for_db(key, value)
53
+ end
54
+
55
+ new(:where, ->(v, _) { {v => {key => val}} })
56
+ end
57
+
45
58
  def for_association(name, value, n_string, model)
46
59
  neo_id = value.try(:neo_id) || value
47
60
  fail ArgumentError, "Invalid value for '#{name}' condition" if not neo_id.is_a?(Integer)
48
61
 
49
- dir = model.associations[name].direction
50
-
51
- arrow = dir == :out ? '-->' : '<--'
52
62
  [
53
- new(:match, ->(v, _) { "#{v}#{arrow}(#{n_string})" }),
63
+ new(:match, ->(v, _) { "#{v}#{model.associations[name].arrow_cypher}(#{n_string})" }),
54
64
  new(:where, ->(_, _) { {"ID(#{n_string})" => neo_id.to_i} })
55
65
  ]
56
66
  end
@@ -135,6 +135,7 @@ module Neo4j
135
135
  # support for null object pattern
136
136
  '1 = 2'
137
137
  end
138
+
138
139
  self.where(where_arg)
139
140
  end
140
141
 
@@ -203,6 +204,9 @@ module Neo4j
203
204
  # So for a `Teacher` model inheriting from a `Person` model and an `Article` model
204
205
  # if you called .as_models([Teacher, Article])
205
206
  # The where clause would look something like:
207
+ #
208
+ # .. code-block:: cypher
209
+ #
206
210
  # WHERE (node_var:Teacher:Person OR node_var:Article)
207
211
  def as_models(models)
208
212
  where_clause = models.map do |model|
@@ -0,0 +1,17 @@
1
+ module Neo4j
2
+ module ActiveNode
3
+ module Query
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
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ module Neo4j
2
+ module ActiveNode
3
+ module Unpersisted
4
+ def pending_associations
5
+ @pending_associations ||= {}
6
+ end
7
+
8
+ def pending_associations?
9
+ !@pending_associations.blank?
10
+ end
11
+
12
+ private
13
+
14
+ # TODO: Change this method's name.
15
+ # Takes the pending_associations hash, which is in the format { cache_key => [:association_name, :association_operator]},
16
+ # and returns them as { association_name => [[nodes_for_persistence], :operator] }
17
+ def pending_associations_with_nodes
18
+ return unless pending_associations?
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]
23
+ end
24
+ end
25
+ end
26
+
27
+ # @param [Hash] deferred_nodes A hash created by :pending_associations_with_nodes
28
+ def process_unpersisted_nodes!(deferred_nodes)
29
+ deferred_nodes.each_pair do |k, (v, o)|
30
+ save_and_associate_queue(k, v, o)
31
+ end
32
+ end
33
+
34
+
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
+ node_queue.each do |element|
38
+ element.is_a?(Array) ? element.each { |node| association_proc.call(node) } : association_proc.call(element)
39
+ end
40
+ end
41
+
42
+ def save_and_associate_node(association_name, node, operator)
43
+ node.save if node.changed? || !node.persisted?
44
+ fail "Unable to defer node persistence, could not save #{node.inspect}" unless node.persisted?
45
+ operator == :<< ? send(association_name).send(operator, node) : send(:"#{association_name}=", node)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -8,7 +8,7 @@ module Neo4j
8
8
 
9
9
  # @return [Boolean] true if valid
10
10
  def valid?(context = nil)
11
- context ||= (new_record? ? :create : :update)
11
+ context ||= (new_record? ? :create : :update)
12
12
  super(context)
13
13
  errors.empty?
14
14
  end
@@ -22,6 +22,23 @@ module Neo4j
22
22
  super
23
23
  end
24
24
 
25
+ def inspect
26
+ attribute_pairs = attributes.sort.map { |key, value| "#{key}: #{value.inspect}" }
27
+ attribute_descriptions = attribute_pairs.join(', ')
28
+ separator = ' ' unless attribute_descriptions.empty?
29
+
30
+ cypher_representation = "#{node_cypher_representation(from_node)}-[:#{type}]->#{node_cypher_representation(to_node)}"
31
+ "#<#{self.class.name} #{cypher_representation}#{separator}#{attribute_descriptions}>"
32
+ end
33
+
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}})"
40
+ end
41
+
25
42
  def neo4j_obj
26
43
  _persisted_obj || fail('Tried to access native neo4j object on a non persisted object')
27
44
  end
@@ -47,7 +47,14 @@ module Neo4j::ActiveRel
47
47
 
48
48
  # Same as #create, but raises an error if there is a problem during save.
49
49
  def create!(*args)
50
- fail RelInvalidError, self unless create(*args)
50
+ props = args[0] || {}
51
+ relationship_props = extract_association_attributes!(props) || {}
52
+ new(props).tap do |obj|
53
+ relationship_props.each do |prop, value|
54
+ obj.send("#{prop}=", value)
55
+ end
56
+ obj.save!
57
+ end
51
58
  end
52
59
  end
53
60
 
@@ -70,9 +77,7 @@ module Neo4j::ActiveRel
70
77
  "Node class was #{node.class} (#{node.class.object_id}), expected #{type_class} (#{type_class.object_id})"
71
78
  end
72
79
 
73
- def _create_rel(from_node, to_node, *args)
74
- props = self.class.default_property_values(self)
75
- props.merge!(args[0]) if args[0].is_a?(Hash)
80
+ def _create_rel(from_node, to_node, props = {})
76
81
  set_classname(props, true)
77
82
 
78
83
  if from_node.id.nil? || to_node.id.nil?
@@ -91,7 +96,7 @@ module Neo4j::ActiveRel
91
96
  end
92
97
 
93
98
  def create_method
94
- self.class.unique? ? :create_unique : :create
99
+ self.class.creates_unique? ? :create_unique : :create
95
100
  end
96
101
  end
97
102
  end
@@ -19,8 +19,8 @@ module Neo4j::ActiveRel
19
19
  self.class._type
20
20
  end
21
21
 
22
- def initialize(attributes = {}, options = {})
23
- super(attributes, options)
22
+ def initialize(attributes = nil)
23
+ super(attributes)
24
24
  send_props(@relationship_props) unless @relationship_props.nil?
25
25
  end
26
26
 
@@ -28,6 +28,7 @@ module Neo4j::ActiveRel
28
28
  # Extracts keys from attributes hash which are relationships of the model
29
29
  # TODO: Validate separately that relationships are getting the right values? Perhaps also store the values and persist relationships on save?
30
30
  def extract_association_attributes!(attributes)
31
+ return if attributes.blank?
31
32
  {}.tap do |relationship_props|
32
33
  attributes.each_key do |key|
33
34
  relationship_props[key] = attributes.delete(key) if [:from_node, :to_node].include?(key)
@@ -55,12 +56,23 @@ module Neo4j::ActiveRel
55
56
  Neo4j::Node.load(id)
56
57
  end
57
58
 
59
+ def creates_unique
60
+ @creates_unique = true
61
+ end
62
+
58
63
  def creates_unique_rel
59
- @unique = true
64
+ warning = <<-WARNING
65
+ creates_unique_rel() is deprecated and will be removed from future releases,
66
+ use creates_unique() instead.
67
+ WARNING
68
+
69
+ ActiveSupport::Deprecation.warn(warning, caller)
70
+
71
+ creates_unique
60
72
  end
61
73
 
62
- def unique?
63
- !!@unique
74
+ def creates_unique?
75
+ !!@creates_unique
64
76
  end
65
77
  end
66
78
 
data/lib/neo4j/railtie.rb CHANGED
@@ -11,7 +11,6 @@ module Neo4j
11
11
  end
12
12
 
13
13
  rake_tasks do
14
- load 'neo4j/tasks/neo4j_server.rake'
15
14
  load 'neo4j/tasks/migration.rake'
16
15
  end
17
16
 
@@ -71,6 +70,8 @@ module Neo4j
71
70
  def register_neo4j_cypher_logging
72
71
  return if @neo4j_cypher_logging_registered
73
72
 
73
+ Neo4j::Core::Query.pretty_cypher = Neo4j::Config[:pretty_logged_cypher_queries]
74
+
74
75
  Neo4j::Server::CypherSession.log_with do |message|
75
76
  (Neo4j::Config[:logger] || Rails.logger).info message
76
77
  end
@@ -113,6 +113,16 @@ module Neo4j::Shared
113
113
  @upstream_primitives ||= {}
114
114
  end
115
115
 
116
+ def value_for_db(key, value)
117
+ return value unless registered_properties[key]
118
+ convert_property(key, value, :to_db)
119
+ end
120
+
121
+ def value_for_ruby(key, value)
122
+ return unless registered_properties[key]
123
+ convert_property(key, value, :to_ruby)
124
+ end
125
+
116
126
  protected
117
127
 
118
128
  # Prevents repeated calls to :_attribute_type, which isn't free and never changes.
@@ -12,16 +12,16 @@ module Neo4j::Shared
12
12
  private
13
13
 
14
14
  def convert_and_assign_attributes(properties)
15
- @attributes ||= self.class.attributes_nil_hash.dup
15
+ @attributes ||= Hash[self.class.attributes_nil_hash]
16
16
  stringify_attributes!(@attributes, properties)
17
- self.default_properties = properties
17
+ self.default_properties = properties if respond_to?(:default_properties=)
18
18
  self.class.declared_property_manager.convert_properties_to(self, :ruby, @attributes)
19
19
  end
20
20
 
21
21
  def stringify_attributes!(attr, properties)
22
22
  properties.each_pair do |k, v|
23
23
  key = self.class.declared_property_manager.string_key(k)
24
- attr[key] = v
24
+ attr[key.freeze] = v
25
25
  end
26
26
  end
27
27
  end
@@ -17,15 +17,14 @@ module Neo4j::Shared
17
17
 
18
18
  # TODO: Remove the commented :super entirely once this code is part of a release.
19
19
  # It calls an init method in active_attr that has a very negative impact on performance.
20
- def initialize(attributes = {}, _options = nil)
21
- attributes = process_attributes(attributes) unless attributes.empty?
20
+ def initialize(attributes = nil)
21
+ attributes = process_attributes(attributes)
22
22
  @relationship_props = self.class.extract_association_attributes!(attributes)
23
23
  writer_method_props = extract_writer_methods!(attributes)
24
24
  validate_attributes!(attributes)
25
- send_props(writer_method_props) unless writer_method_props.empty?
25
+ send_props(writer_method_props)
26
26
 
27
27
  @_persisted_obj = nil
28
- # super(attributes, options)
29
28
  end
30
29
 
31
30
  # Returning nil when we get ActiveAttr::UnknownAttributeError from ActiveAttr
@@ -36,22 +35,8 @@ module Neo4j::Shared
36
35
  end
37
36
  alias_method :[], :read_attribute
38
37
 
39
- def default_properties=(properties)
40
- default_property_keys = self.class.default_properties_keys
41
- @default_properties = properties.select { |key| default_property_keys.include?(key) }
42
- end
43
-
44
- def default_property(key)
45
- default_properties[key.to_sym]
46
- end
47
-
48
- def default_properties
49
- @default_properties ||= Hash.new(nil)
50
- # keys = self.class.default_properties.keys
51
- # _persisted_obj.props.reject{|key| !keys.include?(key)}
52
- end
53
-
54
38
  def send_props(hash)
39
+ return hash if hash.blank?
55
40
  hash.each { |key, value| self.send("#{key}=", value) }
56
41
  end
57
42
 
@@ -70,13 +55,13 @@ module Neo4j::Shared
70
55
  # Changes attributes hash to remove relationship keys
71
56
  # Raises an error if there are any keys left which haven't been defined as properties on the model
72
57
  def validate_attributes!(attributes)
73
- return attributes if attributes.empty?
58
+ return attributes if attributes.blank?
74
59
  invalid_properties = attributes.keys.map(&:to_s) - self.attributes.keys
75
60
  fail UndefinedPropertyError, "Undefined properties: #{invalid_properties.join(',')}" if invalid_properties.size > 0
76
61
  end
77
62
 
78
63
  def extract_writer_methods!(attributes)
79
- return attributes if attributes.empty?
64
+ return attributes if attributes.blank?
80
65
  {}.tap do |writer_method_props|
81
66
  attributes.each_key do |key|
82
67
  writer_method_props[key] = attributes.delete(key) if self.respond_to?("#{key}=")
@@ -86,6 +71,7 @@ module Neo4j::Shared
86
71
 
87
72
  # Gives support for Rails date_select, datetime_select, time_select helpers.
88
73
  def process_attributes(attributes = nil)
74
+ return attributes if attributes.blank?
89
75
  multi_parameter_attributes = {}
90
76
  new_attributes = {}
91
77
  attributes.each_pair do |key, value|
@@ -179,36 +165,6 @@ module Neo4j::Shared
179
165
  @_declared_property_manager ||= DeclaredPropertyManager.new(self)
180
166
  end
181
167
 
182
- # TODO: Move this to the DeclaredPropertyManager
183
- def default_property(name, &block)
184
- reset_default_properties(name) if default_properties.respond_to?(:size)
185
- default_properties[name] = block
186
- end
187
-
188
- # @return [Hash<Symbol,Proc>]
189
- def default_properties
190
- @default_property ||= {}
191
- end
192
-
193
- def default_properties_keys
194
- @default_properties_keys ||= default_properties.keys
195
- end
196
-
197
- def reset_default_properties(name_to_keep)
198
- default_properties.each_key do |property|
199
- @default_properties_keys = nil
200
- undef_method(property) unless property == name_to_keep
201
- end
202
- @default_properties_keys = nil
203
- @default_property = {}
204
- end
205
-
206
- def default_property_values(instance)
207
- default_properties.each_with_object({}) do |(key, block), result|
208
- result[key] = block.call(instance)
209
- end
210
- end
211
-
212
168
  def attribute!(name, options = {})
213
169
  super(name, options)
214
170
  define_method("#{name}=") do |value|
File without changes