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

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