activegraph 10.0.0.pre.alpha.6

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