neo4j 6.1.12 → 7.0.0.rc.1

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: 7cdaac2ed51c1376e2fd9cfba0e3dd46065ce1ab
4
- data.tar.gz: 4ae2e390db76490a98c42e2719d8416c7da0194c
3
+ metadata.gz: b6bcfe613b82f80a6f772cb99f0df6042882209f
4
+ data.tar.gz: 2d63ea0d2a44bc68cd69bb59977bab5b056c8ab1
5
5
  SHA512:
6
- metadata.gz: ae1bded3ab39626e16264cefa89bd00f9ceafc64d9284a98b6a09403341abde2f09f3a5ef9105589199a1c36976d4994278a4ca3c01816c514e936d9f0df39d3
7
- data.tar.gz: 9e2c5859d7e47eefc8135ca5bc54ae08047f07fca167da657631bbe05ad592e6b946f1d548f66ae79c88694ec9fd37a50ffde3857617842d488a7d6d69c64e3c
6
+ metadata.gz: 5e8ae42d5b5708112736c066c5d033ee964130ae3df87ef0a6b4686ae446936770114a38ef66c0a3e2e446a9f9d98f95f0f71b3fd77a932c46b39a0ddce401ec
7
+ data.tar.gz: f3fa6b6e2b81eeff82fe724cb6ac1f1491d3ea93754787a9c7089737ddf394d8b2e8f687c9fc60ac653120e12e5416418d046345d356d97733d10f71375297df
data/CHANGELOG.md CHANGED
@@ -3,29 +3,34 @@ 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.1.12] - 05-27-2016
6
+ ## [7.0.0.rc.1] - 03-08-2016
7
7
 
8
- ### Fixed
9
-
10
- - Fix to `find_in_batches` (thanks to ProGM / see #1208)
11
-
12
- ## [6.1.11] - 05-25-2016
8
+ ### Changed
13
9
 
14
- ### Fixed
10
+ - All explicit dependencies on `ActiveAttr` code that was not removed outright can now be found in the `Neo4j::Shared` namespace.
11
+ - All type conversion uses Neo4j.rb-owned converters in the `Neo4j::Shared::TypeConverters` namespace. This is of particular importance where `Boolean` is concerned. Where explicitly using `ActiveAttr::Typecasting::Boolean`, use `Neo4j::Shared::Boolean`.
12
+ - `Neo4j::Shared::TypeConverters.converters` was replaced with `Neo4j::Shared::TypeConverters::CONVERTERS`.
13
+ - Error classes refactor: All errors now inherits from `Neo4j::Error`. All specific `InvalidParameterError` were replaced with a more generic `Neo4j::InvalidParameterError`.
14
+ - When calling `Node.find(...)` with missing ids, `Neo4j::RecordNotFound` now returns a better error message and some informations about the query.
15
15
 
16
- - Allow models to use their superclass' scopes (thanks to veetow for the heads-up / see #1205)
16
+ #### Internal
17
17
 
18
- ## [6.1.10] - 03-14-2016
18
+ - Ran transpec and fixed error warning (thanks brucek / #1132)
19
19
 
20
- ### Fixed
21
-
22
- - Fixed issue where backticks weren't being added to where clauses for `with_associations`
20
+ ### Added
23
21
 
24
- ## [6.1.9] - 2016-03-08
22
+ - A number of modules and unit tests were moved directly from the ActiveAttr gem, which is no longer being maintained.
23
+ - `ActiveNode` models now respond to `update_all` (thanks ProGM / #1113)
24
+ - Association chains now respond to `update_all` and `update_all_rels` (thanks ProGM / #1113)
25
+ - Rails will now rescue all `Neo4j::RecordNotFound` errors with a 404 status code by default
26
+ - A clone of [ActiveRecord::Enum](http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html) API. See docs for details. (thanks ProGM / #1129)
27
+ - Added #branch method to `QueryProxy` to allow for easy branching of matches in association chains (thanks ProGM / #1147 / #1143)
25
28
 
26
- ### Fixed
29
+ ### Removed
27
30
 
28
- - Issue where creating relationships via `has_one` association created two relationships (forward ported from 6.0.7)
31
+ - All external [ActiveAttr](https://github.com/cgriego/active_attr) dependencies.
32
+ - All `call` class methods from Type Converters. Use `to_ruby` instead.
33
+ - `Neo4j::ActiveNode::Labels::InvalidQueryError`, since it's unused.
29
34
 
30
35
  ## [6.1.8] - 2016-03-02
31
36
 
@@ -94,24 +99,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
94
99
  - `config/neo4j.yml` now renders with an ERB step (thanks to mrstif via #1060)
95
100
  - `#increment`, `#increment!` and `#concurrent_increment!` methods added to instances of ActiveNode and ActiveRel (thanks to ProGM in #1074)
96
101
 
97
- ## [6.0.9] - 05-27-2016
98
-
99
- ### Fixed
100
-
101
- - Fix to `find_in_batches` (thanks to ProGM / see #1208)
102
-
103
- ## [6.0.8] - 03-14-2016
104
-
105
- ### Fixed
106
-
107
- - Fixed issue where backticks weren't being added to where clauses for `with_associations`
108
-
109
- ## [6.0.7] - 03-08-2016
110
-
111
- ### Fixed
112
-
113
- - Issue where creating relationships via `has_one` association created two relationships
114
-
115
102
  ## [6.0.6] - 01-20-2016
116
103
 
117
104
  ### Fixed
data/lib/neo4j.rb CHANGED
@@ -1,14 +1,5 @@
1
1
  require 'neo4j/version'
2
2
 
3
- # require "delegate"
4
- # require "time"
5
- # require "set"
6
- #
7
- # require "active_support/core_ext"
8
- # require "active_support/json"
9
- # require "active_support/inflector"
10
- # require "active_support/time_with_zone"
11
-
12
3
  require 'neo4j-core'
13
4
  require 'neo4j/core/cypher_session'
14
5
  require 'neo4j/core/query'
@@ -16,7 +7,6 @@ require 'active_model'
16
7
  require 'active_support/concern'
17
8
  require 'active_support/core_ext/class/attribute.rb'
18
9
 
19
- require 'active_attr'
20
10
  require 'neo4j/errors'
21
11
  require 'neo4j/config'
22
12
  require 'neo4j/wrapper'
@@ -36,6 +26,10 @@ require 'neo4j/shared/filtered_hash'
36
26
  require 'neo4j/shared/declared_property/index'
37
27
  require 'neo4j/shared/declared_property'
38
28
  require 'neo4j/shared/declared_properties'
29
+ require 'neo4j/shared/enum'
30
+ require 'neo4j/shared/mass_assignment'
31
+ require 'neo4j/shared/attributes'
32
+ require 'neo4j/shared/typecasted_attributes'
39
33
  require 'neo4j/shared/property'
40
34
  require 'neo4j/shared/persistence'
41
35
  require 'neo4j/shared/validations'
@@ -61,8 +55,10 @@ require 'neo4j/active_rel'
61
55
  require 'neo4j/active_node/dependent'
62
56
  require 'neo4j/active_node/dependent/query_proxy_methods'
63
57
  require 'neo4j/active_node/dependent/association_methods'
58
+ require 'neo4j/active_node/enum'
64
59
  require 'neo4j/active_node/query_methods'
65
60
  require 'neo4j/active_node/query/query_proxy_methods'
61
+ require 'neo4j/active_node/query/query_proxy_methods_of_mass_updating'
66
62
  require 'neo4j/active_node/query/query_proxy_enumerable'
67
63
  require 'neo4j/active_node/query/query_proxy_find_in_batches'
68
64
  require 'neo4j/active_node/query/query_proxy_eager_loading'
@@ -43,6 +43,7 @@ module Neo4j
43
43
  include Neo4j::ActiveNode::HasN
44
44
  include Neo4j::ActiveNode::Scope
45
45
  include Neo4j::ActiveNode::Dependent
46
+ include Neo4j::ActiveNode::Enum
46
47
 
47
48
  def initialize(args = nil)
48
49
  symbol_args = args.is_a?(Hash) ? args.symbolize_keys : args
@@ -0,0 +1,29 @@
1
+ module Neo4j::ActiveNode
2
+ module Enum
3
+ extend ActiveSupport::Concern
4
+ include Neo4j::Shared::Enum
5
+
6
+ module ClassMethods
7
+ protected
8
+
9
+ def build_property_options(enum_keys, options = {})
10
+ if options[:_index]
11
+ super.merge!(index: :exact)
12
+ else
13
+ super
14
+ end
15
+ end
16
+
17
+ def define_enum_methods(property_name, enum_keys, options)
18
+ super
19
+ define_enum_scopes(property_name, enum_keys)
20
+ end
21
+
22
+ def define_enum_scopes(property_name, enum_keys)
23
+ enum_keys.keys.each do |name|
24
+ scope name, -> { where(property_name => name) }
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -2,7 +2,7 @@ module Neo4j::ActiveNode
2
2
  module HasN
3
3
  extend ActiveSupport::Concern
4
4
 
5
- class NonPersistedNodeError < StandardError; end
5
+ class NonPersistedNodeError < Neo4j::Error; end
6
6
 
7
7
  # Return this object from associations
8
8
  # It uses a QueryProxy to get results
@@ -79,7 +79,7 @@ module Neo4j
79
79
  return if model_class == false
80
80
 
81
81
  Array.new(target_classes).map do |target_class|
82
- "#{name}:`#{target_class.mapped_label_name}`"
82
+ "#{name}:#{target_class.mapped_label_name}"
83
83
  end.join(' OR ')
84
84
  end
85
85
 
@@ -20,8 +20,7 @@ module Neo4j
20
20
  Neo4j::ActiveNode::Labels.add_wrapped_class(model) unless Neo4j::ActiveNode::Labels._wrapped_classes.include?(model)
21
21
  end
22
22
 
23
- class InvalidQueryError < StandardError; end
24
- class RecordNotFound < StandardError; end
23
+ class RecordNotFound < Neo4j::RecordNotFound; end
25
24
 
26
25
  # @return the labels
27
26
  # @see Neo4j-core
@@ -78,17 +77,18 @@ module Neo4j
78
77
  module ClassMethods
79
78
  include Neo4j::ActiveNode::QueryMethods
80
79
 
80
+ delegate :update_all, to: :all
81
+
81
82
  # Returns the object with the specified neo4j id.
82
83
  # @param [String,Integer] id of node to find
83
84
  def find(id)
84
85
  map_id = proc { |object| object.respond_to?(:id) ? object.send(:id) : object }
85
86
 
86
- result = if id.is_a?(Array)
87
- find_by_ids(id.map { |o| map_id.call(o) })
88
- else
89
- find_by_id(map_id.call(id))
90
- end
91
- fail Neo4j::RecordNotFound if result.blank?
87
+ result = find_by_id_or_ids(map_id, id)
88
+
89
+ fail RecordNotFound.new(
90
+ "Couldn't find #{name} with '#{id_property_name}'=#{id}",
91
+ name, id_property_name, id) if result.blank?
92
92
  result.tap { |r| find_callbacks!(r) }
93
93
  end
94
94
 
@@ -162,6 +162,14 @@ module Neo4j
162
162
 
163
163
  private
164
164
 
165
+ def find_by_id_or_ids(map_id, id)
166
+ if id.is_a?(Array)
167
+ find_by_ids(id.map(&map_id))
168
+ else
169
+ find_by_id(map_id.call(id))
170
+ end
171
+ end
172
+
165
173
  def find_callbacks!(result)
166
174
  case result
167
175
  when Neo4j::ActiveNode
@@ -128,15 +128,23 @@ module Neo4j::ActiveNode
128
128
  end
129
129
  end
130
130
 
131
- def merge(attributes)
132
- neo4j_session.query.merge(n: {self.mapped_label_names => attributes})
133
- .on_create_set(n: on_create_props(attributes))
134
- .on_match_set(n: on_match_props)
131
+ def merge(match_attributes, optional_attrs = {})
132
+ options = [:on_create, :on_match, :set]
133
+ optional_attrs.assert_valid_keys(*options)
134
+
135
+ optional_attrs.default = {}
136
+ on_create_attrs, on_match_attrs, set_attrs = optional_attrs.values_at(*options)
137
+
138
+ neo4j_session.query.merge(n: {self.mapped_label_names => match_attributes})
139
+ .on_create_set(n: on_create_props(on_create_attrs))
140
+ .on_match_set(n: on_match_props(on_match_attrs))
141
+ .break.set(n: set_attrs)
135
142
  .pluck(:n).first
136
143
  end
137
144
 
138
145
  def find_or_create(find_attributes, set_attributes = {})
139
146
  on_create_attributes = set_attributes.reverse_merge(on_create_props(find_attributes))
147
+
140
148
  neo4j_session.query.merge(n: {self.mapped_label_names => find_attributes})
141
149
  .on_create_set(n: on_create_attributes)
142
150
  .pluck(:n).first
@@ -162,8 +170,8 @@ module Neo4j::ActiveNode
162
170
  find_attributes.merge(self.new(find_attributes).props_for_create)
163
171
  end
164
172
 
165
- def on_match_props
166
- {}.tap { |props| props[:updated_at] = DateTime.now.to_i if attributes_nil_hash.key?('updated_at'.freeze) }
173
+ def on_match_props(match_attributes)
174
+ match_attributes.tap { |props| props[:updated_at] = DateTime.now.to_i if attributes_nil_hash.key?('updated_at') }
167
175
  end
168
176
  end
169
177
  end
@@ -4,6 +4,7 @@ module Neo4j
4
4
  class QueryProxy
5
5
  include Neo4j::ActiveNode::Query::QueryProxyEnumerable
6
6
  include Neo4j::ActiveNode::Query::QueryProxyMethods
7
+ include Neo4j::ActiveNode::Query::QueryProxyMethodsOfMassUpdating
7
8
  include Neo4j::ActiveNode::Query::QueryProxyFindInBatches
8
9
  include Neo4j::ActiveNode::Query::QueryProxyEagerLoading
9
10
  include Neo4j::ActiveNode::Dependent::QueryProxyMethods
@@ -173,6 +174,25 @@ module Neo4j
173
174
  self
174
175
  end
175
176
 
177
+ # Executes the relation chain specified in the block, while keeping the current scope
178
+ #
179
+ # @example Load all people that have friends
180
+ # Person.all.branch { friends }.to_a # => Returns a list of `Person`
181
+ #
182
+ # @example Load all people that has old friends
183
+ # Person.all.branch { friends.where('age > 70') }.to_a # => Returns a list of `Person`
184
+ #
185
+ # @yield the block that will be evaluated starting from the current scope
186
+ #
187
+ # @return [QueryProxy] A new QueryProxy
188
+ def branch(&block)
189
+ if block
190
+ instance_eval(&block).query.proxy_as(self.model, identity)
191
+ else
192
+ fail LocalJumpError, 'no block given'
193
+ end
194
+ end
195
+
176
196
  def [](index)
177
197
  # TODO: Maybe for this and other methods, use array if already loaded, otherwise
178
198
  # use OFFSET and LIMIT 1?
@@ -4,7 +4,7 @@ module Neo4j
4
4
  module QueryProxyFindInBatches
5
5
  def find_in_batches(options = {})
6
6
  query.return(identity).find_in_batches(identity, @model.primary_key, options) do |batch|
7
- yield batch.map(&identity)
7
+ yield batch.map(&:identity)
8
8
  end
9
9
  end
10
10
 
@@ -2,7 +2,6 @@ module Neo4j
2
2
  module ActiveNode
3
3
  module Query
4
4
  module QueryProxyMethods
5
- class InvalidParameterError < StandardError; end
6
5
  FIRST = 'HEAD'
7
6
  LAST = 'LAST'
8
7
 
@@ -30,32 +29,15 @@ module Neo4j
30
29
  first_and_last(LAST, target)
31
30
  end
32
31
 
33
- def first_and_last(func, target)
34
- new_query, pluck_proc = if self.query.clause?(:order)
35
- [self.query.with(identity),
36
- proc { |var| "#{func}(COLLECT(#{var})) as #{var}" }]
37
- else
38
- [self.order(order_property).limit(1),
39
- proc { |var| var }]
40
- end
41
- result = query_with_target(target) do |var|
42
- final_pluck = pluck_proc.call(var)
43
- new_query.pluck(final_pluck)
44
- end
45
- result.first
46
- end
47
-
48
32
  def order_property
49
33
  # This should maybe be based on a setting in the association
50
34
  # rather than a hardcoded `nil`
51
35
  model ? model.id_property_name : nil
52
36
  end
53
37
 
54
- private :first_and_last
55
-
56
38
  # @return [Integer] number of nodes of this class
57
39
  def count(distinct = nil, target = nil)
58
- fail(InvalidParameterError, ':count accepts `distinct` or nil as a parameter') unless distinct.nil? || distinct == :distinct
40
+ fail(Neo4j::InvalidParameterError, ':count accepts `distinct` or nil as a parameter') unless distinct.nil? || distinct == :distinct
59
41
  query_with_target(target) do |var|
60
42
  q = distinct.nil? ? var : "DISTINCT #{var}"
61
43
  limited_query = self.query.clause?(:limit) ? self.query.break.with(var) : self.query.reorder
@@ -93,32 +75,20 @@ module Neo4j
93
75
  "#{var}.#{association_id_key} = {other_node_id}"
94
76
  end
95
77
  node_id = other.respond_to?(:neo_id) ? other.neo_id : other
96
- self.where(where_filter).params(other_node_id: node_id).query.reorder.return("count(#{var}) as count").first.count > 0
78
+ self.where(where_filter).params(other_node_id: node_id).query.return("count(#{var}) as count").first.count > 0
97
79
  end
98
80
  end
99
81
 
100
82
  def exists?(node_condition = nil, target = nil)
101
- fail(InvalidParameterError, ':exists? only accepts neo_ids') unless node_condition.is_a?(Integer) || node_condition.is_a?(Hash) || node_condition.nil?
83
+ unless node_condition.is_a?(Integer) || node_condition.is_a?(Hash) || node_condition.nil?
84
+ fail(Neo4j::InvalidParameterError, ':exists? only accepts neo_ids')
85
+ end
102
86
  query_with_target(target) do |var|
103
87
  start_q = exists_query_start(node_condition, var)
104
88
  start_q.query.reorder.return("COUNT(#{var}) AS count").first.count > 0
105
89
  end
106
90
  end
107
91
 
108
- # Deletes a group of nodes and relationships within a QP chain. When identifier is omitted, it will remove the last link in the chain.
109
- # The optional argument must be a node identifier. A relationship identifier will result in a Cypher Error
110
- # @param identifier [String,Symbol] the optional identifier of the link in the chain to delete.
111
- def delete_all(identifier = nil)
112
- query_with_target(identifier) do |target|
113
- begin
114
- self.query.with(target).optional_match("(#{target})-[#{target}_rel]-()").delete("#{target}, #{target}_rel").exec
115
- rescue Neo4j::Session::CypherError
116
- self.query.delete(target).exec
117
- end
118
- clear_source_object_cache
119
- end
120
- end
121
-
122
92
  # Shorthand for `MATCH (start)-[r]-(other_node) WHERE ID(other_node) = #{other_node.neo_id}`
123
93
  # The `node` param can be a persisted ActiveNode instance, any string or integer, or nil.
124
94
  # When it's a node, it'll use the object's neo_id, which is fastest. When not nil, it'll figure out the
@@ -157,27 +127,6 @@ module Neo4j
157
127
  end
158
128
  alias_method :all_rels_to, :rels_to
159
129
 
160
- # Deletes the relationship between a node and its last link in the QueryProxy chain. Executed in the database, callbacks will not run.
161
- def delete(node)
162
- self.match_to(node).query.delete(rel_var).exec
163
- clear_source_object_cache
164
- end
165
-
166
- # Deletes the relationships between all nodes for the last step in the QueryProxy chain. Executed in the database, callbacks will not be run.
167
- def delete_all_rels
168
- return unless start_object && start_object._persisted_obj
169
- self.query.delete(rel_var).exec
170
- end
171
-
172
- # Deletes the relationships between all nodes for the last step in the QueryProxy chain and replaces them with relationships to the given nodes.
173
- # Executed in the database, callbacks will not be run.
174
- def replace_with(node_or_nodes)
175
- nodes = Array(node_or_nodes)
176
-
177
- self.delete_all_rels
178
- nodes.each { |node| self << node }
179
- end
180
-
181
130
  # When called, this method returns a single node that satisfies the match specified in the params hash.
182
131
  # If no existing node is found to satisfy the match, one is created or associated as expected.
183
132
  def find_or_create_by(params)
@@ -191,12 +140,6 @@ module Neo4j
191
140
  end
192
141
  end
193
142
 
194
- # Returns all relationships between a node and its last link in the QueryProxy chain, destroys them in Ruby. Callbacks will be run.
195
- def destroy(node)
196
- self.rels_to(node).map!(&:destroy)
197
- clear_source_object_cache
198
- end
199
-
200
143
  # A shortcut for attaching a new, optional match to the end of a QueryProxy chain.
201
144
  def optional(association, node_var = nil, rel_var = nil)
202
145
  self.send(association, node_var, rel_var, optional: true)
@@ -222,8 +165,18 @@ module Neo4j
222
165
 
223
166
  private
224
167
 
225
- def clear_source_object_cache
226
- self.source_object.clear_association_cache if self.source_object.respond_to?(:clear_association_cache)
168
+ def first_and_last(func, target)
169
+ new_query, pluck_proc = if self.query.clause?(:order)
170
+ [self.query.with(identity),
171
+ proc { |var| "#{func}(COLLECT(#{var})) as #{var}" }]
172
+ else
173
+ [self.order(order_property).limit(1),
174
+ proc { |var| var }]
175
+ end
176
+ query_with_target(target) do |var|
177
+ final_pluck = pluck_proc.call(var)
178
+ new_query.pluck(final_pluck)
179
+ end.first
227
180
  end
228
181
 
229
182
  # @return [String] The primary key of a the current QueryProxy's model or target class
@@ -243,8 +196,10 @@ module Neo4j
243
196
 
244
197
  def exists_query_start(condition, target)
245
198
  case condition
246
- when Integer then self.where("ID(#{target}) = {exists_condition}").params(exists_condition: condition)
247
- when Hash then self.where(condition.keys.first => condition.values.first)
199
+ when Integer
200
+ self.where("ID(#{target}) = {exists_condition}").params(exists_condition: condition)
201
+ when Hash
202
+ self.where(condition.keys.first => condition.values.first)
248
203
  else
249
204
  self
250
205
  end