neo4j 4.0.0.rc.1 → 4.0.0.rc.3

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: 122872ebd58702ee7d868124092285e8907cfbfd
4
- data.tar.gz: 11989c0af301ccb1305df5deb26e9c55a20313f6
3
+ metadata.gz: 28fe8a8c4370fbe08721a8ebd9f88665439a71f2
4
+ data.tar.gz: 802a04febb895b8a561e4c82b9324b159c4eb68a
5
5
  SHA512:
6
- metadata.gz: e9433059f7d5dd25d4bd22be97af295f98cbd1340f5bdf1c1869c01111b22d61bf8cf6e260d08de528c3cb90735984d069234fafe32556cd6b200c415b0d7243
7
- data.tar.gz: c894497b7e00362c8145bdbf61bdd8b50f772f108b839ae068971bd32e888f502897ecfc03c4284bfd1f838815b75fe8dc41d3222ea61b0d1157d371a3ff325e
6
+ metadata.gz: 37cbe5595a4991d3f13f44411c71e2c2ec4e3587489cdaec51f639668d94e3819dae84d4fc1213573fb76bac3e6f592ca3919617ea5fa1b9be840b767bf2fd41
7
+ data.tar.gz: 61781492746a92e307d2bc5769ce7d0db389c06a16864c750cec5981067594fc553d0cd0330984b9fd009b76817380cf9f6cd4224334f4c8b60572472d67b241
data/CHANGELOG CHANGED
@@ -1,3 +1,16 @@
1
+ == 4.0.0.rc.3
2
+ Released minutes after rc.2 to catch one late addition!
3
+ * Adds serialization support for QueryProxy.
4
+
5
+ == 4.0.0.rc.2
6
+ This release builds on features introduced in the first RC. We are releasing this as another RC because the API may be tweaked before release.
7
+ * New `proxy_as` for Core::Query to build QueryProxy chains onto Core::Query objects!
8
+ * Using `proxy_as`, new `optional` method in QueryProxy to use the `OPTIONAL MATCH` Cypher function.
9
+ * `match_to` and methods that depend on it now support arrays of nodes or IDs.
10
+ * New `rels_to`/`all_rels_to` methods.
11
+ * New `delete` and `destroy` methods in QueryProxy to easily remove relationships.
12
+ * Serialized objects will include IDs by default.
13
+
1
14
  == 4.0.0.rc.1
2
15
  This release introduces API changes that may be considered breaking under certain conditions. See See https://github.com/neo4jrb/neo4j/wiki/Neo4j.rb-v4-Introduction.
3
16
  Please use https://github.com/neo4jrb/neo4j/issues for support regarding this update! You can also reach us on Twitter: @neo4jrb (Brian) and @subvertallmedia (Chris).
@@ -10,6 +10,7 @@ require 'neo4j/version'
10
10
  #require "active_support/time_with_zone"
11
11
 
12
12
  require "neo4j-core"
13
+ require 'neo4j/core/query'
13
14
  require "active_model"
14
15
  require 'active_support/concern'
15
16
  require 'active_support/core_ext/class/attribute.rb'
@@ -119,7 +119,7 @@ module HasN
119
119
 
120
120
  instance_eval(%Q{
121
121
  def #{name}(node = nil, rel = nil, proxy_obj = nil)
122
- query_proxy = proxy_obj || Neo4j::ActiveNode::Query::QueryProxy.new(#{self.name}, nil, {
122
+ query_proxy = proxy_obj || Neo4j::ActiveNode::Query::QueryProxy.new(#{self.name}, nil, {
123
123
  session: self.neo4j_session, query_proxy: nil, context: '#{self.name}' + '##{name}'
124
124
  })
125
125
  context = (query_proxy && query_proxy.context ? query_proxy.context : '#{self.name}') + '##{name}'
@@ -131,6 +131,7 @@ module HasN
131
131
  node: node,
132
132
  rel: rel,
133
133
  context: context,
134
+ optional: query_proxy.optional?,
134
135
  caller: query_proxy.caller
135
136
  })
136
137
  end}, __FILE__, __LINE__)
@@ -167,11 +168,7 @@ module HasN
167
168
  result = #{name}_query_proxy(node: node, rel: rel, context: '#{self.name}##{name}')
168
169
  association = self.class.reflect_on_association(__method__)
169
170
  query_return = association_instance_get(result.to_cypher_with_params, association)
170
- if query_return.nil?
171
- association_instance_set(result.to_cypher_with_params, result.first, association)
172
- else
173
- query_return
174
- end
171
+ query_return || association_instance_set(result.to_cypher_with_params, result.first, association)
175
172
  end}, __FILE__, __LINE__)
176
173
 
177
174
  instance_eval(%Q{
@@ -9,7 +9,7 @@ module Neo4j
9
9
 
10
10
  # The most recent node to start a QueryProxy chain.
11
11
  # Will be nil when using QueryProxy chains on class methods.
12
- attr_reader :caller, :association, :model
12
+ attr_reader :caller, :association, :model, :starting_query
13
13
 
14
14
  # QueryProxy is ActiveNode's Cypher DSL. While the name might imply that it creates queries in a general sense,
15
15
  # it is actually referring to <tt>Neo4j::Core::Query</tt>, which is a pure Ruby Cypher DSL provided by the <tt>neo4j-core</tt> gem.
@@ -42,13 +42,15 @@ module Neo4j
42
42
  @session = options[:session]
43
43
  @caller = options[:caller]
44
44
  @chain = []
45
+ @starting_query = options[:starting_query]
46
+ @optional = options[:optional]
45
47
  @params = options[:query_proxy] ? options[:query_proxy].instance_variable_get('@params') : {}
46
48
  end
47
49
 
48
50
  # The current node identifier on deck, so to speak. It is the object that will be returned by calling `each` and the last node link
49
51
  # in the QueryProxy chain.
50
52
  def identity
51
- @node_var || :result
53
+ @node_var || _result_string
52
54
  end
53
55
  alias_method :node_identity, :identity
54
56
 
@@ -142,20 +144,18 @@ module Neo4j
142
144
  # student.lessons.query_as(:l).with('your cypher here...')
143
145
  def query_as(var)
144
146
  query = if @association
145
- chain_var = _association_chain_var
146
- label_string = @model && ":`#{@model.mapped_label_name}`"
147
- (_association_query_start(chain_var) & _query_model_as(var)).match("#{chain_var}#{_association_arrow}(#{var}#{label_string})")
148
- else
149
- _query_model_as(var)
150
- end
151
-
147
+ chain_var = _association_chain_var
148
+ label_string = @model && ":`#{@model.mapped_label_name}`"
149
+ (_association_query_start(chain_var) & _query_model_as(var)).send(_match_type, "#{chain_var}#{_association_arrow}(#{var}#{label_string})")
150
+ else
151
+ starting_query ? (starting_query & _query_model_as(var)) : _query_model_as(var)
152
+ end
152
153
  # Build a query chain via the chain, return the result
153
154
  @chain.inject(query.params(@params)) do |query, (method, arg)|
154
155
  query.send(method, arg.respond_to?(:call) ? arg.call(var) : arg)
155
156
  end
156
157
  end
157
158
 
158
-
159
159
  # Scope all queries to the current scope.
160
160
  #
161
161
  # Comment.where(post_id: 1).scoping do
@@ -182,7 +182,7 @@ module Neo4j
182
182
  # Returns a string of the cypher query with return objects and params
183
183
  # @param [Array] columns array containing symbols of identifiers used in the query
184
184
  # @return [String]
185
- def to_cypher_with_params(columns = [:result])
185
+ def to_cypher_with_params(columns = [self.identity])
186
186
  final_query = query.return_query(columns)
187
187
  "#{final_query.to_cypher} | params: #{final_query.send(:merge_params)}"
188
188
  end
@@ -232,6 +232,10 @@ module Neo4j
232
232
  end
233
233
  end
234
234
 
235
+ def read_attribute_for_serialization(*args)
236
+ to_a.map {|o| o.read_attribute_for_serialization(*args) }
237
+ end
238
+
235
239
  # QueryProxy objects act as a representation of a model at the class level so we pass through calls
236
240
  # This allows us to define class functions for reusable query chaining or for end-of-query aggregation/summarizing
237
241
  def method_missing(method_name, *args, &block)
@@ -243,6 +247,10 @@ module Neo4j
243
247
  end
244
248
  end
245
249
 
250
+ def optional?
251
+ @optional == true
252
+ end
253
+
246
254
  attr_reader :context
247
255
  attr_reader :node_var
248
256
 
@@ -259,12 +267,23 @@ module Neo4j
259
267
 
260
268
  def _query_model_as(var)
261
269
  match_arg = if @model
262
- label = @model.respond_to?(:mapped_label_name) ? @model.mapped_label_name : @model
263
- {var => label}
270
+ label = @model.respond_to?(:mapped_label_name) ? @model.mapped_label_name : @model
271
+ { var => label }
272
+ else
273
+ var
274
+ end
275
+ _session.query(context: @context).send(_match_type, match_arg)
276
+ end
277
+
278
+ # TODO: Refactor this. Too much happening here.
279
+ def _result_string
280
+ if self.association
281
+ "result_#{self.association.name}".to_sym
282
+ elsif self.model
283
+ "result_#{self.model.name.tr!(':', '')}".to_sym
264
284
  else
265
- var
285
+ :result
266
286
  end
267
- _session.query(context: @context).match(match_arg)
268
287
  end
269
288
 
270
289
  def _session
@@ -276,9 +295,7 @@ module Neo4j
276
295
  end
277
296
 
278
297
  def _chain_level
279
- if @options[:start_object]
280
- 1
281
- elsif query_proxy = @options[:query_proxy]
298
+ if query_proxy = @options[:query_proxy]
282
299
  query_proxy._chain_level + 1
283
300
  else
284
301
  1
@@ -309,6 +326,10 @@ module Neo4j
309
326
  :"rel#{_chain_level - 1}"
310
327
  end
311
328
 
329
+ def _match_type
330
+ @optional ? :optional_match : :match
331
+ end
332
+
312
333
  attr_writer :context
313
334
 
314
335
  private
@@ -59,7 +59,7 @@ module Neo4j
59
59
  rescue Neo4j::Session::CypherError
60
60
  self.query.delete(target).exec
61
61
  end
62
- self.caller.clear_association_cache if self.caller.respond_to?(:clear_association_cache)
62
+ clear_caller_cache
63
63
  end
64
64
  end
65
65
 
@@ -68,36 +68,84 @@ module Neo4j
68
68
  # When it's a node, it'll use the object's neo_id, which is fastest. When not nil, it'll figure out the
69
69
  # primary key of that model. When nil, it uses `1 = 2` to prevent matching all records, which is the default
70
70
  # behavior when nil is passed to `where` in QueryProxy.
71
+ # @param [#neo_id, String, Enumerable] node A node, a string representing a node's ID, or an enumerable of nodes or IDs.
71
72
  # @return [Neo4j::ActiveNode::Query::QueryProxy] A QueryProxy object upon which you can build.
72
73
  def match_to(node)
73
- if node.respond_to?(:neo_id)
74
- self.where(neo_id: node.neo_id)
75
- elsif !node.nil?
76
- id_key = self.association.nil? ? model.primary_key : self.association.target_class.primary_key
77
- self.where(id_key => node)
78
- else
79
- # support for null object pattern
80
- self.where('1 = 2')
81
- end
74
+ where_arg = if node.respond_to?(:neo_id)
75
+ { neo_id: node.neo_id }
76
+ elsif !node.nil?
77
+ id_key = association_id_key
78
+ node = ids_array(node) if node.is_a?(Array)
79
+ { id_key => node }
80
+ else
81
+ # support for null object pattern
82
+ '1 = 2'
83
+ end
84
+ self.where(where_arg)
82
85
  end
83
86
 
84
87
  # Gives you the first relationship between the last link of a QueryProxy chain and a given node
85
88
  # Shorthand for `MATCH (start)-[r]-(other_node) WHERE ID(other_node) = #{other_node.neo_id} RETURN r`
89
+ # @param [#neo_id, String, Enumerable] node An object to be sent to `match_to`. See params for that method.
90
+ # @return A relationship (ActiveRel, CypherRelationship, EmbeddedRelationship) or nil.
86
91
  def first_rel_to(node)
87
92
  self.match_to(node).limit(1).pluck(rel_identity).first
88
93
  end
89
94
 
95
+ # Returns all relationships across a QueryProxy chain between a given node or array of nodes and the preceeding link.
96
+ # @param [#neo_id, String, Enumerable] node An object to be sent to `match_to`. See params for that method.
97
+ # @return An enumerable of relationship objects.
98
+ def rels_to(node)
99
+ self.match_to(node).pluck(rel_identity)
100
+ end
101
+ alias_method :all_rels_to, :rels_to
102
+
103
+ # Deletes the relationship between a node and its last link in the QueryProxy chain. Executed in the database, callbacks will not run.
104
+ def delete(node)
105
+ self.match_to(node).query.delete(rel_identity).exec
106
+ clear_caller_cache
107
+ end
108
+
109
+ # Returns all relationships between a node and its last link in the QueryProxy chain, destroys them in Ruby. Callbacks will be run.
110
+ def destroy(node)
111
+ self.rels_to(node).map!(&:destroy)
112
+ clear_caller_cache
113
+ end
114
+
115
+ # A shortcut for attaching a new, optional match to the end of a QueryProxy chain.
116
+ # TODO: It's silly that we have to call constantize here. There should be a better way of finding the target class of the destination.
117
+ def optional(association, node_id = nil)
118
+ target_qp = self.send(association)
119
+ model = target_qp.name.constantize
120
+ var = node_id || target_qp.identity
121
+ self.query.proxy_as(model, var, true)
122
+ end
123
+
90
124
  private
91
125
 
92
- def query_with_target(target, &block)
93
- block.yield(target || identity)
126
+ def clear_caller_cache
127
+ self.caller.clear_association_cache if self.caller.respond_to?(:clear_association_cache)
128
+ end
129
+
130
+ # @return [String] The primary key of a the current QueryProxy's model or target class
131
+ def association_id_key
132
+ self.association.nil? ? model.primary_key : self.association.target_class.primary_key
133
+ end
134
+
135
+ # @param [Enumerable] node An enumerable of nodes or ids.
136
+ # @return [Array] An array after having `id` called on each object
137
+ def ids_array(node)
138
+ node.first.respond_to?(:id) ? node.map!(&:id) : node
139
+ end
140
+
141
+ def query_with_target(target)
142
+ yield(target || identity)
94
143
  end
95
144
 
96
145
  def exists_query_start(origin, condition, target)
97
- case
98
- when condition.class == Fixnum
146
+ if condition.class == Fixnum
99
147
  self.where("ID(#{target}) = {exists_condition}").params(exists_condition: condition)
100
- when condition.class == Hash
148
+ elsif condition.class == Hash
101
149
  self.where(condition.keys.first => condition.values.first)
102
150
  else
103
151
  self
@@ -0,0 +1,19 @@
1
+ module Neo4j::Core
2
+ class Query
3
+ # Creates a Neo4j::ActiveNode::Query::QueryProxy object that builds off of a Core::Query object.
4
+ #
5
+ # @param [Class] model An ActiveNode model to be used as the start of a new QueryuProxy chain
6
+ # @param [Symbol] var The variable to be used to refer to the object from within the new QueryProxy
7
+ # @param [Boolean] optional Indicate whether the new QueryProxy will use MATCH or OPTIONAL MATCH.
8
+ # @return [Neo4j::ActiveNode::Query::QueryProxy] A QueryProxy object.
9
+ def proxy_as(model, var, optional = false)
10
+ # TODO: Discuss whether it's necessary to call `break` on the query or if this should be left to the user.
11
+ Neo4j::ActiveNode::Query::QueryProxy.new(model, nil, { starting_query: self.break, node: var, optional: optional })
12
+ end
13
+
14
+ # Calls proxy_as with `optional` set true. This doesn't offer anything different from calling `proxy_as` directly but it may be more readable.
15
+ def proxy_as_optional(model, var)
16
+ proxy_as(model, var, true)
17
+ end
18
+ end
19
+ end
@@ -11,6 +11,10 @@ module Neo4j::Shared
11
11
  self.class.serialized_properties
12
12
  end
13
13
 
14
+ def serializable_hash(*args)
15
+ super.merge(id: id)
16
+ end
17
+
14
18
  module ClassMethods
15
19
  def serialized_properties
16
20
  @serialize || {}
@@ -1,3 +1,3 @@
1
1
  module Neo4j
2
- VERSION = "4.0.0.rc.1"
2
+ VERSION = "4.0.0.rc.3"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neo4j
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0.rc.1
4
+ version: 4.0.0.rc.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andreas Ronge
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-05 00:00:00.000000000 Z
11
+ date: 2014-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: orm_adapter
@@ -143,6 +143,7 @@ files:
143
143
  - lib/neo4j/active_rel/types.rb
144
144
  - lib/neo4j/active_rel/validations.rb
145
145
  - lib/neo4j/config.rb
146
+ - lib/neo4j/core/query.rb
146
147
  - lib/neo4j/migration.rb
147
148
  - lib/neo4j/paginated.rb
148
149
  - lib/neo4j/railtie.rb