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

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.
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