activegraph 10.0.0.pre.alpha.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1989 -0
  3. data/CONTRIBUTORS +12 -0
  4. data/Gemfile +24 -0
  5. data/README.md +107 -0
  6. data/bin/rake +17 -0
  7. data/config/locales/en.yml +5 -0
  8. data/config/neo4j/add_classnames.yml +1 -0
  9. data/config/neo4j/config.yml +38 -0
  10. data/lib/neo4j.rb +116 -0
  11. data/lib/neo4j/active_base.rb +89 -0
  12. data/lib/neo4j/active_node.rb +108 -0
  13. data/lib/neo4j/active_node/callbacks.rb +8 -0
  14. data/lib/neo4j/active_node/dependent.rb +11 -0
  15. data/lib/neo4j/active_node/dependent/association_methods.rb +49 -0
  16. data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +51 -0
  17. data/lib/neo4j/active_node/enum.rb +26 -0
  18. data/lib/neo4j/active_node/has_n.rb +612 -0
  19. data/lib/neo4j/active_node/has_n/association.rb +278 -0
  20. data/lib/neo4j/active_node/has_n/association/rel_factory.rb +61 -0
  21. data/lib/neo4j/active_node/has_n/association/rel_wrapper.rb +23 -0
  22. data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +108 -0
  23. data/lib/neo4j/active_node/id_property.rb +224 -0
  24. data/lib/neo4j/active_node/id_property/accessor.rb +62 -0
  25. data/lib/neo4j/active_node/initialize.rb +21 -0
  26. data/lib/neo4j/active_node/labels.rb +207 -0
  27. data/lib/neo4j/active_node/labels/index.rb +37 -0
  28. data/lib/neo4j/active_node/labels/reloading.rb +21 -0
  29. data/lib/neo4j/active_node/node_list_formatter.rb +13 -0
  30. data/lib/neo4j/active_node/node_wrapper.rb +54 -0
  31. data/lib/neo4j/active_node/orm_adapter.rb +82 -0
  32. data/lib/neo4j/active_node/persistence.rb +187 -0
  33. data/lib/neo4j/active_node/property.rb +60 -0
  34. data/lib/neo4j/active_node/query.rb +76 -0
  35. data/lib/neo4j/active_node/query/query_proxy.rb +374 -0
  36. data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +177 -0
  37. data/lib/neo4j/active_node/query/query_proxy_eager_loading/association_tree.rb +75 -0
  38. data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +110 -0
  39. data/lib/neo4j/active_node/query/query_proxy_find_in_batches.rb +19 -0
  40. data/lib/neo4j/active_node/query/query_proxy_link.rb +139 -0
  41. data/lib/neo4j/active_node/query/query_proxy_methods.rb +302 -0
  42. data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +86 -0
  43. data/lib/neo4j/active_node/query_methods.rb +68 -0
  44. data/lib/neo4j/active_node/reflection.rb +86 -0
  45. data/lib/neo4j/active_node/rels.rb +11 -0
  46. data/lib/neo4j/active_node/scope.rb +166 -0
  47. data/lib/neo4j/active_node/unpersisted.rb +48 -0
  48. data/lib/neo4j/active_node/validations.rb +59 -0
  49. data/lib/neo4j/active_rel.rb +67 -0
  50. data/lib/neo4j/active_rel/callbacks.rb +15 -0
  51. data/lib/neo4j/active_rel/initialize.rb +28 -0
  52. data/lib/neo4j/active_rel/persistence.rb +134 -0
  53. data/lib/neo4j/active_rel/persistence/query_factory.rb +95 -0
  54. data/lib/neo4j/active_rel/property.rb +95 -0
  55. data/lib/neo4j/active_rel/query.rb +101 -0
  56. data/lib/neo4j/active_rel/rel_wrapper.rb +31 -0
  57. data/lib/neo4j/active_rel/related_node.rb +87 -0
  58. data/lib/neo4j/active_rel/types.rb +82 -0
  59. data/lib/neo4j/active_rel/validations.rb +8 -0
  60. data/lib/neo4j/ansi.rb +14 -0
  61. data/lib/neo4j/class_arguments.rb +39 -0
  62. data/lib/neo4j/config.rb +135 -0
  63. data/lib/neo4j/core.rb +14 -0
  64. data/lib/neo4j/core/connection_failed_error.rb +6 -0
  65. data/lib/neo4j/core/cypher_error.rb +37 -0
  66. data/lib/neo4j/core/driver.rb +66 -0
  67. data/lib/neo4j/core/has_uri.rb +63 -0
  68. data/lib/neo4j/core/instrumentable.rb +36 -0
  69. data/lib/neo4j/core/label.rb +158 -0
  70. data/lib/neo4j/core/logging.rb +44 -0
  71. data/lib/neo4j/core/node.rb +23 -0
  72. data/lib/neo4j/core/querable.rb +88 -0
  73. data/lib/neo4j/core/query.rb +487 -0
  74. data/lib/neo4j/core/query_builder.rb +32 -0
  75. data/lib/neo4j/core/query_clauses.rb +727 -0
  76. data/lib/neo4j/core/query_ext.rb +24 -0
  77. data/lib/neo4j/core/query_find_in_batches.rb +49 -0
  78. data/lib/neo4j/core/relationship.rb +13 -0
  79. data/lib/neo4j/core/responses.rb +50 -0
  80. data/lib/neo4j/core/result.rb +33 -0
  81. data/lib/neo4j/core/schema.rb +30 -0
  82. data/lib/neo4j/core/schema_errors.rb +12 -0
  83. data/lib/neo4j/core/wrappable.rb +30 -0
  84. data/lib/neo4j/errors.rb +57 -0
  85. data/lib/neo4j/migration.rb +148 -0
  86. data/lib/neo4j/migrations.rb +27 -0
  87. data/lib/neo4j/migrations/base.rb +77 -0
  88. data/lib/neo4j/migrations/check_pending.rb +20 -0
  89. data/lib/neo4j/migrations/helpers.rb +105 -0
  90. data/lib/neo4j/migrations/helpers/id_property.rb +75 -0
  91. data/lib/neo4j/migrations/helpers/relationships.rb +66 -0
  92. data/lib/neo4j/migrations/helpers/schema.rb +51 -0
  93. data/lib/neo4j/migrations/migration_file.rb +24 -0
  94. data/lib/neo4j/migrations/runner.rb +195 -0
  95. data/lib/neo4j/migrations/schema.rb +44 -0
  96. data/lib/neo4j/migrations/schema_migration.rb +14 -0
  97. data/lib/neo4j/model_schema.rb +139 -0
  98. data/lib/neo4j/paginated.rb +27 -0
  99. data/lib/neo4j/railtie.rb +105 -0
  100. data/lib/neo4j/schema/operation.rb +102 -0
  101. data/lib/neo4j/shared.rb +60 -0
  102. data/lib/neo4j/shared/attributes.rb +216 -0
  103. data/lib/neo4j/shared/callbacks.rb +68 -0
  104. data/lib/neo4j/shared/cypher.rb +37 -0
  105. data/lib/neo4j/shared/declared_properties.rb +204 -0
  106. data/lib/neo4j/shared/declared_property.rb +109 -0
  107. data/lib/neo4j/shared/declared_property/index.rb +37 -0
  108. data/lib/neo4j/shared/enum.rb +167 -0
  109. data/lib/neo4j/shared/filtered_hash.rb +79 -0
  110. data/lib/neo4j/shared/identity.rb +34 -0
  111. data/lib/neo4j/shared/initialize.rb +64 -0
  112. data/lib/neo4j/shared/marshal.rb +23 -0
  113. data/lib/neo4j/shared/mass_assignment.rb +64 -0
  114. data/lib/neo4j/shared/permitted_attributes.rb +28 -0
  115. data/lib/neo4j/shared/persistence.rb +282 -0
  116. data/lib/neo4j/shared/property.rb +240 -0
  117. data/lib/neo4j/shared/query_factory.rb +102 -0
  118. data/lib/neo4j/shared/rel_type_converters.rb +43 -0
  119. data/lib/neo4j/shared/serialized_properties.rb +30 -0
  120. data/lib/neo4j/shared/type_converters.rb +433 -0
  121. data/lib/neo4j/shared/typecasted_attributes.rb +98 -0
  122. data/lib/neo4j/shared/typecaster.rb +53 -0
  123. data/lib/neo4j/shared/validations.rb +44 -0
  124. data/lib/neo4j/tasks/migration.rake +202 -0
  125. data/lib/neo4j/timestamps.rb +11 -0
  126. data/lib/neo4j/timestamps/created.rb +9 -0
  127. data/lib/neo4j/timestamps/updated.rb +9 -0
  128. data/lib/neo4j/transaction.rb +139 -0
  129. data/lib/neo4j/type_converters.rb +7 -0
  130. data/lib/neo4j/undeclared_properties.rb +53 -0
  131. data/lib/neo4j/version.rb +3 -0
  132. data/lib/neo4j/wrapper.rb +4 -0
  133. data/lib/rails/generators/neo4j/migration/migration_generator.rb +14 -0
  134. data/lib/rails/generators/neo4j/migration/templates/migration.erb +9 -0
  135. data/lib/rails/generators/neo4j/model/model_generator.rb +88 -0
  136. data/lib/rails/generators/neo4j/model/templates/migration.erb +9 -0
  137. data/lib/rails/generators/neo4j/model/templates/model.erb +15 -0
  138. data/lib/rails/generators/neo4j/upgrade_v8/templates/migration.erb +17 -0
  139. data/lib/rails/generators/neo4j/upgrade_v8/upgrade_v8_generator.rb +32 -0
  140. data/lib/rails/generators/neo4j_generator.rb +119 -0
  141. data/neo4j.gemspec +51 -0
  142. metadata +421 -0
@@ -0,0 +1,302 @@
1
+ module Neo4j
2
+ module ActiveNode
3
+ module Query
4
+ # rubocop:disable Metrics/ModuleLength
5
+ module QueryProxyMethods
6
+ # rubocop:enable Metrics/ModuleLength
7
+ FIRST = 'HEAD'
8
+ LAST = 'LAST'
9
+
10
+ def rels
11
+ fail 'Cannot get rels without a relationship variable.' if !@rel_var
12
+
13
+ pluck(@rel_var)
14
+ end
15
+
16
+ def rel
17
+ rels.first
18
+ end
19
+
20
+ def as(node_var)
21
+ new_link(node_var)
22
+ end
23
+
24
+ # Give ability to call `#find` on associations to get a scoped find
25
+ # Doesn't pass through via `method_missing` because Enumerable has a `#find` method
26
+ def find(*args)
27
+ scoping { @model.find(*args) }
28
+ end
29
+
30
+ def first(target = nil)
31
+ first_and_last(FIRST, target)
32
+ end
33
+
34
+ def last(target = nil)
35
+ first_and_last(LAST, target)
36
+ end
37
+
38
+ def order_property
39
+ # This should maybe be based on a setting in the association
40
+ # rather than a hardcoded `nil`
41
+ model ? model.id_property_name : nil
42
+ end
43
+
44
+ def distinct
45
+ new_link.tap do |e|
46
+ e.instance_variable_set(:@distinct, true)
47
+ end
48
+ end
49
+
50
+ def propagate_context(query_proxy)
51
+ query_proxy.instance_variable_set(:@distinct, @distinct)
52
+ end
53
+
54
+ # @return [Integer] number of nodes of this class
55
+ def count(distinct = nil, target = nil)
56
+ return 0 if unpersisted_start_object?
57
+ fail(Neo4j::InvalidParameterError, ':count accepts the `:distinct` symbol or nil as a parameter') unless distinct.nil? || distinct == :distinct
58
+ query_with_target(target) do |var|
59
+ q = ensure_distinct(var, !distinct.nil?)
60
+ limited_query = self.query.clause?(:limit) ? self.query.break.with(var) : self.query.reorder
61
+ limited_query.pluck("count(#{q}) AS #{var}").first
62
+ end
63
+ end
64
+
65
+ def size
66
+ result_cache? ? result_cache_for.length : count
67
+ end
68
+
69
+ delegate :length, to: :to_a
70
+
71
+ # TODO: update this with public API methods if/when they are exposed
72
+ def limit_value
73
+ return unless self.query.clause?(:limit)
74
+ limit_clause = self.query.send(:clauses).find { |clause| clause.is_a?(Neo4j::Core::QueryClauses::LimitClause) }
75
+ limit_clause.instance_variable_get(:@arg)
76
+ end
77
+
78
+ def empty?(target = nil)
79
+ return true if unpersisted_start_object?
80
+ query_with_target(target) { |var| !self.exists?(nil, var) }
81
+ end
82
+
83
+ alias blank? empty?
84
+
85
+ # @param [Neo4j::ActiveNode, Neo4j::Node, String] other An instance of a Neo4j.rb model, a Neo4j-core node, or a string uuid
86
+ # @param [String, Symbol] target An identifier of a link in the Cypher chain
87
+ # @return [Boolean]
88
+ def include?(other, target = nil)
89
+ query_with_target(target) do |var|
90
+ where_filter = if other.respond_to?(:neo_id) || association_id_key == :neo_id
91
+ "ID(#{var}) = {other_node_id}"
92
+ else
93
+ "#{var}.#{association_id_key} = {other_node_id}"
94
+ end
95
+ 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
97
+ end
98
+ end
99
+
100
+ def exists?(node_condition = nil, target = nil)
101
+ unless [Integer, String, Hash, NilClass].any? { |c| node_condition.is_a?(c) }
102
+ fail(Neo4j::InvalidParameterError, ':exists? only accepts ids or conditions')
103
+ end
104
+ query_with_target(target) do |var|
105
+ start_q = exists_query_start(node_condition, var)
106
+ result = start_q.query.reorder.return("ID(#{var}) AS proof_of_life LIMIT 1").first
107
+ !!result
108
+ end
109
+ end
110
+
111
+ # Shorthand for `MATCH (start)-[r]-(other_node) WHERE ID(other_node) = #{other_node.neo_id}`
112
+ # The `node` param can be a persisted ActiveNode instance, any string or integer, or nil.
113
+ # When it's a node, it'll use the object's neo_id, which is fastest. When not nil, it'll figure out the
114
+ # primary key of that model. When nil, it uses `1 = 2` to prevent matching all records, which is the default
115
+ # behavior when nil is passed to `where` in QueryProxy.
116
+ # @param [#neo_id, String, Enumerable] node A node, a string representing a node's ID, or an enumerable of nodes or IDs.
117
+ # @return [Neo4j::ActiveNode::Query::QueryProxy] A QueryProxy object upon which you can build.
118
+ def match_to(node)
119
+ first_node = node.is_a?(Array) ? node.first : node
120
+ where_arg = if first_node.respond_to?(:neo_id)
121
+ {neo_id: node.is_a?(Array) ? node.map(&:neo_id) : node}
122
+ elsif !node.nil?
123
+ {association_id_key => node.is_a?(Array) ? ids_array(node) : node}
124
+ else
125
+ # support for null object pattern
126
+ '1 = 2'
127
+ end
128
+
129
+ self.where(where_arg)
130
+ end
131
+
132
+
133
+ # Gives you the first relationship between the last link of a QueryProxy chain and a given node
134
+ # Shorthand for `MATCH (start)-[r]-(other_node) WHERE ID(other_node) = #{other_node.neo_id} RETURN r`
135
+ # @param [#neo_id, String, Enumerable] node An object to be sent to `match_to`. See params for that method.
136
+ # @return A relationship (ActiveRel, CypherRelationship, EmbeddedRelationship) or nil.
137
+ def first_rel_to(node)
138
+ self.match_to(node).limit(1).pluck(rel_var).first
139
+ end
140
+
141
+ # Returns all relationships across a QueryProxy chain between a given node or array of nodes and the preceeding link.
142
+ # @param [#neo_id, String, Enumerable] node An object to be sent to `match_to`. See params for that method.
143
+ # @return An enumerable of relationship objects.
144
+ def rels_to(node)
145
+ self.match_to(node).pluck(rel_var)
146
+ end
147
+ alias all_rels_to rels_to
148
+
149
+ # When called, this method returns a single node that satisfies the match specified in the params hash.
150
+ # If no existing node is found to satisfy the match, one is created or associated as expected.
151
+ def find_or_create_by(params)
152
+ fail 'Method invalid when called on Class objects' unless source_object
153
+ result = self.where(params).first
154
+ return result unless result.nil?
155
+ Neo4j::ActiveBase.run_transaction do
156
+ node = model.create(params)
157
+ self << node
158
+ return node
159
+ end
160
+ end
161
+
162
+ def find_or_initialize_by(attributes, &block)
163
+ find_by(attributes) || initialize_by_current_chain_params(attributes, &block)
164
+ end
165
+
166
+ def first_or_initialize(attributes = {}, &block)
167
+ first || initialize_by_current_chain_params(attributes, &block)
168
+ end
169
+
170
+ # A shortcut for attaching a new, optional match to the end of a QueryProxy chain.
171
+ def optional(association, node_var = nil, rel_var = nil)
172
+ self.send(association, node_var, rel_var, optional: true)
173
+ end
174
+
175
+ # Takes an Array of ActiveNode models and applies the appropriate WHERE clause
176
+ # So for a `Teacher` model inheriting from a `Person` model and an `Article` model
177
+ # if you called .as_models([Teacher, Article])
178
+ # The where clause would look something like:
179
+ #
180
+ # .. code-block:: cypher
181
+ #
182
+ # WHERE (node_var:Teacher:Person OR node_var:Article)
183
+ def as_models(models)
184
+ where_clause = models.map do |model|
185
+ "`#{identity}`:" + model.mapped_label_names.map do |mapped_label_name|
186
+ "`#{mapped_label_name}`"
187
+ end.join(':')
188
+ end.join(' OR ')
189
+
190
+ where("(#{where_clause})")
191
+ end
192
+
193
+ # Matches all nodes having at least a relation
194
+ #
195
+ # @example Load all people having a friend
196
+ # Person.all.having_rel(:friends).to_a # => Returns a list of `Person`
197
+ #
198
+ # @example Load all people having a best friend
199
+ # Person.all.having_rel(:friends, best: true).to_a # => Returns a list of `Person`
200
+ #
201
+ # @return [QueryProxy] A new QueryProxy
202
+ def having_rel(association_name, rel_properties = {})
203
+ association = association_or_fail(association_name)
204
+ where("(#{identity})#{association.arrow_cypher(nil, rel_properties)}()")
205
+ end
206
+
207
+ # Matches all nodes not having a certain relation
208
+ #
209
+ # @example Load all people not having friends
210
+ # Person.all.not_having_rel(:friends).to_a # => Returns a list of `Person`
211
+ #
212
+ # @example Load all people not having best friends
213
+ # Person.all.not_having_rel(:friends, best: true).to_a # => Returns a list of `Person`
214
+ #
215
+ # @return [QueryProxy] A new QueryProxy
216
+ def not_having_rel(association_name, rel_properties = {})
217
+ association = association_or_fail(association_name)
218
+ where_not("(#{identity})#{association.arrow_cypher(nil, rel_properties)}()")
219
+ end
220
+
221
+ private
222
+
223
+ def association_or_fail(association_name)
224
+ model.associations[association_name] || fail(ArgumentError, "No such association #{association_name}")
225
+ end
226
+
227
+ def find_inverse_association!(model, source, association)
228
+ model.associations.values.find do |reverse_association|
229
+ association.inverse_of?(reverse_association) ||
230
+ reverse_association.inverse_of?(association) ||
231
+ inverse_relation_of?(source, association, model, reverse_association)
232
+ end || fail("Could not find reverse association for #{@context}")
233
+ end
234
+
235
+ def inverse_relation_of?(source, source_association, target, target_association)
236
+ source_association.direction != target_association.direction &&
237
+ source == target_association.target_class &&
238
+ target == source_association.target_class &&
239
+ source_association.relationship_class_name == target_association.relationship_class_name
240
+ end
241
+
242
+ def initialize_by_current_chain_params(params = {})
243
+ result = new(where_clause_params.merge(params))
244
+
245
+ inverse_association = find_inverse_association!(model, source_object.class, association) if source_object
246
+ result.tap do |m|
247
+ yield(m) if block_given?
248
+ m.public_send(inverse_association.name) << source_object if inverse_association
249
+ end
250
+ end
251
+
252
+ def where_clause_params
253
+ query.clauses.select { |c| c.is_a?(Neo4j::Core::QueryClauses::WhereClause) && c.arg.is_a?(Hash) }
254
+ .map! { |e| e.arg[identity] }.compact.inject { |a, b| a.merge(b) } || {}
255
+ end
256
+
257
+ def first_and_last(func, target)
258
+ new_query, pluck_proc = if self.query.clause?(:order)
259
+ [self.query.with(identity),
260
+ proc { |var| "#{func}(COLLECT(#{var})) as #{var}" }]
261
+ else
262
+ ord_prop = (func == LAST ? {order_property => :DESC} : order_property)
263
+ [self.order(ord_prop).limit(1),
264
+ proc { |var| var }]
265
+ end
266
+ query_with_target(target) do |var|
267
+ final_pluck = pluck_proc.call(var)
268
+ new_query.pluck(final_pluck)
269
+ end.first
270
+ end
271
+
272
+ # @return [String] The primary key of a the current QueryProxy's model or target class
273
+ def association_id_key
274
+ self.association.nil? ? model.primary_key : self.association.target_class.primary_key
275
+ end
276
+
277
+ # @param [Enumerable] node An enumerable of nodes or ids.
278
+ # @return [Array] An array after having `id` called on each object
279
+ def ids_array(node)
280
+ node.first.respond_to?(:id) ? node.map(&:id) : node
281
+ end
282
+
283
+ def query_with_target(target)
284
+ yield(target || identity)
285
+ end
286
+
287
+ def exists_query_start(condition, target)
288
+ case condition
289
+ when Integer
290
+ self.where("ID(#{target}) = {exists_condition}").params(exists_condition: condition)
291
+ when Hash
292
+ self.where(condition.keys.first => condition.values.first)
293
+ when String
294
+ self.where(model.primary_key => condition)
295
+ else
296
+ self
297
+ end
298
+ end
299
+ end
300
+ end
301
+ end
302
+ end
@@ -0,0 +1,86 @@
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::Core::CypherError # <=- Seems hacky
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.map do |node|
56
+ node if _create_relation_or_defer(node)
57
+ end.compact
58
+ # nodes.each { |node| self << node }
59
+ end
60
+
61
+ # Returns all relationships between a node and its last link in the QueryProxy chain, destroys them in Ruby. Callbacks will be run.
62
+ def destroy(node)
63
+ self.rels_to(node).map!(&:destroy)
64
+ clear_source_object_cache
65
+ end
66
+
67
+ private
68
+
69
+ def update_all_with_query(var_name, updates, params)
70
+ query = all.query
71
+
72
+ case updates
73
+ when Hash then query.set(var_name => updates).pluck("count(#{var_name})").first
74
+ when String then query.set(updates).params(params).pluck("count(#{var_name})").first
75
+ else
76
+ fail ArgumentError, "Invalid parameter type #{updates.class} for `updates`."
77
+ end
78
+ end
79
+
80
+ def clear_source_object_cache
81
+ self.source_object.clear_association_cache if self.source_object.respond_to?(:clear_association_cache)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,68 @@
1
+ module Neo4j
2
+ module ActiveNode
3
+ module QueryMethods
4
+ def exists?(node_condition = nil)
5
+ unless [Integer, String, Hash, NilClass].any? { |c| node_condition.is_a?(c) }
6
+ fail(Neo4j::InvalidParameterError, ':exists? only accepts ids or conditions')
7
+ end
8
+ query_start = exists_query_start(node_condition)
9
+ start_q = query_start.respond_to?(:query_as) ? query_start.query_as(:n) : query_start
10
+ result = start_q.return('ID(n) AS proof_of_life LIMIT 1').first
11
+ !!result
12
+ end
13
+
14
+ # Returns the first node of this class, sorted by ID. Note that this may not be the first node created since Neo4j recycles IDs.
15
+ def first
16
+ self.query_as(:n).limit(1).order(n: primary_key).pluck(:n).first
17
+ end
18
+
19
+ # Returns the last node of this class, sorted by ID. Note that this may not be the first node created since Neo4j recycles IDs.
20
+ def last
21
+ self.query_as(:n).limit(1).order(n: {primary_key => :desc}).pluck(:n).first
22
+ end
23
+
24
+ # @return [Integer] number of nodes of this class
25
+ def count(distinct = nil)
26
+ fail(Neo4j::InvalidParameterError, ':count accepts the `:distinct` symbol or nil as a parameter') unless distinct.nil? || distinct == :distinct
27
+ q = distinct.nil? ? 'n' : 'DISTINCT n'
28
+ self.query_as(:n).return("count(#{q}) AS count").first.count
29
+ end
30
+
31
+ alias size count
32
+ alias length count
33
+
34
+ def empty?
35
+ !self.all.exists?
36
+ end
37
+
38
+ alias blank? empty?
39
+
40
+ def find_in_batches(options = {})
41
+ self.query_as(:n).return(:n).find_in_batches(:n, primary_key, options) do |batch|
42
+ yield batch.map(&:n)
43
+ end
44
+ end
45
+
46
+ def find_each(options = {})
47
+ self.query_as(:n).return(:n).find_each(:n, primary_key, options) do |batch|
48
+ yield batch.n
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def exists_query_start(node_condition)
55
+ case node_condition
56
+ when Integer
57
+ self.query_as(:n).where('ID(n)' => node_condition)
58
+ when String
59
+ self.query_as(:n).where(n: {primary_key => node_condition})
60
+ when Hash
61
+ self.where(node_condition.keys.first => node_condition.values.first)
62
+ else
63
+ self.query_as(:n)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end