neo4j 3.0.0.rc.3 → 3.0.0.rc.4

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.
@@ -1,8 +1,46 @@
1
1
  module Neo4j::ActiveNode
2
2
  module Persistence
3
+
4
+ class RecordInvalidError < RuntimeError
5
+ attr_reader :record
6
+
7
+ def initialize(record)
8
+ @record = record
9
+ super(@record.errors.full_messages.join(", "))
10
+ end
11
+ end
12
+
3
13
  extend ActiveSupport::Concern
4
14
  include Neo4j::Shared::Persistence
5
15
 
16
+ # Saves the model.
17
+ #
18
+ # If the model is new a record gets created in the database, otherwise the existing record gets updated.
19
+ # If perform_validation is true validations run.
20
+ # If any of them fail the action is cancelled and save returns false. If the flag is false validations are bypassed altogether. See ActiveRecord::Validations for more information.
21
+ # There’s a series of callbacks associated with save. If any of the before_* callbacks return false the action is cancelled and save returns false.
22
+ def save(*)
23
+ update_magic_properties
24
+ clear_association_cache
25
+ create_or_update
26
+ end
27
+
28
+ # Persist the object to the database. Validations and Callbacks are included
29
+ # by default but validation can be disabled by passing :validate => false
30
+ # to #save! Creates a new transaction.
31
+ #
32
+ # @raise a RecordInvalidError if there is a problem during save.
33
+ # @param (see Neo4j::Rails::Validations#save)
34
+ # @return nil
35
+ # @see #save
36
+ # @see Neo4j::Rails::Validations Neo4j::Rails::Validations - for the :validate parameter
37
+ # @see Neo4j::Rails::Callbacks Neo4j::Rails::Callbacks - for callbacks
38
+ def save!(*args)
39
+ unless save(*args)
40
+ raise RecordInvalidError.new(self)
41
+ end
42
+ end
43
+
6
44
  # Creates a model with values matching those of the instance attributes and returns its id.
7
45
  # @private
8
46
  # @return true
@@ -60,6 +98,7 @@ module Neo4j::ActiveNode
60
98
  def load_entity(id)
61
99
  Neo4j::Node.load(id)
62
100
  end
101
+
63
102
  end
64
103
 
65
104
  private
@@ -5,6 +5,11 @@ module Neo4j
5
5
 
6
6
  include Enumerable
7
7
  include Neo4j::ActiveNode::Query::QueryProxyMethods
8
+ include Neo4j::ActiveNode::Query::QueryProxyFindInBatches
9
+
10
+ # The most recent node to start a QueryProxy chain.
11
+ # Will be nil when using QueryProxy chains on class methods.
12
+ attr_reader :caller
8
13
 
9
14
  def initialize(model, association = nil, options = {})
10
15
  @model = model
@@ -14,6 +19,7 @@ module Neo4j
14
19
  @node_var = options[:node]
15
20
  @rel_var = options[:rel] || _rel_chain_var
16
21
  @session = options[:session]
22
+ @caller = options[:caller]
17
23
  @chain = []
18
24
  @params = options[:query_proxy] ? options[:query_proxy].instance_variable_get('@params') : {}
19
25
  end
@@ -22,16 +28,24 @@ module Neo4j
22
28
  @node_var || :result
23
29
  end
24
30
 
31
+ def enumerable_query(node, rel = nil)
32
+ pluck_this = rel.nil? ? [node] : [node, rel]
33
+ return self.pluck(*pluck_this) if @association.nil? || caller.nil?
34
+ cypher_string = self.to_cypher_with_params(pluck_this)
35
+ association_collection = caller.association_instance_get(cypher_string, @association)
36
+ if association_collection.nil?
37
+ association_collection = self.pluck(*pluck_this)
38
+ caller.association_instance_set(cypher_string, association_collection, @association) unless association_collection.empty?
39
+ end
40
+ association_collection
41
+ end
42
+
25
43
  def each(node = true, rel = nil, &block)
26
44
  if node && rel
27
- self.pluck(identity, @rel_var).each do |obj, rel|
28
- yield obj, rel
29
- end
45
+ enumerable_query(identity, @rel_var).each { |obj, rel| yield obj, rel }
30
46
  else
31
47
  pluck_this = !rel ? identity : @rel_var
32
- self.pluck(pluck_this).each do |obj|
33
- yield obj
34
- end
48
+ enumerable_query(pluck_this).each { |obj| yield obj }
35
49
  end
36
50
  end
37
51
 
@@ -97,6 +111,14 @@ module Neo4j
97
111
  query.to_cypher
98
112
  end
99
113
 
114
+ # Returns a string of the cypher query with return objects and params
115
+ # @param [Array] columns array containing symbols of identifiers used in the query
116
+ # @return [String]
117
+ def to_cypher_with_params(columns = [:result])
118
+ final_query = query.return_query(columns)
119
+ "#{final_query.to_cypher} | params: #{final_query.send(:merge_params)}"
120
+ end
121
+
100
122
  # To add a relationship for the node for the association on this QueryProxy
101
123
  def <<(other_node)
102
124
  create(other_node, {})
@@ -131,6 +153,7 @@ module Neo4j
131
153
  return false if @association.perform_callback(@options[:start_object], other_node, :before) == false
132
154
 
133
155
  start_object = @options[:start_object]
156
+ start_object.clear_association_cache
134
157
  _session.query(context: @options[:context])
135
158
  .start(start: "node(#{start_object.neo_id})", end: "node(#{other_node.neo_id})")
136
159
  .create("start#{_association_arrow(properties, true)}end").exec
@@ -0,0 +1,20 @@
1
+ module Neo4j
2
+ module ActiveNode
3
+ module Query
4
+ module QueryProxyFindInBatches
5
+ def find_in_batches(options = {})
6
+ query.return(identity).find_in_batches(identity, @model.primary_key, options) do |batch|
7
+ yield batch
8
+ end
9
+ end
10
+
11
+ def find_each(options = {})
12
+ query.return(identity).find_each(identity, @model.primary_key, options) do |result|
13
+ yield result
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+ end
@@ -4,11 +4,6 @@ module Neo4j
4
4
  module QueryProxyMethods
5
5
  class InvalidParameterError < StandardError; end
6
6
 
7
- def query_with_target(target, &block)
8
- target = target.nil? ? identity : target
9
- block.yield(target)
10
- end
11
-
12
7
  def first(target=nil)
13
8
  query_with_target(target) { |target| first_and_last("ID(#{target})", target) }
14
9
  end
@@ -42,19 +37,36 @@ module Neo4j
42
37
  def include?(other, target=nil)
43
38
  raise(InvalidParameterError, ':include? only accepts nodes') unless other.respond_to?(:neo_id)
44
39
  query_with_target(target) do |target|
45
- self.where("ID(#{target}) = {other_node_id}").params(other_node_id: other.neo_id).query.return("count(#{target}) AS count").first.count > 0
40
+ self.query.where(target => {@model.primary_key => other.id}).return("count(#{target}) AS count").first.count > 0
46
41
  end
47
42
  end
48
43
 
49
- def exists?(node_id=nil, target=nil)
50
- raise(InvalidParameterError, ':exists? only accepts neo_ids') unless node_id.is_a?(Integer) || node_id.nil?
44
+ def exists?(node_condition=nil, target=nil)
45
+ raise(InvalidParameterError, ':exists? only accepts neo_ids') unless node_condition.is_a?(Fixnum) || node_condition.is_a?(Hash) || node_condition.nil?
51
46
  query_with_target(target) do |target|
52
- start_q = self.query
53
- end_q = node_id.nil? ? start_q : start_q.where("ID(#{target}) = #{node_id}")
54
- end_q.return("COUNT(#{target}) AS count").first.count > 0
47
+ start_q = exists_query_start(self, node_condition, target)
48
+ start_q.query.return("COUNT(#{target}) AS count").first.count > 0
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def query_with_target(target, &block)
55
+ target = target.nil? ? identity : target
56
+ block.yield(target)
57
+ end
58
+
59
+ def exists_query_start(origin, condition, target)
60
+ case
61
+ when condition.class == Fixnum
62
+ self.where("ID(#{target}) = #{condition}")
63
+ when condition.class == Hash
64
+ self.where(condition.keys.first => condition.values.first)
65
+ else
66
+ self
55
67
  end
56
68
  end
57
69
  end
58
70
  end
59
71
  end
60
- end
72
+ end
@@ -3,21 +3,21 @@ module Neo4j
3
3
  module QueryMethods
4
4
  class InvalidParameterError < StandardError; end
5
5
 
6
- def exists?(node_id=nil)
7
- raise(InvalidParameterError, ':exists? only accepts neo_ids') unless node_id.is_a?(Integer) || node_id.nil?
8
- start_q = self.query_as(:n)
9
- end_q = node_id.nil? ? start_q : start_q.where("ID(n) = #{node_id}")
10
- end_q.return("COUNT(n) AS count").first.count > 0
6
+ def exists?(node_condition=nil)
7
+ raise(InvalidParameterError, ':exists? only accepts ids or conditions') unless node_condition.is_a?(Fixnum) || node_condition.is_a?(Hash) || node_condition.nil?
8
+ query_start = exists_query_start(node_condition)
9
+ start_q = query_start.respond_to?(:query_as) ? query_start.query_as(:n) : query_start
10
+ start_q.return("COUNT(n) AS count").first.count > 0
11
11
  end
12
12
 
13
13
  # Returns the first node of this class, sorted by ID. Note that this may not be the first node created since Neo4j recycles IDs.
14
14
  def first
15
- self.query_as(:n).limit(1).order('ID(n)').pluck(:n).first
15
+ self.query_as(:n).limit(1).order(n: primary_key).pluck(:n).first
16
16
  end
17
17
 
18
18
  # Returns the last node of this class, sorted by ID. Note that this may not be the first node created since Neo4j recycles IDs.
19
19
  def last
20
- self.query_as(:n).limit(1).order('ID(n) DESC').pluck(:n).first
20
+ self.query_as(:n).limit(1).order(n: {primary_key => :desc}).pluck(:n).first
21
21
  end
22
22
 
23
23
  # @return [Fixnum] number of nodes of this class
@@ -38,10 +38,21 @@ module Neo4j
38
38
 
39
39
  def include?(other)
40
40
  raise(InvalidParameterError, ':include? only accepts nodes') unless other.respond_to?(:neo_id)
41
- self.query_as(:n).where("ID(n) = #{other.neo_id}").return("count(n) AS count").first.count > 0
41
+ self.query_as(:n).where(n: {primary_key => other.id}).return("count(n) AS count").first.count > 0
42
42
  end
43
43
 
44
-
44
+ private
45
+
46
+ def exists_query_start(node_condition)
47
+ case
48
+ when node_condition.class == Fixnum
49
+ self.query_as(:n).where("ID(n) = #{node_condition}")
50
+ when node_condition.class == Hash
51
+ self.where(node_condition.keys.first => node_condition.values.first)
52
+ else
53
+ self.query_as(:n)
54
+ end
55
+ end
45
56
  end
46
57
  end
47
- end
58
+ end
@@ -0,0 +1,85 @@
1
+ module Neo4j::ActiveNode
2
+ # A reflection contains information about an association.
3
+ # They are often used in connection with form builders to determine associated classes.
4
+ # This module contains methods related to the creation and retrieval of reflections.
5
+ module Reflection
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ class_attribute :reflections
10
+ self.reflections = {}
11
+ end
12
+
13
+ # Adds methods to the class related to creating and retrieving reflections.
14
+ module ClassMethods
15
+ # @param macro [Symbol] the association type, :has_many or :has_one
16
+ # @param name [Symbol] the association name
17
+ # @param association_object [Neo4j::ActiveNode::HasN::Association] the association object created in the course of creating this reflection
18
+ def create_reflection(macro, name, association_object)
19
+ self.reflections = self.reflections.merge(name => AssociationReflection.new(macro, name, association_object))
20
+ end
21
+
22
+ private :create_reflection
23
+ # @param [Symbol] an association declared on the model
24
+ # @return [Neo4j::ActiveNode::Reflection::AssociationReflection] of the given association
25
+ def reflect_on_association(association)
26
+ reflections[association.to_sym]
27
+ end
28
+
29
+ # Returns an array containing one reflection for each association declared in the model.
30
+ def reflect_on_all_associations(macro = nil)
31
+ association_reflections = reflections.values
32
+ macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
33
+ end
34
+ end
35
+
36
+ # The actual reflection object that contains information about the given association.
37
+ # These should never need to be created manually, they will always be created by declaring a :has_many or :has_one association on a model.
38
+ class AssociationReflection
39
+ # The name of the association
40
+ attr_reader :name
41
+
42
+ # The type of association
43
+ attr_reader :macro
44
+
45
+ # The association object referenced by this reflection
46
+ attr_reader :association
47
+
48
+ def initialize(macro, name, association)
49
+ @macro = macro
50
+ @name = name
51
+ @association = association
52
+ end
53
+
54
+ # Returns the target model
55
+ def klass
56
+ @klass ||= class_name.constantize
57
+ end
58
+
59
+ # Returns the name of the target model
60
+ def class_name
61
+ @class_name ||= association.target_class.name
62
+ end
63
+
64
+ def rel_klass
65
+ @rel_klass ||= rel_class_name.constantize
66
+ end
67
+
68
+ def rel_class_name
69
+ @rel_class_name ||= association.relationship_class.name.to_s
70
+ end
71
+
72
+ def type
73
+ @type ||= association.relationship_type
74
+ end
75
+
76
+ def collection?
77
+ macro == :has_many
78
+ end
79
+
80
+ def validate?
81
+ true
82
+ end
83
+ end
84
+ end
85
+ end
@@ -5,7 +5,9 @@ module Neo4j
5
5
  include Neo4j::Shared::Callbacks
6
6
 
7
7
  def save(*args)
8
- raise Neo4j::ActiveRel::Persistence::RelInvalidError.new(self) unless self.persisted? || (from_node.respond_to?(:neo_id) && to_node.respond_to?(:neo_id))
8
+ unless self.persisted? || (from_node.respond_to?(:neo_id) && to_node.respond_to?(:neo_id))
9
+ raise Neo4j::ActiveRel::Persistence::RelInvalidError, 'from_node and to_node must be node objects'
10
+ end
9
11
  super(*args)
10
12
  end
11
13
  end
@@ -10,12 +10,12 @@ module Neo4j::ActiveRel
10
10
  # @param [Neo4j::Relationship] start_node the starting node in the relationship.
11
11
  # @param [Neo4j::Relationship] end_node the ending node in the relationship
12
12
  # @param [String] type the relationship type
13
- def init_on_load(persisted_rel, start_node_id, end_node_id, type)
13
+ def init_on_load(persisted_rel, from_node_id, to_node_id, type)
14
14
  @_persisted_obj = persisted_rel
15
15
  @rel_type = type
16
16
  changed_attributes && changed_attributes.clear
17
17
  @attributes = attributes.merge(persisted_rel.props.stringify_keys)
18
- load_nodes(start_node_id, end_node_id)
18
+ load_nodes(from_node_id, to_node_id)
19
19
  self.default_properties = persisted_rel.props
20
20
  @attributes = convert_properties_to :ruby, @attributes
21
21
  end
@@ -3,14 +3,7 @@ module Neo4j::ActiveRel
3
3
  extend ActiveSupport::Concern
4
4
  include Neo4j::Shared::Persistence
5
5
 
6
- class RelInvalidError < RuntimeError
7
- attr_reader :record
8
-
9
- def initialize(record)
10
- @record = record
11
- super(@record.errors.full_messages.join(", "))
12
- end
13
- end
6
+ class RelInvalidError < RuntimeError; end
14
7
 
15
8
  class ModelClassInvalidError < RuntimeError; end
16
9
 
@@ -31,7 +24,7 @@ module Neo4j::ActiveRel
31
24
  set_timestamps
32
25
  properties = convert_properties_to :db, props
33
26
  rel = _create_rel(from_node, to_node, properties)
34
- init_on_load(rel, to_node, from_node, @rel_type)
27
+ init_on_load(rel, from_node, to_node, @rel_type)
35
28
  true
36
29
  end
37
30
 
@@ -61,10 +54,10 @@ module Neo4j::ActiveRel
61
54
 
62
55
  def confirm_node_classes
63
56
  [from_node, to_node].each do |node|
64
- check = from_node == node ? :_from_class : :_to_class
65
- next if self.class.send(check) == :any
66
- unless self.class.send(check) == node.class
67
- raise ModelClassInvalidError, "Node class was #{node.class}, expected #{self.class.send(check)}"
57
+ type = from_node == node ? :_from_class : :_to_class
58
+ next if allows_any_class?(type)
59
+ unless class_as_constant(type) == node.class
60
+ raise ModelClassInvalidError, "Node class was #{node.class}, expected #{self.class.send(type)}"
68
61
  end
69
62
  end
70
63
  end
@@ -75,5 +68,21 @@ module Neo4j::ActiveRel
75
68
  set_classname(props)
76
69
  from_node.create_rel(type, to_node, props)
77
70
  end
71
+
72
+ def class_as_constant(type)
73
+ given_class = self.class.send(type)
74
+ case
75
+ when given_class.is_a?(String)
76
+ given_class.constantize
77
+ when given_class.is_a?(Symbol)
78
+ given_class.to_s.constantize
79
+ else
80
+ given_class
81
+ end
82
+ end
83
+
84
+ def allows_any_class?(type)
85
+ self.class.send(type) == :any || self.class.send(type) == false
86
+ end
78
87
  end
79
- end
88
+ end
@@ -1,4 +1,3 @@
1
-
2
1
  module Neo4j::ActiveRel
3
2
  module Property
4
3
  extend ActiveSupport::Concern
@@ -7,7 +6,7 @@ module Neo4j::ActiveRel
7
6
  %w[to_node from_node].each do |direction|
8
7
  define_method("#{direction}") { instance_variable_get("@#{direction}") }
9
8
  define_method("#{direction}=") do |argument|
10
- raise FrozenRelError, "Relationship start/end nodes cannot be changed once persisted" if self.persisted?
9
+ raise FrozenRelError, 'Relationship start/end nodes cannot be changed once persisted' if self.persisted?
11
10
  instance_variable_set("@#{direction}", argument)
12
11
  end
13
12
  end
@@ -15,18 +14,18 @@ module Neo4j::ActiveRel
15
14
  alias_method :start_node, :from_node
16
15
  alias_method :end_node, :to_node
17
16
 
17
+ # @return [String] a string representing the relationship type that will be created
18
18
  def type
19
19
  self.class._type
20
20
  end
21
21
 
22
- def initialize(attributes={}, options={})
22
+ def initialize(attributes = {}, options = {})
23
23
  super(attributes, options)
24
24
 
25
25
  send_props(@relationship_props) unless @relationship_props.nil?
26
26
  end
27
27
 
28
28
  module ClassMethods
29
-
30
29
  # Extracts keys from attributes hash which are relationships of the model
31
30
  # TODO: Validate separately that relationships are getting the right values? Perhaps also store the values and persist relationships on save?
32
31
  def extract_association_attributes!(attributes)
@@ -43,10 +42,12 @@ module Neo4j::ActiveRel
43
42
  alias_method :start_class, :from_class
44
43
  alias_method :end_class, :to_class
45
44
 
45
+ # @param type [String] sets the relationship type when creating relationships via this class
46
46
  def type(type = nil)
47
47
  @rel_type = type
48
48
  end
49
49
 
50
+ # @return [String] a string representing the relationship type that will be created
50
51
  def _type
51
52
  @rel_type
52
53
  end
@@ -54,15 +55,13 @@ module Neo4j::ActiveRel
54
55
  def load_entity(id)
55
56
  Neo4j::Node.load(id)
56
57
  end
57
-
58
58
  end
59
59
 
60
60
  private
61
61
 
62
- def load_nodes(start_node=nil, end_node=nil)
63
- @from_node = RelatedNode.new(end_node)
64
- @to_node = RelatedNode.new(start_node)
62
+ def load_nodes(from_node = nil, to_node = nil)
63
+ @from_node = RelatedNode.new(from_node)
64
+ @to_node = RelatedNode.new(to_node)
65
65
  end
66
-
67
66
  end
68
- end
67
+ end