forester 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/forester.gemspec +4 -4
- data/lib/forester/aggregators.rb +44 -23
- data/lib/forester/tree_node.rb +31 -0
- data/lib/forester/version.rb +2 -3
- data/test/test_treenode.rb +40 -0
- metadata +20 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca08cece8313d3e3c8b20bae94f78163b81853cb
|
4
|
+
data.tar.gz: d6b07a8b064d28e35b302a9999f5c45b8a0bcdb2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 696d7af03b121cba11c01a3fb75aea488edf4a56a415a266bf3c2971f8111358dac17e40a06a110aba00171fd838306034337bbe1b17f9b337c6c91f6676fcd8
|
7
|
+
data.tar.gz: 3ca6bd73015e51026f8aec700ce061affccea8540db8eedab15b6967ae0421931e5497145a59762c53734ac10502f617f38fb24bab4b9aba756a05f14f7070c1
|
data/forester.gemspec
CHANGED
@@ -6,7 +6,7 @@ require 'forester/version'
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = 'forester'
|
8
8
|
s.version = Forester::Version
|
9
|
-
s.date = '2016-07-
|
9
|
+
s.date = '2016-07-24'
|
10
10
|
s.summary = "A gem to represent and interact with tree data structures"
|
11
11
|
s.description = "Based on rubytree and enzymator, this gem lets you build trees and run queries against them."
|
12
12
|
s.authors = ["Eugenio Bruno"]
|
@@ -19,10 +19,10 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
20
20
|
s.require_paths = ['lib']
|
21
21
|
|
22
|
-
s.required_ruby_version = '>=
|
22
|
+
s.required_ruby_version = '>= 2.0.0'
|
23
23
|
|
24
|
-
s.add_runtime_dependency 'rubytree', ['
|
25
|
-
s.add_runtime_dependency 'enzymator', ['
|
24
|
+
s.add_runtime_dependency 'rubytree', ['0.9.7']
|
25
|
+
s.add_runtime_dependency 'enzymator', ['1.0.0']
|
26
26
|
|
27
27
|
s.add_development_dependency 'rake', ['~> 11.2']
|
28
28
|
s.add_development_dependency 'minitest', ['~> 5.9']
|
data/lib/forester/aggregators.rb
CHANGED
@@ -1,43 +1,64 @@
|
|
1
1
|
module Forester
|
2
2
|
module Aggregators
|
3
3
|
|
4
|
+
def own_and_descendants(options = {})
|
5
|
+
default_options = {
|
6
|
+
field: 'name',
|
7
|
+
if_field_missing: ->(c) { [] },
|
8
|
+
}
|
9
|
+
|
10
|
+
options = default_options.merge(options)
|
11
|
+
|
12
|
+
Enzymator::Aggregations::MapReduce.new(
|
13
|
+
{
|
14
|
+
mapping: ->(node) { Array(node.get(options[:field], &options[:if_field_missing])) },
|
15
|
+
#reduction: ->(acum, values) { acum.concat(values) },
|
16
|
+
#empty: []
|
17
|
+
# automatically assumed
|
18
|
+
}
|
19
|
+
)
|
20
|
+
.run_on(self)
|
21
|
+
end
|
22
|
+
|
4
23
|
def values_by_subtree_of_level(options = {})
|
5
24
|
default_options = {
|
6
25
|
level: 1,
|
7
26
|
group_field: 'name',
|
8
27
|
aggregation_field: 'value',
|
9
|
-
if_field_missing:
|
28
|
+
if_field_missing: ->(c) { [] },
|
10
29
|
include_ancestry_in_keys: false, # if false, with_root is ignored
|
11
30
|
with_root: false,
|
12
31
|
}
|
13
32
|
|
14
33
|
options = default_options.merge(options)
|
15
34
|
|
16
|
-
|
17
|
-
null_result: lambda { Hash.new },
|
18
|
-
initial_clusters: lambda { |tree| tree.nodes_of_level(options[:level]) },
|
19
|
-
map: lambda do |node|
|
20
|
-
key_nodes = if options[:include_ancestry_in_keys]
|
21
|
-
node.ancestry(options[:with_root], true)
|
22
|
-
else
|
23
|
-
[node]
|
24
|
-
end
|
25
|
-
key = key_nodes.map { |kn| kn.get(options[:group_field]) { |n| n.object_id } }
|
26
|
-
key = key.first if key.one?
|
27
|
-
key
|
28
|
-
end,
|
29
|
-
enumerator: lambda { |level| level.each_node },
|
30
|
-
map_each: lambda { |node| node.get(options[:aggregation_field], &options[:if_field_missing]) },
|
31
|
-
reduce_each: lambda { |acum, value| Array(acum).concat(Array(value)) },
|
32
|
-
reduce: lambda { |prev, group, result| prev.merge( { group => result } ) },
|
35
|
+
input = nodes_of_level(options[:level])
|
33
36
|
|
34
|
-
|
37
|
+
Enzymator::Aggregations::MapReduce.new({
|
38
|
+
mapping: ->(node) {
|
35
39
|
|
36
|
-
|
37
|
-
|
40
|
+
key_nodes = if options[:include_ancestry_in_keys]
|
41
|
+
node.ancestry(options[:with_root], true)
|
42
|
+
else
|
43
|
+
[node]
|
44
|
+
end
|
45
|
+
|
46
|
+
key = key_nodes.map { |kn| kn.get(options[:group_field]) { |n| n.object_id } }
|
47
|
+
key = key.first if key.one?
|
48
|
+
|
49
|
+
value = node.own_and_descendants(
|
50
|
+
{
|
51
|
+
field: options[:aggregation_field],
|
52
|
+
if_field_missing: options[:if_field_missing]
|
53
|
+
}
|
54
|
+
)
|
38
55
|
|
39
|
-
|
40
|
-
|
56
|
+
{ key => value }
|
57
|
+
},
|
58
|
+
# reduction: ->(acum, hash) { acum.merge(hash) }
|
59
|
+
# empty: {}
|
60
|
+
# automatically assumed
|
61
|
+
}).run_on(input)
|
41
62
|
end
|
42
63
|
|
43
64
|
end
|
data/lib/forester/tree_node.rb
CHANGED
@@ -28,6 +28,37 @@ module Forester
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
def values_by_field(options)
|
32
|
+
default_options = {
|
33
|
+
field_to_search: 'name',
|
34
|
+
search_keyword: :missing_search_keyword,
|
35
|
+
values_key: :missing_values_key,
|
36
|
+
include_descendants: false,
|
37
|
+
assume_uniqueness: false
|
38
|
+
}
|
39
|
+
options = default_options.merge(options)
|
40
|
+
|
41
|
+
found_nodes = nodes_with(options[:field_to_search], options[:search_keyword])
|
42
|
+
|
43
|
+
# When assuming that node names are unique,
|
44
|
+
# if more than one node was found,
|
45
|
+
# discard all but the first one
|
46
|
+
found_nodes = found_nodes.slice(0, 1) if options[:assume_uniqueness]
|
47
|
+
|
48
|
+
found_nodes.flat_map do |node|
|
49
|
+
if options[:include_descendants]
|
50
|
+
node.own_and_descendants({ field: options[:values_key] })
|
51
|
+
else
|
52
|
+
node.get(options[:values_key])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
def nodes_with(content_key, content_value)
|
59
|
+
each_node.select { |node| Array(node.get(content_key) { :no_match }).include? content_value }
|
60
|
+
end
|
61
|
+
|
31
62
|
def get(field, &block)
|
32
63
|
content.public_send(field, &block)
|
33
64
|
end
|
data/lib/forester/version.rb
CHANGED
data/test/test_treenode.rb
CHANGED
@@ -30,6 +30,46 @@ class TreeNodeTest < Minitest::Test
|
|
30
30
|
aggregation_result = tree.values_by_subtree_of_level(level: 2, aggregation_field: 'strings', include_ancestry_in_keys: true)
|
31
31
|
|
32
32
|
assert_equal expected, aggregation_result
|
33
|
+
|
34
|
+
|
35
|
+
expected_value = [7]
|
36
|
+
actual_value = tree.values_by_field({
|
37
|
+
field_to_search: 'name',
|
38
|
+
search_keyword: 'Second node of level 3',
|
39
|
+
values_key: 'value'
|
40
|
+
})
|
41
|
+
assert_equal expected_value, actual_value
|
42
|
+
|
43
|
+
expected_values = [7, 8, 9]
|
44
|
+
actual_values = tree.values_by_field({
|
45
|
+
field_to_search: 'name',
|
46
|
+
search_keyword: 'Second node of level 3',
|
47
|
+
values_key: 'value',
|
48
|
+
include_descendants: true
|
49
|
+
})
|
50
|
+
assert_equal expected_values, actual_values
|
51
|
+
|
52
|
+
expected_value = [7]
|
53
|
+
actual_value = tree.values_by_field({
|
54
|
+
field_to_search: 'strings',
|
55
|
+
search_keyword: 'A hidden secret lies in the deepest leaves...',
|
56
|
+
values_key: 'value'
|
57
|
+
})
|
58
|
+
assert_equal expected_value, actual_value
|
59
|
+
|
60
|
+
|
61
|
+
expected_names = ["A hidden secret lies in the deepest leaves...", "Just kidding.", "Could forester handle trees with hundreds of levels?", "Maybe."]
|
62
|
+
|
63
|
+
found_nodes = tree.nodes_with('name', 'Second node of level 3')
|
64
|
+
assert_equal 1, found_nodes.length
|
65
|
+
|
66
|
+
actual_names = found_nodes.flat_map do |node|
|
67
|
+
node.own_and_descendants({
|
68
|
+
field: 'strings'
|
69
|
+
})
|
70
|
+
end
|
71
|
+
|
72
|
+
assert_equal expected_names, actual_names
|
33
73
|
end
|
34
74
|
|
35
75
|
end
|
metadata
CHANGED
@@ -1,83 +1,83 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forester
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eugenio Bruno
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-07-
|
11
|
+
date: 2016-07-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubytree
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.9.7
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 0.9.7
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: enzymator
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - '='
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 1.0.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - '='
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 1.0.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - ~>
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '11.2'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - ~>
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '11.2'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: minitest
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - ~>
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '5.9'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - ~>
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '5.9'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: pry-byebug
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - ~>
|
73
|
+
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '3.4'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - ~>
|
80
|
+
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '3.4'
|
83
83
|
description: Based on rubytree and enzymator, this gem lets you build trees and run
|
@@ -112,17 +112,17 @@ require_paths:
|
|
112
112
|
- lib
|
113
113
|
required_ruby_version: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
|
-
- -
|
115
|
+
- - ">="
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version:
|
117
|
+
version: 2.0.0
|
118
118
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
119
|
requirements:
|
120
|
-
- -
|
120
|
+
- - ">="
|
121
121
|
- !ruby/object:Gem::Version
|
122
122
|
version: '0'
|
123
123
|
requirements: []
|
124
124
|
rubyforge_project:
|
125
|
-
rubygems_version: 2.
|
125
|
+
rubygems_version: 2.5.1
|
126
126
|
signing_key:
|
127
127
|
specification_version: 4
|
128
128
|
summary: A gem to represent and interact with tree data structures
|