activegraph 10.0.0.pre.alpha.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|