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,177 @@
|
|
|
1
|
+
module Neo4j
|
|
2
|
+
module ActiveNode
|
|
3
|
+
module Query
|
|
4
|
+
module QueryProxyEagerLoading
|
|
5
|
+
class IdentityMap < Hash
|
|
6
|
+
def add(node)
|
|
7
|
+
self[node.neo_id] ||= node
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def pluck_vars(node, rel)
|
|
12
|
+
with_associations_tree.empty? ? super : perform_query
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def perform_query
|
|
16
|
+
@_cache = IdentityMap.new
|
|
17
|
+
build_query
|
|
18
|
+
.map do |record, eager_data|
|
|
19
|
+
cache_and_init(record, with_associations_tree)
|
|
20
|
+
eager_data.zip(with_associations_tree.paths.map(&:last)).each do |eager_records, element|
|
|
21
|
+
eager_records.first.zip(eager_records.last).each do |eager_record|
|
|
22
|
+
add_to_cache(*eager_record, element)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
record
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def with_associations(*spec)
|
|
30
|
+
new_link.tap do |new_query_proxy|
|
|
31
|
+
new_query_proxy.with_associations_tree = with_associations_tree.clone
|
|
32
|
+
new_query_proxy.with_associations_tree.add_spec(spec)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def propagate_context(query_proxy)
|
|
37
|
+
super
|
|
38
|
+
query_proxy.instance_variable_set('@with_associations_tree', @with_associations_tree)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def with_associations_tree
|
|
42
|
+
@with_associations_tree ||= association_tree_class.new(model)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def association_tree_class
|
|
46
|
+
AssociationTree
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def with_associations_tree=(tree)
|
|
50
|
+
@with_associations_tree = tree
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def first
|
|
54
|
+
(query.clause?(:order) ? self : order(order_property)).limit(1).to_a.first
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def add_to_cache(rel, node, element)
|
|
60
|
+
direction = element.association.direction
|
|
61
|
+
node = cache_and_init(node, element)
|
|
62
|
+
if rel.is_a?(Neo4j::ActiveRel)
|
|
63
|
+
rel.instance_variable_set(direction == :in ? '@from_node' : '@to_node', node)
|
|
64
|
+
end
|
|
65
|
+
@_cache[direction == :out ? rel.start_node_neo_id : rel.end_node_neo_id]
|
|
66
|
+
.association_proxy(element.name).add_to_cache(node, rel)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def init_associations(node, element)
|
|
70
|
+
element.each_key { |key| node.association_proxy(key).init_cache }
|
|
71
|
+
node.association_proxy(element.name).init_cache if element.rel_length == ''
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def cache_and_init(node, element)
|
|
75
|
+
@_cache.add(node).tap { |n| init_associations(n, element) }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def with_associations_return_clause
|
|
79
|
+
path_names.map { |n| var(n, :collection, &:itself) }.join(',')
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def var(*parts)
|
|
83
|
+
yield(escape(parts.compact.join('_')))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# In neo4j version 2.1.8 this fails due to a bug:
|
|
87
|
+
# MATCH (`n`) WITH `n` RETURN `n`
|
|
88
|
+
# but this
|
|
89
|
+
# MATCH (`n`) WITH n RETURN `n`
|
|
90
|
+
# and this
|
|
91
|
+
# MATCH (`n`) WITH `n` AS `n` RETURN `n`
|
|
92
|
+
# does not
|
|
93
|
+
def var_fix(*var)
|
|
94
|
+
var(*var, &method(:as_alias))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def as_alias(var)
|
|
98
|
+
"#{var} AS #{var}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def escape(s)
|
|
102
|
+
"`#{s}`"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def path_name(path)
|
|
106
|
+
path.map(&:name).join('.')
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def path_names
|
|
110
|
+
with_associations_tree.paths.map { |path| path_name(path) }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def build_query
|
|
114
|
+
before_pluck(query_from_association_tree).pluck(identity, "[#{with_associations_return_clause}]")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def before_pluck(query)
|
|
118
|
+
query_from_chain(@order_chain, query, identity)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def query_from_association_tree
|
|
122
|
+
previous_with_vars = []
|
|
123
|
+
with_associations_tree.paths.inject(query_as(identity).with(ensure_distinct(identity))) do |query, path|
|
|
124
|
+
with_association_query_part(query, path, previous_with_vars).tap do
|
|
125
|
+
previous_with_vars << var_fix(path_name(path), :collection)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def with_association_query_part(base_query, path, previous_with_vars)
|
|
131
|
+
optional_match_with_where(base_query, path, previous_with_vars)
|
|
132
|
+
.with(identity,
|
|
133
|
+
"[#{relationship_collection(path)}, collect(#{escape path_name(path)})] "\
|
|
134
|
+
"AS #{escape("#{path_name(path)}_collection")}",
|
|
135
|
+
*previous_with_vars)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def relationship_collection(path)
|
|
139
|
+
path.last.rel_length ? "collect(last(relationships(#{escape("#{path_name(path)}_path")})))" : "collect(#{escape("#{path_name(path)}_rel")})"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def optional_match_with_where(base_query, path, _)
|
|
143
|
+
path
|
|
144
|
+
.each_with_index.map { |_, index| path[0..index] }
|
|
145
|
+
.inject(optional_match(base_query, path)) do |query, path_prefix|
|
|
146
|
+
query.where(path_prefix.last.association.target_where_clause(escape(path_name(path_prefix))))
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def optional_match(base_query, path)
|
|
151
|
+
start_path = "#{escape("#{path_name(path)}_path")}=(#{identity})"
|
|
152
|
+
base_query.optional_match(
|
|
153
|
+
"#{start_path}#{path.each_with_index.map do |element, index|
|
|
154
|
+
relationship_part(element.association, path_name(path[0..index]), element.rel_length)
|
|
155
|
+
end.join}"
|
|
156
|
+
)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def relationship_part(association, path_name, rel_length)
|
|
160
|
+
if rel_length
|
|
161
|
+
rel_name = nil
|
|
162
|
+
length = {max: rel_length}
|
|
163
|
+
else
|
|
164
|
+
rel_name = escape("#{path_name}_rel")
|
|
165
|
+
length = nil
|
|
166
|
+
end
|
|
167
|
+
"#{association.arrow_cypher(rel_name, {}, false, false, length)}(#{escape(path_name)})"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def chain
|
|
171
|
+
@order_chain = @chain.select { |link| link.clause == :order } unless with_associations_tree.empty?
|
|
172
|
+
@chain
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module Neo4j
|
|
2
|
+
module ActiveNode
|
|
3
|
+
module Query
|
|
4
|
+
module QueryProxyEagerLoading
|
|
5
|
+
class AssociationTree < Hash
|
|
6
|
+
attr_accessor :model, :name, :association, :path, :rel_length
|
|
7
|
+
|
|
8
|
+
def initialize(model, name = nil, rel_length = nil)
|
|
9
|
+
super()
|
|
10
|
+
self.model = name ? target_class(model, name) : model
|
|
11
|
+
self.name = name
|
|
12
|
+
self.association = name ? model.associations[name] : nil
|
|
13
|
+
self.rel_length = rel_length
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def clone
|
|
17
|
+
super.tap { |copy| copy.each { |key, value| copy[key] = value.clone } }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_spec(spec)
|
|
21
|
+
fail_spec(spec) unless model
|
|
22
|
+
|
|
23
|
+
case spec
|
|
24
|
+
when nil
|
|
25
|
+
nil
|
|
26
|
+
when Array
|
|
27
|
+
spec.each(&method(:add_spec))
|
|
28
|
+
when Hash
|
|
29
|
+
process_hash(spec)
|
|
30
|
+
when String
|
|
31
|
+
process_string(spec)
|
|
32
|
+
else
|
|
33
|
+
add_key(spec)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def fail_spec(spec)
|
|
38
|
+
fail "Cannot eager load \"past\" a polymorphic association. \
|
|
39
|
+
(Since the association can return multiple models, we don't how to handle the \"#{spec}\" association.)"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def paths(*prefix)
|
|
43
|
+
values.flat_map { |v| [[*prefix, v]] + v.paths(*prefix, v) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def process_hash(spec)
|
|
47
|
+
spec.each { |key, value| add_nested(key, value) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def add_key(key, length = nil)
|
|
51
|
+
self[key] ||= self.class.new(model, key, length)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def add_nested(key, value, length = nil)
|
|
55
|
+
add_key(key, length).add_spec(value)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def process_string(str)
|
|
59
|
+
head, rest = str.split('.', 2)
|
|
60
|
+
k, length = head.split('*', -2)
|
|
61
|
+
add_nested(k.to_sym, rest, length)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def target_class(model, key)
|
|
67
|
+
association = model.associations[key]
|
|
68
|
+
fail "Invalid association: #{[*path, key].join('.')}" unless association
|
|
69
|
+
model.associations[key].target_class
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
module Neo4j
|
|
2
|
+
module ActiveNode
|
|
3
|
+
module Query
|
|
4
|
+
# Methods related to returning nodes and rels from QueryProxy
|
|
5
|
+
module QueryProxyEnumerable
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
# Just like every other <tt>each</tt> but it allows for optional params to support the versions that also return relationships.
|
|
9
|
+
# The <tt>node</tt> and <tt>rel</tt> params are typically used by those other methods but there's nothing stopping you from
|
|
10
|
+
# using `your_node.each(true, true)` instead of `your_node.each_with_rel`.
|
|
11
|
+
# @return [Enumerable] An enumerable containing some combination of nodes and rels.
|
|
12
|
+
def each(node = true, rel = nil, &block)
|
|
13
|
+
result(node, rel).each(&block)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def result(node = true, rel = nil)
|
|
17
|
+
return [].freeze if unpersisted_start_object?
|
|
18
|
+
|
|
19
|
+
@result_cache ||= {}
|
|
20
|
+
return result_cache_for(node, rel) if result_cache?(node, rel)
|
|
21
|
+
|
|
22
|
+
result = pluck_vars(node, rel)
|
|
23
|
+
set_instance_caches(result, node, rel)
|
|
24
|
+
|
|
25
|
+
@result_cache[[node, rel]] ||= result
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def result_cache?(node = true, rel = nil)
|
|
29
|
+
!!result_cache_for(node, rel)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def result_cache_for(node = true, rel = nil)
|
|
33
|
+
(@result_cache || {})[[node, rel]]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def fetch_result_cache
|
|
37
|
+
@result_cache ||= yield
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# When called at the end of a QueryProxy chain, it will return the resultant relationship objects intead of nodes.
|
|
41
|
+
# For example, to return the relationship between a given student and their lessons:
|
|
42
|
+
#
|
|
43
|
+
# .. code-block:: ruby
|
|
44
|
+
#
|
|
45
|
+
# student.lessons.each_rel do |rel|
|
|
46
|
+
#
|
|
47
|
+
# @return [Enumerable] An enumerable containing any number of applicable relationship objects.
|
|
48
|
+
def each_rel(&block)
|
|
49
|
+
block_given? ? each(false, true, &block) : to_enum(:each, false, true)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# When called at the end of a QueryProxy chain, it will return the nodes and relationships of the last link.
|
|
53
|
+
# For example, to return a lesson and each relationship to a given student:
|
|
54
|
+
#
|
|
55
|
+
# .. code-block:: ruby
|
|
56
|
+
#
|
|
57
|
+
# student.lessons.each_with_rel do |lesson, rel|
|
|
58
|
+
def each_with_rel(&block)
|
|
59
|
+
block_given? ? each(true, true, &block) : to_enum(:each, true, true)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Does exactly what you would hope. Without it, comparing `bobby.lessons == sandy.lessons` would evaluate to false because it
|
|
63
|
+
# would be comparing the QueryProxy objects, not the lessons themselves.
|
|
64
|
+
def ==(other)
|
|
65
|
+
self.to_a == other
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# For getting variables which have been defined as part of the association chain
|
|
69
|
+
def pluck(*args)
|
|
70
|
+
transformable_attributes = (model ? model.attribute_names : []) + %w(uuid neo_id)
|
|
71
|
+
arg_list = args.map do |arg|
|
|
72
|
+
arg = Neo4j::ActiveNode::Query::QueryProxy::Link.converted_key(model, arg)
|
|
73
|
+
if transformable_attributes.include?(arg.to_s)
|
|
74
|
+
{identity => arg}
|
|
75
|
+
else
|
|
76
|
+
arg
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
self.query.pluck(*arg_list)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
protected
|
|
84
|
+
|
|
85
|
+
def ensure_distinct(node, force = false)
|
|
86
|
+
@distinct || force ? "DISTINCT(#{node})" : node
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def pluck_vars(node, rel)
|
|
92
|
+
vars = []
|
|
93
|
+
vars << ensure_distinct(identity) if node
|
|
94
|
+
vars << @rel_var if rel
|
|
95
|
+
pluck(*vars)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def set_instance_caches(instance, node, rel)
|
|
99
|
+
instance.each do |object|
|
|
100
|
+
object.instance_variable_set('@source_query_proxy', self)
|
|
101
|
+
object.instance_variable_set('@source_proxy_result_cache', instance)
|
|
102
|
+
if node && rel && object.last.is_a?(Neo4j::ActiveRel)
|
|
103
|
+
object.last.instance_variable_set(association.direction == :in ? '@from_node' : '@to_node', object.first)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Neo4j
|
|
2
|
+
module ActiveNode
|
|
3
|
+
module Query
|
|
4
|
+
module QueryProxyFindInBatches
|
|
5
|
+
def find_in_batches(options = {})
|
|
6
|
+
query.return(identity).find_in_batches(identity, @model.primary_key, options) do |batch|
|
|
7
|
+
yield batch.map(&identity)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def find_each(options = {})
|
|
12
|
+
query.return(identity).find_each(identity, @model.primary_key, options) do |result|
|
|
13
|
+
yield result.send(identity)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
module Neo4j
|
|
2
|
+
module ActiveNode
|
|
3
|
+
module Query
|
|
4
|
+
class QueryProxy
|
|
5
|
+
class Link
|
|
6
|
+
attr_reader :clause
|
|
7
|
+
|
|
8
|
+
def initialize(clause, arg, args = [])
|
|
9
|
+
@clause = clause
|
|
10
|
+
@arg = arg
|
|
11
|
+
@args = args
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def args(var, rel_var)
|
|
15
|
+
if @arg.respond_to?(:call)
|
|
16
|
+
@arg.call(var, rel_var)
|
|
17
|
+
else
|
|
18
|
+
[@arg] + @args
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
def for_clause(clause, arg, model, *args)
|
|
24
|
+
method_to_call = "for_#{clause}_clause"
|
|
25
|
+
|
|
26
|
+
send(method_to_call, arg, model, *args)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def for_where_clause(arg, model, *args)
|
|
30
|
+
node_num = 1
|
|
31
|
+
result = []
|
|
32
|
+
if arg.is_a?(Hash)
|
|
33
|
+
arg.each do |key, value|
|
|
34
|
+
if model && model.association?(key)
|
|
35
|
+
result += for_association(key, value, "n#{node_num}", model)
|
|
36
|
+
node_num += 1
|
|
37
|
+
else
|
|
38
|
+
result << new_for_key_and_value(model, key, value)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
elsif arg.is_a?(String)
|
|
42
|
+
result << new(:where, arg, args)
|
|
43
|
+
end
|
|
44
|
+
result
|
|
45
|
+
end
|
|
46
|
+
alias for_node_where_clause for_where_clause
|
|
47
|
+
|
|
48
|
+
def for_where_not_clause(*args)
|
|
49
|
+
for_where_clause(*args).each do |link|
|
|
50
|
+
link.instance_variable_set('@clause', :where_not)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def new_for_key_and_value(model, key, value)
|
|
55
|
+
key = converted_key(model, key)
|
|
56
|
+
|
|
57
|
+
val = if !model
|
|
58
|
+
value
|
|
59
|
+
elsif key == model.id_property_name && value.is_a?(Neo4j::ActiveNode)
|
|
60
|
+
value.id
|
|
61
|
+
else
|
|
62
|
+
converted_value(model, key, value)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
new(:where, ->(v, _) { {v => {key => val}} })
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def for_association(name, value, n_string, model)
|
|
69
|
+
neo_id = value.try(:neo_id) || value
|
|
70
|
+
fail ArgumentError, "Invalid value for '#{name}' condition" if not neo_id.is_a?(Integer)
|
|
71
|
+
|
|
72
|
+
[
|
|
73
|
+
new(:match, ->(v, _) { "(#{v})#{model.associations[name].arrow_cypher}(#{n_string})" }),
|
|
74
|
+
new(:where, ->(_, _) { {"ID(#{n_string})" => neo_id.to_i} })
|
|
75
|
+
]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# We don't accept strings here. If you want to use a string, just use where.
|
|
79
|
+
def for_rel_where_clause(arg, _, association)
|
|
80
|
+
arg.each_with_object([]) do |(key, value), result|
|
|
81
|
+
rel_class = association.relationship_class if association.relationship_class
|
|
82
|
+
val = rel_class ? converted_value(rel_class, key, value) : value
|
|
83
|
+
result << new(:where, ->(_, rel_var) { {rel_var => {key => val}} })
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def for_rel_where_not_clause(*args)
|
|
88
|
+
for_rel_where_clause(*args).each do |link|
|
|
89
|
+
link.instance_variable_set('@clause', :where_not)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def for_rel_order_clause(arg, _)
|
|
94
|
+
[new(:order, ->(_, v) { arg.is_a?(String) ? arg : {v => arg} })]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def for_order_clause(arg, model)
|
|
98
|
+
[new(:order, ->(v, _) { arg.is_a?(String) ? arg : {v => converted_keys(model, arg)} })]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def for_args(model, clause, args, association = nil)
|
|
102
|
+
if [:where, :where_not].include?(clause) && args[0].is_a?(String) # Better way?
|
|
103
|
+
[for_arg(model, clause, args[0], *args[1..-1])]
|
|
104
|
+
elsif [:rel_where, :rel_where_not].include?(clause)
|
|
105
|
+
args.map { |arg| for_arg(model, clause, arg, association) }
|
|
106
|
+
else
|
|
107
|
+
args.map { |arg| for_arg(model, clause, arg) }
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def for_arg(model, clause, arg, *args)
|
|
112
|
+
default = [Link.new(clause, arg, *args)]
|
|
113
|
+
|
|
114
|
+
Link.for_clause(clause, arg, model, *args) || default
|
|
115
|
+
rescue NoMethodError
|
|
116
|
+
default
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def converted_keys(model, arg)
|
|
120
|
+
arg.is_a?(Hash) ? Hash[arg.map { |key, value| [converted_key(model, key), value] }] : arg
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def converted_key(model, key)
|
|
124
|
+
if key.to_sym == :id
|
|
125
|
+
model ? model.id_property_name : :uuid
|
|
126
|
+
else
|
|
127
|
+
key
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def converted_value(model, key, value)
|
|
132
|
+
model.declared_properties.value_for_where(key, value)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|