activegraph-extensions 0.0.1-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1 -0
- data/Gemfile +24 -0
- data/README.md +1 -0
- data/activegraph-extensions.gemspec +45 -0
- data/lib/active_graph_extensions/node/query/query_proxy.rb +16 -0
- data/lib/active_graph_extensions/node/query/query_proxy_eager_loading/association_eager_load.rb +32 -0
- data/lib/active_graph_extensions/node/query/query_proxy_eager_loading/association_limiting.rb +79 -0
- data/lib/active_graph_extensions/node/query/query_proxy_eager_loading/eager_loading_order.rb +42 -0
- data/lib/active_graph_extensions/node/query/query_proxy_eager_loading/enhanced_tree.rb +67 -0
- data/lib/active_graph_extensions/node/query/query_proxy_eager_loading/scope_eager_loading.rb +50 -0
- data/lib/active_graph_extensions/node/query/query_proxy_eager_loading.rb +148 -0
- data/lib/active_graph_extensions/node/query.rb +8 -0
- data/lib/active_graph_extensions/node.rb +6 -0
- data/lib/active_graph_extensions/string_parsers/relation_parser.rb +24 -0
- data/lib/active_graph_extensions/string_parsers.rb +6 -0
- data/lib/active_graph_extensions/version.rb +3 -0
- data/lib/activegraph-extensions.rb +15 -0
- metadata +264 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9c33fee6eede1db2664a17ae82a9f7dcb0d3611b2665605a024fd184a2695fa9
|
4
|
+
data.tar.gz: ea57d1a76c541d3b5c8c655f1b4cea22142917c83500a2275f4f2fc177f120af
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dc88d9b5d715d803ede073cfd97f60bdbb4b1efe93231416a95ba3963d5831af872bf7e55e53906061693009f9e052e725dea8e80bd8a1a5e44864221659b4ad
|
7
|
+
data.tar.gz: d0b79494bac58f25b9c972951c7bce4d27beb33c5bdcbf865905a81401375b344ab9b9cb101360e93aa1db9bfcc14015ecc1b7a853623e90f9a6a7ca59ff67a0
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Change Log
|
data/Gemfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
gem 'listen', '< 3.1'
|
6
|
+
active_model_version = ENV['ACTIVE_MODEL_VERSION']
|
7
|
+
gem 'activemodel', "~> #{active_model_version}" if active_model_version&.length&.positive?
|
8
|
+
|
9
|
+
#gem 'zeitwerk'
|
10
|
+
|
11
|
+
group :test do
|
12
|
+
gem 'coveralls', require: false
|
13
|
+
gem 'overcommit'
|
14
|
+
gem 'codecov', require: false
|
15
|
+
gem 'simplecov', require: false
|
16
|
+
gem 'simplecov-html', require: false
|
17
|
+
gem 'its'
|
18
|
+
gem 'test-unit'
|
19
|
+
gem 'colored'
|
20
|
+
gem 'dotenv'
|
21
|
+
gem 'timecop'
|
22
|
+
gem 'pry'
|
23
|
+
gem 'activegraph'
|
24
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Extensions to activegraph gem.
|
@@ -0,0 +1,45 @@
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
|
+
$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
require 'active_graph_extensions/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'activegraph-extensions'
|
8
|
+
s.version = ActiveGraphExtensions::VERSION
|
9
|
+
|
10
|
+
s.required_ruby_version = '>= 2.5'
|
11
|
+
|
12
|
+
s.authors = 'Amit Suryavanshi'
|
13
|
+
s.email = 'amitbsuryavabshi@mail.com'
|
14
|
+
s.homepage = 'https://github.com/neo4jrb/activegraph-extensions/'
|
15
|
+
s.summary = 'Additional features to activegraph'
|
16
|
+
s.license = 'MIT'
|
17
|
+
s.description = <<-DESCRIPTION
|
18
|
+
Additional features to activegraph, like sideload limiting, authorizing sideloads etc.
|
19
|
+
DESCRIPTION
|
20
|
+
|
21
|
+
s.require_path = 'lib'
|
22
|
+
s.files = Dir.glob('{bin,lib,config}/**/*') + %w(README.md CHANGELOG.md Gemfile activegraph-extensions.gemspec)
|
23
|
+
s.executables = []
|
24
|
+
s.extra_rdoc_files = %w( README.md )
|
25
|
+
s.rdoc_options = ['--quiet', '--title', 'Neo4j.rb', '--line-numbers', '--main', 'README.rdoc', '--inline-source']
|
26
|
+
|
27
|
+
s.platform = 'java'
|
28
|
+
|
29
|
+
s.add_dependency('parslet')
|
30
|
+
s.add_dependency('activegraph')
|
31
|
+
|
32
|
+
s.add_development_dependency('guard')
|
33
|
+
s.add_development_dependency('guard-rspec')
|
34
|
+
s.add_development_dependency('guard-rubocop')
|
35
|
+
s.add_development_dependency('neo4j-rake_tasks', '>= 0.3.0')
|
36
|
+
# s.add_development_dependency("neo4j-#{ENV['driver'] == 'java' ? 'java' : 'ruby'}-driver", '~> 4.1.0.beta.1')
|
37
|
+
s.add_development_dependency('os')
|
38
|
+
s.add_development_dependency('pry')
|
39
|
+
s.add_development_dependency('railties', '>= 4.0')
|
40
|
+
s.add_development_dependency('rake')
|
41
|
+
s.add_development_dependency('rubocop', '>= 0.56.0')
|
42
|
+
s.add_development_dependency('yard')
|
43
|
+
s.add_development_dependency('dryspec')
|
44
|
+
s.add_development_dependency('rspec', '< 3.10') # Cannot proxy frozen objects
|
45
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphExtensions
|
4
|
+
module Node
|
5
|
+
module Query
|
6
|
+
# We need earlier proxy to generate cypher in query proxy eagerloading
|
7
|
+
module QueryProxy
|
8
|
+
def branch(&block)
|
9
|
+
proxy = super
|
10
|
+
proxy.instance_variable_set(:@break_proxy, as(identity).instance_eval(&block))
|
11
|
+
proxy
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/active_graph_extensions/node/query/query_proxy_eager_loading/association_eager_load.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphExtensions
|
4
|
+
module Node
|
5
|
+
module Query
|
6
|
+
module QueryProxyEagerLoading
|
7
|
+
# Used for eager loading associations with scope
|
8
|
+
module AssociationEagerLoad
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
class_methods do
|
12
|
+
def associations_to_eagerload
|
13
|
+
@associations_to_eagerload
|
14
|
+
end
|
15
|
+
|
16
|
+
def association_nodes(key, ids, filter)
|
17
|
+
send(@associations_to_eagerload[key], ids, filter)
|
18
|
+
end
|
19
|
+
|
20
|
+
def eagerload_associations(config)
|
21
|
+
@associations_to_eagerload = config
|
22
|
+
end
|
23
|
+
|
24
|
+
def eagerload_association?(key)
|
25
|
+
@associations_to_eagerload.keys.include?(key)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphExtensions
|
4
|
+
module Node
|
5
|
+
module Query
|
6
|
+
module QueryProxyEagerLoading
|
7
|
+
module AssociationLimiting
|
8
|
+
def self.included(base)
|
9
|
+
base.attr_reader(:default_assoc_limit)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def rel_collection_str(path)
|
15
|
+
limit = association_limit(path)
|
16
|
+
collection_name = "[#{relationship_name(path)}, #{escape(path_name(path))}] "
|
17
|
+
collection = limit.present? ? "apoc.agg.slice(#{collection_name}, 0, #{limit})" : "collect(#{collection_name})"
|
18
|
+
"#{collection} AS #{escape("#{path_name(path)}_collection")}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def relationship_name(path)
|
22
|
+
if path.last.rel_length
|
23
|
+
"last(relationships(#{escape("#{path_name(path)}_path")}))"
|
24
|
+
else
|
25
|
+
escape("#{path_name(path)}_rel")
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def convert_to_list(collection_name, limit)
|
31
|
+
limit.present? ? "apoc.agg.slice(#{collection_name}, 0, #{limit})" : "collect(#{collection_name})"
|
32
|
+
end
|
33
|
+
|
34
|
+
def association_limit(path)
|
35
|
+
return if multipath?(path)
|
36
|
+
|
37
|
+
limit = path.last&.association_limit
|
38
|
+
limit.blank? || limit.to_i > default_assoc_limit ? default_assoc_limit : limit
|
39
|
+
end
|
40
|
+
|
41
|
+
def with_association_query_part(base_query, path, previous_with_vars)
|
42
|
+
with_args = [identity, rel_collection_str(path), *previous_with_vars]
|
43
|
+
|
44
|
+
optional_match_with_where(base_query, path, previous_with_vars).with(with_args)
|
45
|
+
end
|
46
|
+
|
47
|
+
def limit_node_in_where_clause(query, path)
|
48
|
+
root_path = path[0..0]
|
49
|
+
query.where("`#{path_name(root_path)}` in [i IN #{node_from_collection(root_path)} | i[1]]")
|
50
|
+
end
|
51
|
+
|
52
|
+
def node_from_collection(path_step)
|
53
|
+
"`#{path_name(path_step)}_collection`"
|
54
|
+
end
|
55
|
+
|
56
|
+
def path_alias(node_alias)
|
57
|
+
var_fix(node_alias, :path)
|
58
|
+
end
|
59
|
+
|
60
|
+
def rel_alias(node_alias)
|
61
|
+
var_fix(node_alias, :rel)
|
62
|
+
end
|
63
|
+
|
64
|
+
def multipath?(path)
|
65
|
+
path.size > 1
|
66
|
+
end
|
67
|
+
|
68
|
+
def association_limit_present?(path)
|
69
|
+
association_limit(path).present?
|
70
|
+
end
|
71
|
+
|
72
|
+
def multipath_with_sideload_limit?(path)
|
73
|
+
multipath?(path) && association_limit_present?(path[0..0])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphExtensions
|
4
|
+
module Node
|
5
|
+
module Query
|
6
|
+
module QueryProxyEagerLoading
|
7
|
+
# Used to append auth scopes to query proxy eagerloading
|
8
|
+
module EagerLoadingOrder
|
9
|
+
def optional_order(query, path, previous_with_vars)
|
10
|
+
node_alias = path_name(path)
|
11
|
+
order_clause = order_clause_for_query(node_alias)
|
12
|
+
if path.last.rel_length
|
13
|
+
order_clause.reject! { |el| el.include?('_rel') }
|
14
|
+
query.order("length(`#{node_alias}_path`)", *order_clause)
|
15
|
+
.with(*with_variables(path, node_alias, previous_with_vars))
|
16
|
+
else
|
17
|
+
query.order(*order_clause).with(*with_variables(path, node_alias, previous_with_vars))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def order_clause_for_query(node_alias)
|
22
|
+
(order = @order_spec&.fetch(node_alias, nil)) ? order.map(&method(:nested_order_clause).curry.call(node_alias)) : []
|
23
|
+
end
|
24
|
+
|
25
|
+
def nested_order_clause(node_alias, order_spec)
|
26
|
+
[node_or_rel_alias(node_alias, order_spec), name(order_spec)].join('.')
|
27
|
+
end
|
28
|
+
|
29
|
+
def order_clause(key, order_spec)
|
30
|
+
property_with_direction = name(order_spec)
|
31
|
+
node_alias = node_aliase_for_collection(key, order_spec) || node_aliase_for_order(property_with_direction)
|
32
|
+
[node_alias, property_with_direction].compact.join('.')
|
33
|
+
end
|
34
|
+
|
35
|
+
def skip_order?
|
36
|
+
@order_spec.blank? || @order_spec.keys.all?(&:blank?)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphExtensions
|
4
|
+
module Node
|
5
|
+
module Query
|
6
|
+
module QueryProxyEagerLoading
|
7
|
+
# Tree allowing storage of additional information about the associations
|
8
|
+
class EnhancedTree < ::ActiveGraph::Node::Query::QueryProxyEagerLoading::AssociationTree
|
9
|
+
attr_reader :options, :association_limit
|
10
|
+
|
11
|
+
def initialize(model, name = nil, rel_length = nil, association_limit = nil)
|
12
|
+
@association_limit = association_limit
|
13
|
+
super(model, name, rel_length)
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_spec_and_validate(spec)
|
17
|
+
add_spec(spec)
|
18
|
+
validate_for_zero_length_paths
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate_for_zero_length_paths
|
22
|
+
fail 'Can not eager load more than one zero length path.' if values.count(&:zero_length_path?) > 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def zero_length_path?
|
26
|
+
rel_length&.fetch(:min, nil)&.to_s == '0' || values.any?(&:zero_length_path?)
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_key(key, length = nil, assoc_limit = nil)
|
30
|
+
self[key] ||= self.class.new(model, key, length, assoc_limit)
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_nested(key, value, length = nil, assoc_limit = nil)
|
34
|
+
add_key(key, length, assoc_limit).add_spec(value)
|
35
|
+
end
|
36
|
+
|
37
|
+
def process_string(str)
|
38
|
+
# head, rest = str.split('.', 2)
|
39
|
+
# head, association_limit = extract_assoc_limit(head)
|
40
|
+
# k, length = head.split('*', -2)
|
41
|
+
# length = { max: length } if length
|
42
|
+
#add_nested(k.to_sym, rest, length, association_limit)
|
43
|
+
map = StringParsers::RelationParser.new.parse(str)
|
44
|
+
add_nested(map[:rel_name].to_sym, map[:rest_str].to_s.presence, map[:length_part], map[:limit_digit])
|
45
|
+
end
|
46
|
+
|
47
|
+
# def extract_assoc_limit(str)
|
48
|
+
# transformer = StringParsers::RelationParamTransformer.new(str)
|
49
|
+
# [transformer.rel_name_n_length, transformer.rel_limit_number]
|
50
|
+
# end
|
51
|
+
|
52
|
+
def process_hash(spec)
|
53
|
+
spec = spec.dup
|
54
|
+
@options = spec.delete(:_options)
|
55
|
+
super(spec)
|
56
|
+
end
|
57
|
+
|
58
|
+
def target_class(model, key)
|
59
|
+
association = model.associations[key.to_sym]
|
60
|
+
fail "Invalid association: #{[*path, key].join('.')}" unless association
|
61
|
+
model.associations[key].target_class
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphExtensions
|
4
|
+
module Node
|
5
|
+
module Query
|
6
|
+
module QueryProxyEagerLoading
|
7
|
+
# Used to append auth scopes to query proxy eagerloading
|
8
|
+
module ScopeEagerLoading
|
9
|
+
def authorized_rel(path, var)
|
10
|
+
rel_model = relationship_model(path)
|
11
|
+
return {} if @opts.blank? || !(auth_scope = authorized_scope(rel_model, path))
|
12
|
+
conf = { rels: [], chain: {} }
|
13
|
+
proxy = auth_scope.call(var, "#{var}_rel", user: @opts[:user],
|
14
|
+
properties: properties_for(rel_model),
|
15
|
+
privileges: @opts[:privileges],
|
16
|
+
rel_length: path.last.rel_length)
|
17
|
+
proxy_rel_parts(proxy.instance_variable_get(:@break_proxy) || proxy, conf)
|
18
|
+
conf
|
19
|
+
end
|
20
|
+
|
21
|
+
def properties_for(rel_model)
|
22
|
+
return [] unless @opts[:properties]
|
23
|
+
@opts[:properties].select { |prop| prop.model.name == rel_model.name }.map(&:name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def relationship_model(path)
|
27
|
+
path[0..-2].inject(model) { |mod, rel| mod.send(rel.name).model }
|
28
|
+
end
|
29
|
+
|
30
|
+
def authorized_scope(rel_model, path)
|
31
|
+
rel_model.scopes["authorized_#{path.last.association.name}".to_sym]
|
32
|
+
end
|
33
|
+
|
34
|
+
def proxy_rel_parts(auth_proxy, conf)
|
35
|
+
return unless auth_proxy&.association
|
36
|
+
rel_length = auth_proxy.instance_variable_get(:@rel_length)
|
37
|
+
conf[:rels] << relationship_part(auth_proxy.association, auth_proxy.identity, rel_length)
|
38
|
+
assign_config_chain(conf, auth_proxy, rel_length)
|
39
|
+
proxy_rel_parts(auth_proxy.query_proxy, conf)
|
40
|
+
end
|
41
|
+
|
42
|
+
def assign_config_chain(conf, auth_proxy, rel_length)
|
43
|
+
return unless (auth_chain = auth_proxy.instance_variable_get(:@chain))
|
44
|
+
conf[:chain][[auth_proxy.identity, rel_length ? "#{auth_proxy.identity}_rel" : nil]] = auth_chain
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module ActiveGraphExtensions
|
2
|
+
module Node
|
3
|
+
module Query
|
4
|
+
module QueryProxyEagerLoading
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include AssociationLimiting
|
7
|
+
include ScopeEagerLoading
|
8
|
+
include EagerLoadingOrder
|
9
|
+
|
10
|
+
def association_tree_class
|
11
|
+
EnhancedTree
|
12
|
+
end
|
13
|
+
|
14
|
+
def with_ordered_associations(spec, order, opts = {})
|
15
|
+
@default_assoc_limit = opts[:default_assoc_limit]
|
16
|
+
@with_vars = opts[:with_vars]
|
17
|
+
@order_spec = order.with_indifferent_access unless spec.empty?
|
18
|
+
@opts = opts
|
19
|
+
with_associations(spec)
|
20
|
+
end
|
21
|
+
|
22
|
+
def first
|
23
|
+
limit(1).to_a.first
|
24
|
+
end
|
25
|
+
|
26
|
+
class_methods do
|
27
|
+
def rel?(order_spec)
|
28
|
+
order_spec.is_a?(Hash) ? 0 : 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def with_associations(*spec)
|
33
|
+
new_link.tap do |new_query_proxy|
|
34
|
+
new_query_proxy.with_associations_tree = with_associations_tree.clone
|
35
|
+
new_query_proxy.with_associations_tree.add_spec_and_validate(spec)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def query_from_association_tree
|
42
|
+
previous_with_vars = defalut_previous_with_vars
|
43
|
+
with_associations_tree.paths.inject(query_as(identity).with(base_query_with_vars)) do |query, path|
|
44
|
+
with_association_query_part(query, path, previous_with_vars).tap do
|
45
|
+
previous_with_vars << var_fix(path_name(path), :collection)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def defalut_previous_with_vars
|
51
|
+
@with_vars&.dup || []
|
52
|
+
end
|
53
|
+
|
54
|
+
def base_query_with_vars
|
55
|
+
[ensure_distinct(identity)] + (@with_vars || [])
|
56
|
+
end
|
57
|
+
|
58
|
+
def optional_match_with_where(query, path, vars)
|
59
|
+
computed_query = super
|
60
|
+
computed_query = limit_node_in_where_clause(computed_query, path) if multipath_with_sideload_limit?(path)
|
61
|
+
skip_order? && !path.last.rel_length ? computed_query : optional_order(computed_query, path, vars)
|
62
|
+
end
|
63
|
+
|
64
|
+
def optional_match(base_query, path)
|
65
|
+
start_path = "#{escape("#{path_name(path)}_path")}=(#{identity})"
|
66
|
+
conf = authorized_rel(path, path_name(path[0..-1]))
|
67
|
+
query = construct_optional_match(start_path, base_query, conf[:rels] ? path[0..-2] : path, conf[:rels])
|
68
|
+
conf[:rels] ? apply_chain(conf[:chain], query) : query
|
69
|
+
end
|
70
|
+
|
71
|
+
def construct_optional_match(start_path, base_query, path, scope_rels)
|
72
|
+
base_query.optional_match(
|
73
|
+
"#{start_path}#{path.each_with_index.map do |element, index|
|
74
|
+
relationship_part(element.association, path_name(path[0..index]), element.rel_length)
|
75
|
+
end.join}#{(scope_rels || []).reverse.join}"
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
def apply_chain(chain, query)
|
80
|
+
chain.each do |key, links|
|
81
|
+
query = links.inject(query) do |q, link|
|
82
|
+
args = link.args(*key)
|
83
|
+
args.is_a?(Array) ? q.send(link.clause, *args) : q.send(link.clause, args)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
query
|
87
|
+
end
|
88
|
+
|
89
|
+
def with_variables(path, node_alias, previous_with_vars)
|
90
|
+
[identity, path.last.rel_length ? path_alias(node_alias) : rel_alias(node_alias), var_fix(node_alias)] +
|
91
|
+
previous_with_vars
|
92
|
+
end
|
93
|
+
|
94
|
+
def before_pluck(query)
|
95
|
+
return query if skip_order? && !include_with_path_length?
|
96
|
+
base_query = query.order(
|
97
|
+
(@order_spec || []).flat_map { |key, order_specs| order_specs.map(&method(:order_clause).curry.call(key)) }
|
98
|
+
)
|
99
|
+
query_from_chain(@postponed_chain, base_query, identity)
|
100
|
+
end
|
101
|
+
|
102
|
+
def node_aliase_for_collection(key, order_spec)
|
103
|
+
"#{var(key, :collection, &:itself)}[0][#{self.class.rel?(order_spec)}]" if key.present?
|
104
|
+
end
|
105
|
+
|
106
|
+
def node_aliase_for_order(property_with_direction)
|
107
|
+
identity unless @with_vars&.include?(property_with_direction.split(' ').first.to_sym)
|
108
|
+
end
|
109
|
+
|
110
|
+
def name(order_spec)
|
111
|
+
Array(order_spec).flatten.last.to_s
|
112
|
+
end
|
113
|
+
|
114
|
+
def node_or_rel_alias(node_alias, order_spec)
|
115
|
+
var(node_alias, order_spec.is_a?(Hash) ? :rel : nil, &:itself)
|
116
|
+
end
|
117
|
+
|
118
|
+
CLAUSES_TO_POSTPONE = %i[limit order skip].freeze
|
119
|
+
|
120
|
+
def include_with_path_length?(path = @with_associations_tree)
|
121
|
+
path.present? && (path.rel_length.present? || path.any? { |_, val| include_with_path_length?(val) })
|
122
|
+
end
|
123
|
+
|
124
|
+
def chain
|
125
|
+
return super if skip_order? && !include_with_path_length?
|
126
|
+
clauses = !skip_order? ? CLAUSES_TO_POSTPONE : %i[order]
|
127
|
+
@postponed_chain, other_chain = super.partition { |link| clauses.include?(link.clause) }
|
128
|
+
other_chain
|
129
|
+
end
|
130
|
+
|
131
|
+
def perform_query
|
132
|
+
@_cache = ActiveGraph::Node::Query::QueryProxyEagerLoading::IdentityMap.new
|
133
|
+
build_query
|
134
|
+
.map do |record, eager_data|
|
135
|
+
record = cache_and_init(record, with_associations_tree)
|
136
|
+
eager_data.zip(with_associations_tree.paths.map(&:last)).each do |eager_records, element|
|
137
|
+
eager_records.each do |eager_record|
|
138
|
+
next unless eager_record.first&.type&.to_s == element.association.relationship_type.to_s
|
139
|
+
add_to_cache(*eager_record, element)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
record
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGraphExtensions
|
4
|
+
module StringParsers
|
5
|
+
# Parsing relationships with length
|
6
|
+
class RelationParser < ::Parslet::Parser
|
7
|
+
rule(:asterix) { str('*') }
|
8
|
+
rule(:digit) { match('[\d]').repeat }
|
9
|
+
rule(:range) { str('..') }
|
10
|
+
rule(:dot) { str('.') }
|
11
|
+
rule(:zero) { str('0') }
|
12
|
+
rule(:length_1) { zero.as(:min) >> range >> digit.maybe.as(:max) }
|
13
|
+
rule(:length_2) { digit.maybe.as(:max) }
|
14
|
+
rule(:length) { asterix >> (length_1 | length_2) }
|
15
|
+
rule(:rel) { match('[a-z,_]').repeat.as(:rel_name) }
|
16
|
+
rule(:limit) { digit.as(:limit_digit) >> asterix }
|
17
|
+
rule(:key) { limit.maybe >> rel >> length.as(:length_part).maybe }
|
18
|
+
rule(:anything) { match('.').repeat }
|
19
|
+
rule(:root) { key >> dot.maybe >> anything.maybe.as(:rest_str) }
|
20
|
+
|
21
|
+
rule(:rel_sequence) { infix_expression(key, [dot, 1, :left]) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'active_graph'
|
2
|
+
require 'parslet'
|
3
|
+
|
4
|
+
module ActiveGraphExtensions
|
5
|
+
end
|
6
|
+
|
7
|
+
loader = Zeitwerk::Loader.for_gem
|
8
|
+
loader.inflector.inflect 'version' => 'VERSION'
|
9
|
+
loader.ignore(File.expand_path('activegraph-extensions.rb', __dir__))
|
10
|
+
loader.setup
|
11
|
+
|
12
|
+
ActiveGraph::Node::Query::QueryProxy.include ActiveGraphExtensions::Node::Query::QueryProxyEagerLoading
|
13
|
+
ActiveGraph::Node::Query::QueryProxy.prepend ActiveGraphExtensions::Node::Query::QueryProxy
|
14
|
+
|
15
|
+
ActiveGraph::Node.include ActiveGraphExtensions::Node::Query::QueryProxyEagerLoading::AssociationEagerLoad
|
metadata
ADDED
@@ -0,0 +1,264 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activegraph-extensions
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: java
|
6
|
+
authors:
|
7
|
+
- Amit Suryavanshi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-01-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
name: parslet
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
name: activegraph
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
name: guard
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
name: guard-rspec
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
name: guard-rubocop
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 0.3.0
|
89
|
+
name: neo4j-rake_tasks
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.3.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
name: os
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
name: pry
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '4.0'
|
131
|
+
name: railties
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '4.0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
name: rake
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: 0.56.0
|
159
|
+
name: rubocop
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 0.56.0
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
requirement: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
name: yard
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
requirement: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - ">="
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '0'
|
187
|
+
name: dryspec
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
requirement: !ruby/object:Gem::Requirement
|
197
|
+
requirements:
|
198
|
+
- - "<"
|
199
|
+
- !ruby/object:Gem::Version
|
200
|
+
version: '3.10'
|
201
|
+
name: rspec
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - "<"
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '3.10'
|
209
|
+
description: " Additional features to activegraph, like sideload limiting, authorizing\
|
210
|
+
\ sideloads etc.\n"
|
211
|
+
email: amitbsuryavabshi@mail.com
|
212
|
+
executables: []
|
213
|
+
extensions: []
|
214
|
+
extra_rdoc_files:
|
215
|
+
- README.md
|
216
|
+
files:
|
217
|
+
- CHANGELOG.md
|
218
|
+
- Gemfile
|
219
|
+
- README.md
|
220
|
+
- activegraph-extensions.gemspec
|
221
|
+
- lib/active_graph_extensions/node.rb
|
222
|
+
- lib/active_graph_extensions/node/query.rb
|
223
|
+
- lib/active_graph_extensions/node/query/query_proxy.rb
|
224
|
+
- lib/active_graph_extensions/node/query/query_proxy_eager_loading.rb
|
225
|
+
- lib/active_graph_extensions/node/query/query_proxy_eager_loading/association_eager_load.rb
|
226
|
+
- lib/active_graph_extensions/node/query/query_proxy_eager_loading/association_limiting.rb
|
227
|
+
- lib/active_graph_extensions/node/query/query_proxy_eager_loading/eager_loading_order.rb
|
228
|
+
- lib/active_graph_extensions/node/query/query_proxy_eager_loading/enhanced_tree.rb
|
229
|
+
- lib/active_graph_extensions/node/query/query_proxy_eager_loading/scope_eager_loading.rb
|
230
|
+
- lib/active_graph_extensions/string_parsers.rb
|
231
|
+
- lib/active_graph_extensions/string_parsers/relation_parser.rb
|
232
|
+
- lib/active_graph_extensions/version.rb
|
233
|
+
- lib/activegraph-extensions.rb
|
234
|
+
homepage: https://github.com/neo4jrb/activegraph-extensions/
|
235
|
+
licenses:
|
236
|
+
- MIT
|
237
|
+
metadata: {}
|
238
|
+
post_install_message:
|
239
|
+
rdoc_options:
|
240
|
+
- "--quiet"
|
241
|
+
- "--title"
|
242
|
+
- Neo4j.rb
|
243
|
+
- "--line-numbers"
|
244
|
+
- "--main"
|
245
|
+
- README.rdoc
|
246
|
+
- "--inline-source"
|
247
|
+
require_paths:
|
248
|
+
- lib
|
249
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
250
|
+
requirements:
|
251
|
+
- - ">="
|
252
|
+
- !ruby/object:Gem::Version
|
253
|
+
version: '2.5'
|
254
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
255
|
+
requirements:
|
256
|
+
- - ">="
|
257
|
+
- !ruby/object:Gem::Version
|
258
|
+
version: '0'
|
259
|
+
requirements: []
|
260
|
+
rubygems_version: 3.0.6
|
261
|
+
signing_key:
|
262
|
+
specification_version: 4
|
263
|
+
summary: Additional features to activegraph
|
264
|
+
test_files: []
|