neo4j 6.1.12 → 7.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,83 @@
1
+ module Neo4j
2
+ module ActiveNode
3
+ module Query
4
+ module QueryProxyMethodsOfMassUpdating
5
+ # Updates some attributes of a group of nodes within a QP chain.
6
+ # The optional argument makes sense only of `updates` is a string.
7
+ # @param [Hash,String] updates An hash or a string of parameters to be updated.
8
+ # @param [Hash] params An hash of parameters for the update string. It's ignored if `updates` is an Hash.
9
+ def update_all(updates, params = {})
10
+ # Move this to ActiveNode module?
11
+ update_all_with_query(identity, updates, params)
12
+ end
13
+
14
+ # Updates some attributes of a group of relationships within a QP chain.
15
+ # The optional argument makes sense only of `updates` is a string.
16
+ # @param [Hash,String] updates An hash or a string of parameters to be updated.
17
+ # @param [Hash] params An hash of parameters for the update string. It's ignored if `updates` is an Hash.
18
+ def update_all_rels(updates, params = {})
19
+ fail 'Cannot update rels without a relationship variable.' unless @rel_var
20
+ update_all_with_query(@rel_var, updates, params)
21
+ end
22
+
23
+ # Deletes a group of nodes and relationships within a QP chain. When identifier is omitted, it will remove the last link in the chain.
24
+ # The optional argument must be a node identifier. A relationship identifier will result in a Cypher Error
25
+ # @param identifier [String,Symbol] the optional identifier of the link in the chain to delete.
26
+ def delete_all(identifier = nil)
27
+ query_with_target(identifier) do |target|
28
+ begin
29
+ self.query.with(target).optional_match("(#{target})-[#{target}_rel]-()").delete("#{target}, #{target}_rel").exec
30
+ rescue Neo4j::Session::CypherError
31
+ self.query.delete(target).exec
32
+ end
33
+ clear_source_object_cache
34
+ end
35
+ end
36
+
37
+ # Deletes the relationship between a node and its last link in the QueryProxy chain. Executed in the database, callbacks will not run.
38
+ def delete(node)
39
+ self.match_to(node).query.delete(rel_var).exec
40
+ clear_source_object_cache
41
+ end
42
+
43
+ # Deletes the relationships between all nodes for the last step in the QueryProxy chain. Executed in the database, callbacks will not be run.
44
+ def delete_all_rels
45
+ return unless start_object && start_object._persisted_obj
46
+ self.query.delete(rel_var).exec
47
+ end
48
+
49
+ # Deletes the relationships between all nodes for the last step in the QueryProxy chain and replaces them with relationships to the given nodes.
50
+ # Executed in the database, callbacks will not be run.
51
+ def replace_with(node_or_nodes)
52
+ nodes = Array(node_or_nodes)
53
+
54
+ self.delete_all_rels
55
+ nodes.each { |node| self << node }
56
+ end
57
+
58
+ # Returns all relationships between a node and its last link in the QueryProxy chain, destroys them in Ruby. Callbacks will be run.
59
+ def destroy(node)
60
+ self.rels_to(node).map!(&:destroy)
61
+ clear_source_object_cache
62
+ end
63
+
64
+ private
65
+
66
+ def update_all_with_query(var_name, updates, params)
67
+ query = all.query
68
+
69
+ case updates
70
+ when Hash then query.set(var_name => updates).pluck("count(#{var_name})").first
71
+ when String then query.set(updates).params(params).pluck("count(#{var_name})").first
72
+ else
73
+ fail ArgumentError, "Invalid parameter type #{updates.class} for `updates`."
74
+ end
75
+ end
76
+
77
+ def clear_source_object_cache
78
+ self.source_object.clear_association_cache if self.source_object.respond_to?(:clear_association_cache)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -1,11 +1,9 @@
1
1
  module Neo4j
2
2
  module ActiveNode
3
3
  module QueryMethods
4
- class InvalidParameterError < StandardError; end
5
-
6
4
  def exists?(node_condition = nil)
7
5
  unless node_condition.is_a?(Integer) || node_condition.is_a?(Hash) || node_condition.nil?
8
- fail(InvalidParameterError, ':exists? only accepts ids or conditions')
6
+ fail(Neo4j::InvalidParameterError, ':exists? only accepts ids or conditions')
9
7
  end
10
8
  query_start = exists_query_start(node_condition)
11
9
  start_q = query_start.respond_to?(:query_as) ? query_start.query_as(:n) : query_start
@@ -24,7 +22,7 @@ module Neo4j
24
22
 
25
23
  # @return [Integer] number of nodes of this class
26
24
  def count(distinct = nil)
27
- fail(InvalidParameterError, ':count accepts `distinct` or nil as a parameter') unless distinct.nil? || distinct == :distinct
25
+ fail(Neo4j::InvalidParameterError, ':count accepts `distinct` or nil as a parameter') unless distinct.nil? || distinct == :distinct
28
26
  q = distinct.nil? ? 'n' : 'DISTINCT n'
29
27
  self.query_as(:n).return("count(#{q}) AS count").first.count
30
28
  end
@@ -35,13 +35,13 @@ module Neo4j::ActiveNode
35
35
  #
36
36
  # @see http://guides.rubyonrails.org/active_record_querying.html#scopes
37
37
  def scope(name, proc)
38
- scopes[name.to_sym] = proc
38
+ _scope[name.to_sym] = proc
39
39
 
40
40
  klass = class << self; self; end
41
41
  klass.instance_eval do
42
42
  define_method(name) do |query_params = nil, _ = nil|
43
43
  eval_context = ScopeEvalContext.new(self, current_scope || self.query_proxy)
44
- proc = full_scopes[name.to_sym]
44
+ proc = _scope[name.to_sym]
45
45
  _call_scope_context(eval_context, query_params, proc)
46
46
  end
47
47
  end
@@ -56,15 +56,11 @@ module Neo4j::ActiveNode
56
56
  # rubocop:enable Style/PredicateName
57
57
 
58
58
  def scope?(name)
59
- full_scopes.key?(name.to_sym)
59
+ _scope.key?(name.to_sym)
60
60
  end
61
61
 
62
- def scopes
63
- @scopes ||= {}
64
- end
65
-
66
- def full_scopes
67
- scopes.merge(self.superclass.respond_to?(:scopes) ? self.superclass.scopes : {})
62
+ def _scope
63
+ @_scope ||= {}
68
64
  end
69
65
 
70
66
  def _call_scope_context(eval_context, query_params, proc)
@@ -17,8 +17,9 @@ module Neo4j
17
17
  include Neo4j::ActiveRel::Callbacks
18
18
  include Neo4j::ActiveRel::Query
19
19
  include Neo4j::ActiveRel::Types
20
+ include Neo4j::Shared::Enum
20
21
 
21
- class FrozenRelError < StandardError; end
22
+ class FrozenRelError < Neo4j::Error; end
22
23
 
23
24
  def initialize(from_node = nil, to_node = nil, args = nil)
24
25
  load_nodes(node_or_nil(from_node), node_or_nil(to_node))
@@ -3,8 +3,7 @@ module Neo4j::ActiveRel
3
3
  # It's important (or maybe not really IMPORTANT, but at least worth mentioning) that calling method_missing
4
4
  # will result in a query to load the node if the node is not already loaded.
5
5
  class RelatedNode
6
- class InvalidParameterError < StandardError; end
7
- class UnsetRelatedNodeError < StandardError; end
6
+ class UnsetRelatedNodeError < Neo4j::Error; end
8
7
 
9
8
  # ActiveRel's related nodes can be initialized with nothing, an integer, or a fully wrapped node.
10
9
  #
@@ -13,7 +12,7 @@ module Neo4j::ActiveRel
13
12
  # Initialization with an integer happens when a relationship is loaded from the database. It loads using the ID
14
13
  # because that is provided by the Cypher response and does not require an extra query.
15
14
  def initialize(node = nil)
16
- @node = valid_node_param?(node) ? node : (fail InvalidParameterError, 'RelatedNode must be initialized with either a node ID or node')
15
+ @node = valid_node_param?(node) ? node : (fail Neo4j::InvalidParameterError, 'RelatedNode must be initialized with either a node ID or node')
17
16
  end
18
17
 
19
18
  # Loads the node if needed, then conducts comparison.
data/lib/neo4j/errors.rb CHANGED
@@ -1,12 +1,28 @@
1
1
  module Neo4j
2
2
  # Neo4j.rb Errors
3
3
  # Generic Neo4j.rb exception class.
4
- class Neo4jrbError < StandardError
4
+ class Error < StandardError
5
5
  end
6
6
 
7
7
  # Raised when Neo4j.rb cannot find record by given id.
8
- class RecordNotFound < Neo4jrbError
8
+ class RecordNotFound < Error
9
+ attr_reader :model, :primary_key, :id
10
+
11
+ def initialize(message = nil, model = nil, primary_key = nil, id = nil)
12
+ @primary_key = primary_key
13
+ @model = model
14
+ @id = id
15
+
16
+ super(message)
17
+ end
9
18
  end
10
19
 
11
- class InvalidPropertyOptionsError < Neo4jrbError; end
20
+ class InvalidPropertyOptionsError < Error; end
21
+
22
+ class InvalidParameterError < Error; end
23
+
24
+ class UnknownTypeConverterError < Error; end
25
+
26
+ class DangerousAttributeError < ScriptError; end
27
+ class UnknownAttributeError < NoMethodError; end
12
28
  end
data/lib/neo4j/railtie.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require 'active_support/notifications'
2
2
  require 'rails/railtie'
3
+ # Need the action_dispatch railtie to have action_dispatch.rescue_responses initialized correctly
4
+ require 'action_dispatch/railtie'
3
5
 
4
6
  module Neo4j
5
7
  class Railtie < ::Rails::Railtie
@@ -11,6 +13,17 @@ module Neo4j
11
13
  end
12
14
  end
13
15
 
16
+ # Rescue responses similar to ActiveRecord.
17
+ # For rails 3.2 and 4.0
18
+ if config.action_dispatch.respond_to?(:rescue_responses)
19
+ config.action_dispatch.rescue_responses.merge!(
20
+ 'Neo4j::RecordNotFound' => :not_found
21
+ )
22
+ else
23
+ # For rails 3.0 and 3.1
24
+ ActionDispatch::ShowExceptions.rescue_responses['Neo4j::RecordNotFound'] = :not_found
25
+ end
26
+
14
27
  # Add ActiveModel translations to the I18n load_path
15
28
  initializer 'i18n' do
16
29
  config.i18n.load_path += Dir[File.join(File.dirname(__FILE__), '..', '..', '..', 'config', 'locales', '*.{rb,yml}')]
@@ -0,0 +1,207 @@
1
+ module Neo4j::Shared
2
+ # Attributes provides a set of class methods for defining an attributes
3
+ # schema and instance methods for reading and writing attributes.
4
+ #
5
+ # @example Usage
6
+ # class Person
7
+ # include Neo4j::Shared::Attributes
8
+ # attribute :name
9
+ # end
10
+ #
11
+ # person = Person.new
12
+ # person.name = "Ben Poweski"
13
+ #
14
+ # Originally part of ActiveAttr, https://github.com/cgriego/active_attr
15
+ module Attributes
16
+ extend ActiveSupport::Concern
17
+ include ActiveModel::AttributeMethods
18
+
19
+ # Methods deprecated on the Object class which can be safely overridden
20
+ DEPRECATED_OBJECT_METHODS = %w(id type)
21
+
22
+ included do
23
+ attribute_method_suffix '' if attribute_method_matchers.none? { |matcher| matcher.prefix == '' && matcher.suffix == '' }
24
+ attribute_method_suffix '='
25
+ end
26
+
27
+ # Performs equality checking on the result of attributes and its type.
28
+ #
29
+ # @example Compare for equality.
30
+ # model == other
31
+ #
32
+ # @param [ActiveAttr::Attributes, Object] other The other model to compare
33
+ #
34
+ # @return [true, false] True if attributes are equal and other is instance
35
+ # of the same Class, false if not.
36
+ def ==(other)
37
+ return false unless other.instance_of? self.class
38
+ attributes == other.attributes
39
+ end
40
+
41
+ # Returns a Hash of all attributes
42
+ #
43
+ # @example Get attributes
44
+ # person.attributes # => {"name"=>"Ben Poweski"}
45
+ #
46
+ # @return [Hash{String => Object}] The Hash of all attributes
47
+ def attributes
48
+ attributes_map { |name| send name }
49
+ end
50
+
51
+ # Write a single attribute to the model's attribute hash.
52
+ #
53
+ # @example Write the attribute with write_attribute
54
+ # person.write_attribute(:name, "Benjamin")
55
+ # @example Write an attribute with bracket syntax
56
+ # person[:name] = "Benjamin"
57
+ #
58
+ # @param [String, Symbol, #to_s] name The name of the attribute to update.
59
+ # @param [Object] value The value to set for the attribute.
60
+ #
61
+ # @raise [UnknownAttributeError] if the attribute is unknown
62
+ def write_attribute(name, value)
63
+ if respond_to? "#{name}="
64
+ send "#{name}=", value
65
+ else
66
+ fail Neo4j::UnknownAttributeError, "unknown attribute: #{name}"
67
+ end
68
+ end
69
+ alias_method :[]=, :write_attribute
70
+
71
+ private
72
+
73
+ # Read an attribute from the attributes hash
74
+ def attribute(name)
75
+ @attributes ||= {}
76
+ @attributes[name]
77
+ end
78
+
79
+ # Write an attribute to the attributes hash
80
+ def attribute=(name, value)
81
+ @attributes ||= {}
82
+ @attributes[name] = value
83
+ end
84
+
85
+ # Maps all attributes using the given block
86
+ #
87
+ # @example Stringify attributes
88
+ # person.attributes_map { |name| send(name).to_s }
89
+ #
90
+ # @yield [name] block called to return hash value
91
+ # @yieldparam [String] name The name of the attribute to map.
92
+ #
93
+ # @return [Hash{String => Object}] The Hash of mapped attributes
94
+ def attributes_map
95
+ Hash[self.class.attribute_names.map { |name| [name, yield(name)] }]
96
+ end
97
+
98
+ module ClassMethods
99
+ # Defines an attribute
100
+ #
101
+ # For each attribute that is defined, a getter and setter will be
102
+ # added as an instance method to the model. An
103
+ # {AttributeDefinition} instance will be added to result of the
104
+ # attributes class method.
105
+ #
106
+ # @example Define an attribute.
107
+ # attribute :name
108
+ #
109
+ # @param (see AttributeDefinition#initialize)
110
+ #
111
+ # @raise [DangerousAttributeError] if the attribute name conflicts with
112
+ # existing methods
113
+ #
114
+ # @return [AttributeDefinition] Attribute's definition
115
+ def attribute(name)
116
+ if dangerous_attribute?(name)
117
+ fail Neo4j::DangerousAttributeError, %(an attribute method named "#{name}" would conflict with an existing method)
118
+ else
119
+ attribute!(name)
120
+ end
121
+ end
122
+
123
+ # Returns an Array of attribute names as Strings
124
+ #
125
+ # @example Get attribute names
126
+ # Person.attribute_names
127
+ #
128
+ # @return [Array<String>] The attribute names
129
+ def attribute_names
130
+ attributes.keys
131
+ end
132
+
133
+ # Returns a Hash of AttributeDefinition instances
134
+ #
135
+ # @example Get attribute definitions
136
+ # Person.attributes
137
+ #
138
+ # @return [ActiveSupport::HashWithIndifferentAccess{String => Neo4j::Shared::AttributeDefinition}]
139
+ # The Hash of AttributeDefinition instances
140
+ def attributes
141
+ @attributes ||= ActiveSupport::HashWithIndifferentAccess.new
142
+ end
143
+
144
+ # Determine if a given attribute name is dangerous
145
+ #
146
+ # Some attribute names can cause conflicts with existing methods
147
+ # on an object. For example, an attribute named "timeout" would
148
+ # conflict with the timeout method that Ruby's Timeout library
149
+ # mixes into Object.
150
+ #
151
+ # @example Testing a harmless attribute
152
+ # Person.dangerous_attribute? :name #=> false
153
+ #
154
+ # @example Testing a dangerous attribute
155
+ # Person.dangerous_attribute? :nil #=> "nil?"
156
+ #
157
+ # @param name Attribute name
158
+ #
159
+ # @return [false, String] False or the conflicting method name
160
+ def dangerous_attribute?(name)
161
+ attribute_methods(name).detect do |method_name|
162
+ !DEPRECATED_OBJECT_METHODS.include?(method_name.to_s) && allocate.respond_to?(method_name, true)
163
+ end unless attribute_names.include? name.to_s
164
+ end
165
+
166
+ # Returns the class name plus its attribute names
167
+ #
168
+ # @example Inspect the model's definition.
169
+ # Person.inspect
170
+ #
171
+ # @return [String] Human-readable presentation of the attributes
172
+ def inspect
173
+ inspected_attributes = attribute_names.sort
174
+ attributes_list = "(#{inspected_attributes.join(', ')})" unless inspected_attributes.empty?
175
+ "#{name}#{attributes_list}"
176
+ end
177
+
178
+ protected
179
+
180
+ # Assign a set of attribute definitions, used when subclassing models
181
+ #
182
+ # @param [Array<Neo4j::Shared::DeclaredProperties>] The Array of
183
+ # AttributeDefinition instances
184
+ def attributes=(attributes)
185
+ @attributes = attributes
186
+ end
187
+
188
+ # Overrides ActiveModel::AttributeMethods to backport 3.2 fix
189
+ def instance_method_already_implemented?(method_name)
190
+ generated_attribute_methods.method_defined?(method_name)
191
+ end
192
+
193
+ private
194
+
195
+ # Expand an attribute name into its generated methods names
196
+ def attribute_methods(name)
197
+ attribute_method_matchers.map { |matcher| matcher.method_name name }
198
+ end
199
+
200
+ # Ruby inherited hook to assign superclass attributes to subclasses
201
+ def inherited(subclass)
202
+ super
203
+ subclass.attributes = attributes.dup
204
+ end
205
+ end
206
+ end
207
+ end
@@ -101,8 +101,7 @@ module Neo4j::Shared
101
101
  def unregister(name)
102
102
  # might need to be include?(name.to_s)
103
103
  fail ArgumentError, "Argument `#{name}` not an attribute" if not registered_properties[name]
104
- declared_prop = registered_properties[name]
105
- registered_properties.delete(declared_prop)
104
+ registered_properties.delete(name)
106
105
  unregister_magic_typecaster(name)
107
106
  unregister_property_default(name)
108
107
  end
@@ -133,11 +132,6 @@ module Neo4j::Shared
133
132
  @magic_typecast_properties ||= {}
134
133
  end
135
134
 
136
- # The known mappings of declared properties and their primitive types.
137
- def upstream_primitives
138
- @upstream_primitives ||= {}
139
- end
140
-
141
135
  EXCLUDED_TYPES = [Array, Range, Regexp]
142
136
  def value_for_where(key, value)
143
137
  return value unless prop = registered_properties[key]
@@ -166,7 +160,7 @@ module Neo4j::Shared
166
160
 
167
161
  # Prevents repeated calls to :_attribute_type, which isn't free and never changes.
168
162
  def fetch_upstream_primitive(attr)
169
- upstream_primitives[attr] || upstream_primitives[attr] = klass._attribute_type(attr)
163
+ registered_properties[attr].type
170
164
  end
171
165
 
172
166
  private