neo4j 5.0.15 → 5.1.0.rc.1

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