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