neo4j 6.0.0.alpha.9 → 6.0.0.alpha.11

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5e9a0580979b896fd0ff1da7bdb6e42e8dc5a3dd
4
- data.tar.gz: 9709fe48514ff2aad702b9e8606ca25f9afb0e64
3
+ metadata.gz: cbd147ccfe5710b9dd99035774e8ccac71f1a2fe
4
+ data.tar.gz: 0afb13bbc63f47ce1658313f13541586cdbaca8b
5
5
  SHA512:
6
- metadata.gz: 96781c2cb44acf832b0eb205ffa25dd7a7fa86ea058d45ef647be8dc02bb5a8d0c2ce242a5d9ff75a4143c86f59a55ae7d4c4bd279c00a752c109eb3cef7057e
7
- data.tar.gz: 46ebc2cf7bf7a7b0883eb36c46ed327762bba6e77a3d17a6dbb74e7d80b255225ae2ac64b149f8d662bf35263643364ee2d386cc390611cb56dbe9956c796d01
6
+ metadata.gz: ba9b1a7bed89c5e0d3be9349a527b496d0cd92a025b0602e68a89b38e4bcae5e45de4a667249abeb23c7fa63be7fcf26f152e48b117108eebcb6f38fe061729c
7
+ data.tar.gz: ccb77857f67f3bc4ac45087b10d71ef2117b6170ed437aae1593a77f4ff35549a709b76d9046430dd429419efc6cb829cd76e80e1e790b5faad49a454e6d88dd
data/CHANGELOG.md CHANGED
@@ -3,6 +3,27 @@ All notable changes to this project will be documented in this file.
3
3
  This file should follow the standards specified on [http://keepachangelog.com/]
4
4
  This project adheres to [Semantic Versioning](http://semver.org/).
5
5
 
6
+ ## [6.0.0.alpha.11] - 11-3-2015
7
+
8
+ ### Fixed
9
+ - Regression RE: properties being overwritten with their defaults on save in alpha.10.
10
+
11
+ ### Changed
12
+ - `#<<` and `#create` methods on associations now create with the `rel_class` when available so that validations/callbacks/defaults are all used as expected
13
+ - Allow calling of `#method=` methods via model `new` method `Hash` argument
14
+
15
+ ### Added
16
+ - Alternate `ActiveRel` init syntax: `RelClass.new(from_node, to_node, args)`. This is optional, so giving a single hash with props with or without nodes is still possible.
17
+
18
+ ## [6.0.0.alpha.10] - 11-2-2015
19
+
20
+ ### Fixed
21
+ - Long properties in `ActiveNode`/`ActiveRel` `#inspect` are truncated
22
+ - Property defaults are set initially when an instance of a model is loaded, then checked again before save to ensure `valid?` works.
23
+
24
+ ### Added
25
+ - `ActiveRel` `create` actions can now handle unpersisted nodes.
26
+
6
27
  ## [6.0.0.alpha.9] - 10-27-2015
7
28
 
8
29
  ### Fixed
data/lib/neo4j.rb CHANGED
@@ -41,11 +41,13 @@ require 'neo4j/shared/identity'
41
41
  require 'neo4j/shared/serialized_properties'
42
42
  require 'neo4j/shared/typecaster'
43
43
  require 'neo4j/shared/initialize'
44
+ require 'neo4j/shared/query_factory'
44
45
  require 'neo4j/shared'
45
46
 
46
47
  require 'neo4j/active_rel/callbacks'
47
48
  require 'neo4j/active_rel/initialize'
48
49
  require 'neo4j/active_rel/property'
50
+ require 'neo4j/active_rel/persistence/query_factory'
49
51
  require 'neo4j/active_rel/persistence'
50
52
  require 'neo4j/active_rel/validations'
51
53
  require 'neo4j/active_rel/query'
@@ -45,15 +45,6 @@ module Neo4j
45
45
  _persisted_obj || fail('Tried to access native neo4j object on a non persisted object')
46
46
  end
47
47
 
48
- def inspect
49
- id_property_name = self.class.id_property_name.to_s
50
- attribute_pairs = attributes.except(id_property_name).sort.map { |key, value| "#{key}: #{value.inspect}" }
51
- attribute_pairs.unshift("#{id_property_name}: #{self.send(id_property_name).inspect}")
52
- attribute_descriptions = attribute_pairs.join(', ')
53
- separator = ' ' unless attribute_descriptions.empty?
54
- "#<#{self.class.name}#{separator}#{attribute_descriptions}>"
55
- end
56
-
57
48
  included do
58
49
  include Neo4j::Timestamps if Neo4j::Config[:record_timestamps]
59
50
 
@@ -44,5 +44,18 @@ module Neo4j::ActiveNode
44
44
  end
45
45
  end
46
46
  end
47
+
48
+ private
49
+
50
+ def inspect_attributes
51
+ id_property_name = self.class.id_property_name.to_s
52
+
53
+ attribute_pairs = attributes.except(id_property_name).sort.map do |key, value|
54
+ [key, (value.is_a?(String) && value.size > 100) ? value.dup.first(100) : value]
55
+ end
56
+
57
+ attribute_pairs.unshift([id_property_name, self.send(id_property_name)])
58
+ attribute_pairs
59
+ end
47
60
  end
48
61
  end
@@ -208,9 +208,21 @@ module Neo4j
208
208
  end
209
209
 
210
210
  def _create_relationship(other_node_or_nodes, properties)
211
- _session.query(context: @options[:context])
212
- .match(:start, :end).match_nodes(start: @start_object, end: other_node_or_nodes)
213
- .send(association.create_method, "start#{_association_arrow(properties, true)}end").exec
211
+ if association.relationship_class
212
+ _create_relationship_with_rel_class(other_node_or_nodes, properties)
213
+ else
214
+ _session.query(context: @options[:context])
215
+ .match(:start, :end).match_nodes(start: @start_object, end: other_node_or_nodes)
216
+ .send(association.create_method, "start#{_association_arrow(properties, true)}end").exec
217
+ end
218
+ end
219
+
220
+ def _create_relationship_with_rel_class(other_node_or_nodes, properties)
221
+ Array(other_node_or_nodes).each do |other_node|
222
+ node_props = (association.direction == :in) ? {from_node: other_node, to_node: @start_object} : {from_node: @start_object, to_node: other_node}
223
+
224
+ association.relationship_class.create(properties.except(:_classname).merge(node_props))
225
+ end
214
226
  end
215
227
 
216
228
  def read_attribute_for_serialization(*args)
@@ -298,21 +310,20 @@ module Neo4j
298
310
  end
299
311
 
300
312
  def _association_chain_var
313
+ fail 'Crazy error' if !(start_object || @query_proxy)
314
+
301
315
  if start_object
302
316
  :"#{start_object.class.name.gsub('::', '_').downcase}#{start_object.neo_id}"
303
- elsif @query_proxy
304
- @query_proxy.node_var || :"node#{_chain_level}"
305
317
  else
306
- fail 'Crazy error' # TODO: Better error
318
+ @query_proxy.node_var || :"node#{_chain_level}"
307
319
  end
308
320
  end
309
321
 
310
322
  def _association_query_start(var)
311
- if object = (start_object || @query_proxy)
312
- object.query_as(var)
313
- else
314
- fail 'Crazy error' # TODO: Better error
315
- end
323
+ # TODO: Better error
324
+ fail 'Crazy error' if !(object = (start_object || @query_proxy))
325
+
326
+ object.query_as(var)
316
327
  end
317
328
 
318
329
  def _rel_chain_var
@@ -326,11 +337,8 @@ module Neo4j
326
337
  def instance_vars_from_options!(options)
327
338
  @node_var, @session, @source_object, @starting_query, @optional,
328
339
  @start_object, @query_proxy, @chain_level, @association_labels,
329
- @rel_length = options.values_at(:node, :session, :source_object,
330
- :starting_query, :optional,
331
- :start_object, :query_proxy,
332
- :chain_level, :association_labels,
333
- :rel_length)
340
+ @rel_length = options.values_at(:node, :session, :source_object, :starting_query, :optional,
341
+ :start_object, :query_proxy, :chain_level, :association_labels, :rel_length)
334
342
  end
335
343
 
336
344
  def build_deeper_query_proxy(method, args)
@@ -17,18 +17,9 @@ module Neo4j
17
17
 
18
18
  class FrozenRelError < StandardError; end
19
19
 
20
- def initialize(*args)
21
- load_nodes
22
- super
23
- end
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}>"
20
+ def initialize(from_node = nil, to_node = nil, args = nil)
21
+ load_nodes(node_or_nil(from_node), node_or_nil(to_node))
22
+ super(hash_or_nil(from_node, args))
32
23
  end
33
24
 
34
25
  def node_cypher_representation(node)
@@ -52,5 +43,15 @@ module Neo4j
52
43
  end
53
44
 
54
45
  ActiveSupport.run_load_hooks(:active_rel, self)
46
+
47
+ private
48
+
49
+ def node_or_nil(node)
50
+ node.is_a?(Neo4j::ActiveNode) || node.is_a?(Integer) ? node : nil
51
+ end
52
+
53
+ def hash_or_nil(node_or_hash, hash_or_nil)
54
+ node_or_hash.is_a?(Hash) ? node_or_hash : hash_or_nil
55
+ end
55
56
  end
56
57
  end
@@ -3,10 +3,24 @@ module Neo4j::ActiveRel
3
3
  extend ActiveSupport::Concern
4
4
  include Neo4j::Shared::Persistence
5
5
 
6
+ attr_writer :from_node_identifier, :to_node_identifier
7
+
6
8
  class RelInvalidError < RuntimeError; end
7
9
  class ModelClassInvalidError < RuntimeError; end
8
10
  class RelCreateFailedError < RuntimeError; end
9
11
 
12
+ def from_node_identifier
13
+ @from_node_identifier || :from_node
14
+ end
15
+
16
+ def to_node_identifier
17
+ @to_node_identifier || :to_node
18
+ end
19
+
20
+ def cypher_identifier
21
+ @cypher_identifier || :rel
22
+ end
23
+
10
24
  def save(*)
11
25
  create_or_update
12
26
  end
@@ -17,9 +31,9 @@ module Neo4j::ActiveRel
17
31
 
18
32
  def create_model
19
33
  validate_node_classes!
20
- rel = _create_rel(from_node, to_node, props_for_create)
21
- return self unless rel.respond_to?(:_persisted_obj)
22
- init_on_load(rel._persisted_obj, from_node, to_node, @rel_type)
34
+ rel = _create_rel
35
+ return self unless rel.respond_to?(:props)
36
+ init_on_load(rel, from_node, to_node, @rel_type)
23
37
  true
24
38
  end
25
39
 
@@ -53,6 +67,10 @@ module Neo4j::ActiveRel
53
67
  end
54
68
  end
55
69
 
70
+ def create_method
71
+ self.class.create_method
72
+ end
73
+
56
74
  private
57
75
 
58
76
  def validate_node_classes!
@@ -81,28 +99,10 @@ module Neo4j::ActiveRel
81
99
  "Node class was #{node.class} (#{node.class.object_id}), expected #{type_class} (#{type_class.object_id})"
82
100
  end
83
101
 
84
- def _create_rel(from_node, to_node, props = {})
85
- if from_node.id.nil? || to_node.id.nil?
86
- messages = []
87
- messages << 'from_node ID is nil' if from_node.id.nil?
88
- messages << 'to_node ID is nil' if to_node.id.nil?
89
-
90
- fail RelCreateFailedError, "Unable to create relationship (#{messages.join(' / ')})"
91
- end
92
- _rel_creation_query(from_node, to_node, props)
93
- end
94
-
95
- N1_N2_STRING = 'n1, n2'
96
- ACTIVEREL_NODE_MATCH_STRING = 'ID(n1) = {n1_neo_id} AND ID(n2) = {n2_neo_id}'
97
- def _rel_creation_query(from_node, to_node, props)
98
- Neo4j::Session.query.match(N1_N2_STRING)
99
- .where(ACTIVEREL_NODE_MATCH_STRING).params(n1_neo_id: from_node.neo_id, n2_neo_id: to_node.neo_id).break
100
- .send(create_method, "n1-[r:`#{type}`]->n2")
101
- .with('r').set(r: props).pluck(:r).first
102
- end
103
-
104
- def create_method
105
- self.class.create_method
102
+ def _create_rel
103
+ factory = QueryFactory.new(from_node, to_node, self)
104
+ factory.build!
105
+ factory.unwrapped_rel
106
106
  end
107
107
  end
108
108
  end
@@ -0,0 +1,95 @@
1
+ module Neo4j::ActiveRel::Persistence
2
+ # This class builds and executes a Cypher query, using information from the graph objects to determine
3
+ # whether they need to be created simultaneously.
4
+ # It keeps the rel instance from being responsible for inspecting the nodes or talking with Shared::QueryFactory.
5
+ class QueryFactory
6
+ NODE_SYMBOLS = [:from_node, :to_node]
7
+ attr_reader :from_node, :to_node, :rel, :unwrapped_rel
8
+
9
+ def initialize(from_node, to_node, rel)
10
+ @from_node = from_node
11
+ @to_node = to_node
12
+ @rel = rel
13
+ end
14
+
15
+ # TODO: This feels like it should also wrap the rel, but that is handled in Neo4j::ActiveRel::Persistence at the moment.
16
+ # Builds and executes the query using the objects giving during init.
17
+ # It holds the process:
18
+ # * Execute node callbacks if needed
19
+ # * Create and execute the query
20
+ # * Mix the query response into the unpersisted objects given during init
21
+ def build!
22
+ node_before_callbacks! do
23
+ res = query_factory(rel, rel_id, iterative_query).query.unwrapped.return(*unpersisted_return_ids).first
24
+ node_symbols.each { |n| wrap!(send(n), res, n) }
25
+ @unwrapped_rel = res.send(rel_id)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def rel_id
32
+ @rel_id ||= rel.cypher_identifier
33
+ end
34
+
35
+ # Node callbacks only need to be executed if the node is not persisted. We let the `conditional_callback` method do the work,
36
+ # we only have to give it the type of callback we expect to be run and the condition which, if true, will prevent it from executing.
37
+ def node_before_callbacks!
38
+ validate_unpersisted_nodes!
39
+ from_node.conditional_callback(:create, from_node.persisted?) do
40
+ to_node.conditional_callback(:create, to_node.persisted?) do
41
+ yield
42
+ end
43
+ end
44
+ end
45
+
46
+ def validate_unpersisted_nodes!
47
+ node_symbols.each do |s|
48
+ obj = send(s)
49
+ next if obj.persisted?
50
+ fail RelCreateFailedError, "Cannot create rel with unpersisted, invalid #{s}" unless obj.valid?
51
+ end
52
+ end
53
+
54
+ # Each node must be either created or matched before the relationshp can be created. This class does not know or care about
55
+ # how that happens, it just knows that it needs a usable Neo4j::Core::Query object to do that.
56
+ # This method is "iterative" because it creates one factory for each node but the second builds upon the first.
57
+ def iterative_query
58
+ node_symbols.inject(false) do |iterative_query, sym|
59
+ obj = send(sym)
60
+ query_factory(obj, sym, iterative_query)
61
+ end
62
+ end
63
+
64
+ # Isolates the dependency to the shared class. This has an awareness of Neo4j::Core::Query and will match or create
65
+ # based on the current state of the object passed in.
66
+ def query_factory(obj, sym, factory = false)
67
+ Neo4j::Shared::QueryFactory.create(obj, sym).tap do |factory_instance|
68
+ factory_instance.base_query = factory.blank? ? false : factory.query
69
+ end
70
+ end
71
+
72
+ # @return [Array<Symbol>] The Cypher identifiers that will be returned from the query.
73
+ # We only need to return objects from our query that were created during it, otherwise we impact performance.
74
+ def unpersisted_return_ids
75
+ [rel_id].tap do |result|
76
+ node_symbols.each { |k| result << k unless send(k).persisted? }
77
+ end
78
+ end
79
+
80
+ # @param [Neo4j::ActiveNode] node A node, persisted or unpersisted
81
+ # @param [Struct] res The result of calling `return` on a Neo4j::Core::Query object. It responds to the same keys
82
+ # as our graph objects. If the object is unpersisted and was created during the query, the unwrapped node is mixed
83
+ # in, making the object reflect as "persisted".
84
+ # @param [Symbol] key :from_node or :to_node, the object to request from the response.
85
+ def wrap!(node, res, key)
86
+ return if node.persisted? || !res.respond_to?(key)
87
+ unwrapped = res.send(key)
88
+ node.init_on_load(unwrapped, unwrapped.props)
89
+ end
90
+
91
+ def node_symbols
92
+ self.class::NODE_SYMBOLS
93
+ end
94
+ end
95
+ end
@@ -90,5 +90,9 @@ WARNING
90
90
  @from_node = RelatedNode.new(from_node)
91
91
  @to_node = RelatedNode.new(to_node)
92
92
  end
93
+
94
+ def inspect_attributes
95
+ attributes.to_a
96
+ end
93
97
  end
94
98
  end
@@ -4,6 +4,7 @@ module Neo4j::ActiveRel
4
4
  # will result in a query to load the node if the node is not already loaded.
5
5
  class RelatedNode
6
6
  class InvalidParameterError < StandardError; end
7
+ class UnsetRelatedNodeError < StandardError; end
7
8
 
8
9
  # ActiveRel's related nodes can be initialized with nothing, an integer, or a fully wrapped node.
9
10
  #
@@ -11,8 +12,6 @@ module Neo4j::ActiveRel
11
12
  #
12
13
  # Initialization with an integer happens when a relationship is loaded from the database. It loads using the ID
13
14
  # because that is provided by the Cypher response and does not require an extra query.
14
- #
15
- # Initialization with a node doesn't appear to happen in the code. TODO: maybe find out why this is an option.
16
15
  def initialize(node = nil)
17
16
  @node = valid_node_param?(node) ? node : (fail InvalidParameterError, 'RelatedNode must be initialized with either a node ID or node')
18
17
  end
@@ -30,20 +29,41 @@ module Neo4j::ActiveRel
30
29
 
31
30
  # Loads a node from the database or returns the node if already laoded
32
31
  def loaded
32
+ fail NilRelatedNodeError, 'Node not set, cannot load' if @node.nil?
33
33
  @node = @node.respond_to?(:neo_id) ? @node : Neo4j::Node.load(@node)
34
34
  end
35
35
 
36
+ # @param [String, Symbol, Array] clazz An alternate label to use in the event the node is not present or loaded
37
+ def cypher_representation(clazz)
38
+ case
39
+ when !set?
40
+ "(#{formatted_label_list(clazz)})"
41
+ when set? && !loaded?
42
+ "(Node with neo_id #{@node})"
43
+ else
44
+ node_class = self.class
45
+ id_name = node_class.id_property_name
46
+ labels = ':' + node_class.mapped_label_names.join(':')
47
+
48
+ "(#{labels} {#{id_name}: #{@node.id.inspect}})"
49
+ end
50
+ end
51
+
36
52
  # @return [Boolean] indicates whether a node has or has not been fully loaded from the database
37
53
  def loaded?
38
54
  @node.respond_to?(:neo_id)
39
55
  end
40
56
 
57
+ def set?
58
+ !@node.nil?
59
+ end
60
+
41
61
  def method_missing(*args, &block)
42
62
  loaded.send(*args, &block)
43
63
  end
44
64
 
45
65
  def respond_to_missing?(method_name, include_private = false)
46
- loaded if @node.is_a?(Integer)
66
+ loaded if @node.is_a?(Numeric)
47
67
  @node.respond_to?(method_name) ? true : super
48
68
  end
49
69
 
@@ -53,6 +73,10 @@ module Neo4j::ActiveRel
53
73
 
54
74
  private
55
75
 
76
+ def formatted_label_list(list)
77
+ list.is_a?(Array) ? list.join(' || ') : list
78
+ end
79
+
56
80
  def valid_node_param?(node)
57
81
  node.nil? || node.is_a?(Integer) || node.respond_to?(:neo_id)
58
82
  end
@@ -34,6 +34,14 @@ module Neo4j
34
34
  run_callbacks(:touch) { super }
35
35
  end
36
36
 
37
+ # Allows you to perform a callback if a condition is not satisfied.
38
+ # @param [Symbol] kind The callback type to execute unless the guard is true
39
+ # @param [TrueClass,FalseClass] guard When this value is true, the block is yielded without executing callbacks.
40
+ def conditional_callback(kind, guard)
41
+ return yield if guard
42
+ run_callbacks(kind) { yield }
43
+ end
44
+
37
45
  private
38
46
 
39
47
  def create_or_update #:nodoc:
@@ -155,6 +155,13 @@ module Neo4j::Shared
155
155
  convert_property(key, value, :to_ruby)
156
156
  end
157
157
 
158
+ def inject_defaults!(object, props)
159
+ declared_property_defaults.each_pair do |k, v|
160
+ props[k.to_sym] = v if object.send(k).nil? && props[k.to_sym].nil?
161
+ end
162
+ props
163
+ end
164
+
158
165
  protected
159
166
 
160
167
  # Prevents repeated calls to :_attribute_type, which isn't free and never changes.
@@ -25,9 +25,9 @@ module Neo4j::Shared
25
25
  # @return [Hash]
26
26
  def props_for_create
27
27
  inject_timestamps!
28
- converted_props = props_for_db(props)
28
+ props_with_defaults = inject_defaults!(props)
29
+ converted_props = props_for_db(props_with_defaults)
29
30
  inject_classname!(converted_props)
30
- inject_defaults!(converted_props)
31
31
  return converted_props unless self.class.respond_to?(:default_property_values)
32
32
  inject_primary_key!(converted_props)
33
33
  end
@@ -37,8 +37,8 @@ module Neo4j::Shared
37
37
  update_magic_properties
38
38
  changed_props = attributes.select { |k, _| changed_attributes.include?(k) }
39
39
  changed_props.symbolize_keys!
40
- props_for_db(changed_props)
41
40
  inject_defaults!(changed_props)
41
+ props_for_db(changed_props)
42
42
  end
43
43
 
44
44
  # Convenience method to set attribute and #save at the same time
@@ -204,12 +204,7 @@ module Neo4j::Shared
204
204
  self.updated_at ||= now if respond_to?(:updated_at=)
205
205
  end
206
206
 
207
- def inject_defaults!(properties)
208
- self.class.declared_properties.declared_property_defaults.each_pair do |k, v|
209
- properties[k.to_sym] = v if send(k).nil?
210
- end
211
- properties
212
- end
207
+
213
208
 
214
209
  def set_timestamps
215
210
  warning = 'This method has been replaced with `inject_timestamps!` and will be removed in a future version'.freeze
@@ -14,18 +14,32 @@ module Neo4j::Shared
14
14
 
15
15
  attr_reader :_persisted_obj
16
16
 
17
+ def inspect
18
+ attribute_descriptions = inspect_attributes.map do |key, value|
19
+ "#{Neo4j::ANSI::CYAN}#{key}: #{Neo4j::ANSI::CLEAR}#{value.inspect}"
20
+ end.join(', ')
21
+
22
+ separator = ' ' unless attribute_descriptions.empty?
23
+ "#<#{Neo4j::ANSI::YELLOW}#{self.class.name}#{Neo4j::ANSI::CLEAR}#{separator}#{attribute_descriptions}>"
24
+ end
25
+
17
26
  # TODO: Remove the commented :super entirely once this code is part of a release.
18
27
  # It calls an init method in active_attr that has a very negative impact on performance.
19
28
  def initialize(attributes = nil)
20
29
  attributes = process_attributes(attributes)
21
30
  @relationship_props = self.class.extract_association_attributes!(attributes)
22
- writer_method_props = extract_writer_methods!(attributes)
23
- validate_attributes!(attributes)
31
+ modded_attributes = inject_defaults!(attributes)
32
+ validate_attributes!(modded_attributes)
33
+ writer_method_props = extract_writer_methods!(modded_attributes)
24
34
  send_props(writer_method_props)
25
-
26
35
  @_persisted_obj = nil
27
36
  end
28
37
 
38
+ def inject_defaults!(starting_props)
39
+ return starting_props if self.class.declared_properties.declared_property_defaults.empty?
40
+ self.class.declared_properties.inject_defaults!(self, starting_props || {})
41
+ end
42
+
29
43
  # Returning nil when we get ActiveAttr::UnknownAttributeError from ActiveAttr
30
44
  def read_attribute(name)
31
45
  super(name)
@@ -36,7 +50,7 @@ module Neo4j::Shared
36
50
 
37
51
  def send_props(hash)
38
52
  return hash if hash.blank?
39
- hash.each { |key, value| self.send("#{key}=", value) }
53
+ hash.each { |key, value| send("#{key}=", value) }
40
54
  end
41
55
 
42
56
  protected
@@ -53,9 +67,11 @@ module Neo4j::Shared
53
67
 
54
68
  # Changes attributes hash to remove relationship keys
55
69
  # Raises an error if there are any keys left which haven't been defined as properties on the model
70
+ # TODO: use declared_properties instead of self.attributes
56
71
  def validate_attributes!(attributes)
57
72
  return attributes if attributes.blank?
58
73
  invalid_properties = attributes.keys.map(&:to_s) - self.attributes.keys
74
+ invalid_properties.reject! { |name| self.respond_to?("#{name}=") }
59
75
  fail UndefinedPropertyError, "Undefined properties: #{invalid_properties.join(',')}" if invalid_properties.size > 0
60
76
  end
61
77
 
@@ -0,0 +1,91 @@
1
+ module Neo4j::Shared
2
+ # Acts as a bridge between the node and rel models and Neo4j::Core::Query.
3
+ # If the object is persisted, it returns a query matching; otherwise, it returns a query creating it.
4
+ # This class does not execute queries, so it keeps no record of what identifiers have been set or what has happened in previous factories.
5
+ class QueryFactory
6
+ attr_reader :graph_object, :identifier
7
+
8
+ def initialize(graph_object, identifier)
9
+ @graph_object = graph_object
10
+ @identifier = identifier.to_sym
11
+ end
12
+
13
+ def self.create(graph_object, identifier)
14
+ factory = case graph_object
15
+ when Neo4j::ActiveNode
16
+ NodeQueryFactory
17
+ when Neo4j::ActiveRel
18
+ RelQueryFactory
19
+ else
20
+ fail "Unable to find factory for #{graph_object}"
21
+ end
22
+ factory.new(graph_object, identifier)
23
+ end
24
+
25
+ def query
26
+ graph_object.persisted? ? match_query : create_query
27
+ end
28
+
29
+ # @param [Neo4j::Core::Query] query An instance of Neo4j::Core::Query upon which methods will be chained.
30
+ def base_query=(query)
31
+ return if query.blank?
32
+ @base_query = query
33
+ end
34
+
35
+ def base_query
36
+ @base_query || Neo4j::Session.current.query
37
+ end
38
+
39
+ protected
40
+
41
+ def create_query
42
+ fail 'Abstract class, not implemented'
43
+ end
44
+
45
+ def match_query
46
+ base_query
47
+ .match(match_string).where("ID(#{identifier}) = {#{identifier_id}}")
48
+ .params(identifier_id.to_sym => graph_object.neo_id)
49
+ end
50
+
51
+ def identifier_id
52
+ @identifier_id ||= "#{identifier}_id"
53
+ end
54
+
55
+ def identifier_params
56
+ @identifier_params ||= "#{identifier}_params"
57
+ end
58
+ end
59
+
60
+ class NodeQueryFactory < QueryFactory
61
+ protected
62
+
63
+ def match_string
64
+ "(#{identifier})"
65
+ end
66
+
67
+ def create_query
68
+ return match_query if graph_object.persisted?
69
+ base_query.create(identifier => {graph_object.labels_for_create.join(':').to_sym => graph_object.props_for_create})
70
+ end
71
+ end
72
+
73
+ class RelQueryFactory < QueryFactory
74
+ protected
75
+
76
+ def match_string
77
+ "(#{graph_object.from_node_identifier})-[#{identifier}]->()"
78
+ end
79
+
80
+ def create_query
81
+ return match_query if graph_object.persisted?
82
+ base_query.send(graph_object.create_method, query_string).params(identifier_params.to_sym => graph_object.props_for_create)
83
+ end
84
+
85
+ private
86
+
87
+ def query_string
88
+ "#{graph_object.from_node_identifier}-[#{identifier}:#{graph_object.type} {#{identifier_params}}]->#{graph_object.to_node_identifier}"
89
+ end
90
+ end
91
+ end
data/lib/neo4j/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Neo4j
2
- VERSION = '6.0.0.alpha.9'
2
+ VERSION = '6.0.0.alpha.11'
3
3
  end
data/neo4j.gemspec CHANGED
@@ -30,7 +30,7 @@ A Neo4j OGM (Object-Graph-Mapper) for use in Ruby on Rails and Rack frameworks h
30
30
  s.add_dependency('activemodel', '~> 4')
31
31
  s.add_dependency('activesupport', '~> 4')
32
32
  s.add_dependency('active_attr', '~> 0.8')
33
- s.add_dependency('neo4j-core', '~> 6.0.0.alpha.5')
33
+ s.add_dependency('neo4j-core', '~> 6.0.0.alpha.6')
34
34
  s.add_dependency('neo4j-community', '~> 2.0') if RUBY_PLATFORM =~ /java/
35
35
  s.add_development_dependency('railties', '~> 4')
36
36
  s.add_development_dependency('pry')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neo4j
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.0.alpha.9
4
+ version: 6.0.0.alpha.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andreas Ronge, Brian Underwood, Chris Grigg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-27 00:00:00.000000000 Z
11
+ date: 2015-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: orm_adapter
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 6.0.0.alpha.5
75
+ version: 6.0.0.alpha.6
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 6.0.0.alpha.5
82
+ version: 6.0.0.alpha.6
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: railties
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -260,6 +260,7 @@ files:
260
260
  - lib/neo4j/active_rel/callbacks.rb
261
261
  - lib/neo4j/active_rel/initialize.rb
262
262
  - lib/neo4j/active_rel/persistence.rb
263
+ - lib/neo4j/active_rel/persistence/query_factory.rb
263
264
  - lib/neo4j/active_rel/property.rb
264
265
  - lib/neo4j/active_rel/query.rb
265
266
  - lib/neo4j/active_rel/rel_wrapper.rb
@@ -283,6 +284,7 @@ files:
283
284
  - lib/neo4j/shared/persistence.rb
284
285
  - lib/neo4j/shared/property.rb
285
286
  - lib/neo4j/shared/property/default_property.rb
287
+ - lib/neo4j/shared/query_factory.rb
286
288
  - lib/neo4j/shared/rel_type_converters.rb
287
289
  - lib/neo4j/shared/serialized_properties.rb
288
290
  - lib/neo4j/shared/type_converters.rb