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

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