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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1989 -0
- data/CONTRIBUTORS +12 -0
- data/Gemfile +24 -0
- data/README.md +107 -0
- data/bin/rake +17 -0
- data/config/locales/en.yml +5 -0
- data/config/neo4j/add_classnames.yml +1 -0
- data/config/neo4j/config.yml +38 -0
- data/lib/neo4j.rb +116 -0
- data/lib/neo4j/active_base.rb +89 -0
- data/lib/neo4j/active_node.rb +108 -0
- data/lib/neo4j/active_node/callbacks.rb +8 -0
- data/lib/neo4j/active_node/dependent.rb +11 -0
- data/lib/neo4j/active_node/dependent/association_methods.rb +49 -0
- data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +51 -0
- data/lib/neo4j/active_node/enum.rb +26 -0
- data/lib/neo4j/active_node/has_n.rb +612 -0
- data/lib/neo4j/active_node/has_n/association.rb +278 -0
- data/lib/neo4j/active_node/has_n/association/rel_factory.rb +61 -0
- data/lib/neo4j/active_node/has_n/association/rel_wrapper.rb +23 -0
- data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +108 -0
- data/lib/neo4j/active_node/id_property.rb +224 -0
- data/lib/neo4j/active_node/id_property/accessor.rb +62 -0
- data/lib/neo4j/active_node/initialize.rb +21 -0
- data/lib/neo4j/active_node/labels.rb +207 -0
- data/lib/neo4j/active_node/labels/index.rb +37 -0
- data/lib/neo4j/active_node/labels/reloading.rb +21 -0
- data/lib/neo4j/active_node/node_list_formatter.rb +13 -0
- data/lib/neo4j/active_node/node_wrapper.rb +54 -0
- data/lib/neo4j/active_node/orm_adapter.rb +82 -0
- data/lib/neo4j/active_node/persistence.rb +187 -0
- data/lib/neo4j/active_node/property.rb +60 -0
- data/lib/neo4j/active_node/query.rb +76 -0
- data/lib/neo4j/active_node/query/query_proxy.rb +374 -0
- data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +177 -0
- data/lib/neo4j/active_node/query/query_proxy_eager_loading/association_tree.rb +75 -0
- data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +110 -0
- data/lib/neo4j/active_node/query/query_proxy_find_in_batches.rb +19 -0
- data/lib/neo4j/active_node/query/query_proxy_link.rb +139 -0
- data/lib/neo4j/active_node/query/query_proxy_methods.rb +302 -0
- data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +86 -0
- data/lib/neo4j/active_node/query_methods.rb +68 -0
- data/lib/neo4j/active_node/reflection.rb +86 -0
- data/lib/neo4j/active_node/rels.rb +11 -0
- data/lib/neo4j/active_node/scope.rb +166 -0
- data/lib/neo4j/active_node/unpersisted.rb +48 -0
- data/lib/neo4j/active_node/validations.rb +59 -0
- data/lib/neo4j/active_rel.rb +67 -0
- data/lib/neo4j/active_rel/callbacks.rb +15 -0
- data/lib/neo4j/active_rel/initialize.rb +28 -0
- data/lib/neo4j/active_rel/persistence.rb +134 -0
- data/lib/neo4j/active_rel/persistence/query_factory.rb +95 -0
- data/lib/neo4j/active_rel/property.rb +95 -0
- data/lib/neo4j/active_rel/query.rb +101 -0
- data/lib/neo4j/active_rel/rel_wrapper.rb +31 -0
- data/lib/neo4j/active_rel/related_node.rb +87 -0
- data/lib/neo4j/active_rel/types.rb +82 -0
- data/lib/neo4j/active_rel/validations.rb +8 -0
- data/lib/neo4j/ansi.rb +14 -0
- data/lib/neo4j/class_arguments.rb +39 -0
- data/lib/neo4j/config.rb +135 -0
- data/lib/neo4j/core.rb +14 -0
- data/lib/neo4j/core/connection_failed_error.rb +6 -0
- data/lib/neo4j/core/cypher_error.rb +37 -0
- data/lib/neo4j/core/driver.rb +66 -0
- data/lib/neo4j/core/has_uri.rb +63 -0
- data/lib/neo4j/core/instrumentable.rb +36 -0
- data/lib/neo4j/core/label.rb +158 -0
- data/lib/neo4j/core/logging.rb +44 -0
- data/lib/neo4j/core/node.rb +23 -0
- data/lib/neo4j/core/querable.rb +88 -0
- data/lib/neo4j/core/query.rb +487 -0
- data/lib/neo4j/core/query_builder.rb +32 -0
- data/lib/neo4j/core/query_clauses.rb +727 -0
- data/lib/neo4j/core/query_ext.rb +24 -0
- data/lib/neo4j/core/query_find_in_batches.rb +49 -0
- data/lib/neo4j/core/relationship.rb +13 -0
- data/lib/neo4j/core/responses.rb +50 -0
- data/lib/neo4j/core/result.rb +33 -0
- data/lib/neo4j/core/schema.rb +30 -0
- data/lib/neo4j/core/schema_errors.rb +12 -0
- data/lib/neo4j/core/wrappable.rb +30 -0
- data/lib/neo4j/errors.rb +57 -0
- data/lib/neo4j/migration.rb +148 -0
- data/lib/neo4j/migrations.rb +27 -0
- data/lib/neo4j/migrations/base.rb +77 -0
- data/lib/neo4j/migrations/check_pending.rb +20 -0
- data/lib/neo4j/migrations/helpers.rb +105 -0
- data/lib/neo4j/migrations/helpers/id_property.rb +75 -0
- data/lib/neo4j/migrations/helpers/relationships.rb +66 -0
- data/lib/neo4j/migrations/helpers/schema.rb +51 -0
- data/lib/neo4j/migrations/migration_file.rb +24 -0
- data/lib/neo4j/migrations/runner.rb +195 -0
- data/lib/neo4j/migrations/schema.rb +44 -0
- data/lib/neo4j/migrations/schema_migration.rb +14 -0
- data/lib/neo4j/model_schema.rb +139 -0
- data/lib/neo4j/paginated.rb +27 -0
- data/lib/neo4j/railtie.rb +105 -0
- data/lib/neo4j/schema/operation.rb +102 -0
- data/lib/neo4j/shared.rb +60 -0
- data/lib/neo4j/shared/attributes.rb +216 -0
- data/lib/neo4j/shared/callbacks.rb +68 -0
- data/lib/neo4j/shared/cypher.rb +37 -0
- data/lib/neo4j/shared/declared_properties.rb +204 -0
- data/lib/neo4j/shared/declared_property.rb +109 -0
- data/lib/neo4j/shared/declared_property/index.rb +37 -0
- data/lib/neo4j/shared/enum.rb +167 -0
- data/lib/neo4j/shared/filtered_hash.rb +79 -0
- data/lib/neo4j/shared/identity.rb +34 -0
- data/lib/neo4j/shared/initialize.rb +64 -0
- data/lib/neo4j/shared/marshal.rb +23 -0
- data/lib/neo4j/shared/mass_assignment.rb +64 -0
- data/lib/neo4j/shared/permitted_attributes.rb +28 -0
- data/lib/neo4j/shared/persistence.rb +282 -0
- data/lib/neo4j/shared/property.rb +240 -0
- data/lib/neo4j/shared/query_factory.rb +102 -0
- data/lib/neo4j/shared/rel_type_converters.rb +43 -0
- data/lib/neo4j/shared/serialized_properties.rb +30 -0
- data/lib/neo4j/shared/type_converters.rb +433 -0
- data/lib/neo4j/shared/typecasted_attributes.rb +98 -0
- data/lib/neo4j/shared/typecaster.rb +53 -0
- data/lib/neo4j/shared/validations.rb +44 -0
- data/lib/neo4j/tasks/migration.rake +202 -0
- data/lib/neo4j/timestamps.rb +11 -0
- data/lib/neo4j/timestamps/created.rb +9 -0
- data/lib/neo4j/timestamps/updated.rb +9 -0
- data/lib/neo4j/transaction.rb +139 -0
- data/lib/neo4j/type_converters.rb +7 -0
- data/lib/neo4j/undeclared_properties.rb +53 -0
- data/lib/neo4j/version.rb +3 -0
- data/lib/neo4j/wrapper.rb +4 -0
- data/lib/rails/generators/neo4j/migration/migration_generator.rb +14 -0
- data/lib/rails/generators/neo4j/migration/templates/migration.erb +9 -0
- data/lib/rails/generators/neo4j/model/model_generator.rb +88 -0
- data/lib/rails/generators/neo4j/model/templates/migration.erb +9 -0
- data/lib/rails/generators/neo4j/model/templates/model.erb +15 -0
- data/lib/rails/generators/neo4j/upgrade_v8/templates/migration.erb +17 -0
- data/lib/rails/generators/neo4j/upgrade_v8/upgrade_v8_generator.rb +32 -0
- data/lib/rails/generators/neo4j_generator.rb +119 -0
- data/neo4j.gemspec +51 -0
- 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
|