forester 4.0.0 → 4.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c4bc0ddc9aa53d1bdab802726bbdc14871584d67
4
- data.tar.gz: d43910fab0a17df6bee538e7822a68faba3616b0
3
+ metadata.gz: 21870a6a2f8976322af18cc53776dc41a6a1dbb9
4
+ data.tar.gz: 6947433f22479158ef6e08c8d965a2ce0de7a058
5
5
  SHA512:
6
- metadata.gz: 58c9d3199133b793ffc19f7a40dd85b37a6e93e4595b06c974447f091f274c77433d9be1df9291116cf2ba240b49178ab6813766fac65ddabf40f5176341ebd3
7
- data.tar.gz: 05ca996da70214bfb0e5ba33db69ae8d7782b7955fbff7d72893df50ab99a70e9a13a01f92adfe3a94839bc53ff47cb96691b010b12c5625f224083814bb4f0d
6
+ metadata.gz: 3bc863f4b9fdd51749c3bde9da3baea18265d61cf1f7a240571df07192a0d757fc187fcf1412cc94b1ca2304458e0d23f1de71bc951498496e3f25d177d753f0
7
+ data.tar.gz: dfdf878a13b18c54d14ce852d9cee9ac4d136b342c44f080032c8b71f82ad93c87cebfe90ff97a63ce14287b287d08ae84287471cba16c3a1370abee1495834d
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-08-27'
9
+ s.date = '2016-08-28'
10
10
  s.summary = "A gem to represent and interact with tree data structures"
11
11
  s.description = "Based on rubytree, this gem lets you build trees and run queries against them."
12
12
  s.authors = ["Eugenio Bruno"]
@@ -59,6 +59,7 @@ module Forester
59
59
  symbolize_keys: false
60
60
  }
61
61
  options = default_options.merge(options)
62
+ options[:fields_to_include] = fields if options[:fields_to_include] == :all
62
63
 
63
64
  convert_key = ->(k) { k }
64
65
  convert_key = ->(k) { k.to_s } if options[:stringify_keys]
@@ -5,6 +5,7 @@ module Forester
5
5
  def_delegators :@content, :fields, :has?, :put!, :add!, :del!
6
6
 
7
7
  include Aggregators
8
+ include Validators
8
9
  include Mutators
9
10
  include Views
10
11
 
@@ -38,8 +39,8 @@ module Forester
38
39
  content.get(field)
39
40
  elsif block_given?
40
41
  yield self
41
- elsif default != :raise
42
- default
42
+ elsif options[:default] != :raise
43
+ options[:default]
43
44
  else
44
45
  raise ArgumentError.new("the node \"#{name}\" does not have \"#{field}\"")
45
46
  end
@@ -2,18 +2,26 @@ module Forester
2
2
  module Mutators
3
3
 
4
4
  def add_field!(name, definition, options = {})
5
+ add_fields!([{ name: name, definition: definition }], options)
6
+ end
7
+
8
+ def add_fields!(fields, options = {})
5
9
  default_options = {
6
10
  subtree: true
7
11
  }
8
12
  options = default_options.merge(options)
9
13
 
10
- if options[:subtree]
11
- each_node do |node|
12
- node.add_field!(name, definition, options.merge({ subtree: false }))
13
- end
14
- else
15
- value = definition.respond_to?(:call) ? definition.call(self) : definition
16
- put!(name, value)
14
+ target_nodes = options[:subtree] ? each_node : [self]
15
+
16
+ target_nodes.each { |node| node.add_fields_to_root!(fields) }
17
+ end
18
+
19
+ def add_fields_to_root!(fields)
20
+ fields.each do |field|
21
+ value = field[:definition]
22
+ value = value.call(self) if value.respond_to?(:call)
23
+
24
+ put!(field[:name], value)
17
25
  end
18
26
  end
19
27
 
@@ -0,0 +1,134 @@
1
+ module Forester
2
+ module Validators
3
+
4
+ def validate_uniqueness_of_field(field, options = {})
5
+ validate_uniqueness_of_fields([field], options)
6
+ end
7
+
8
+ def validate_uniqueness_of_fields(fields, options = {})
9
+ default_options = {
10
+ combination: false,
11
+ first_failure_only: false,
12
+ within_subtrees_of_level: 0,
13
+ among_siblings_of_level: :not_siblings,
14
+ field_for_failures: :name,
15
+ as_failure: ->(node) { node.get(options[:field_for_failures]) }
16
+ }
17
+ options = default_options.merge(options)
18
+
19
+ return of_combination_of_fields(fields, options) if options[:combination]
20
+
21
+ failures = Hash.new(Hash.new([]))
22
+
23
+ nodes_of_level(options[:within_subtrees_of_level]).each do |subtree|
24
+ visited_nodes = []
25
+ nodes_to_visit =
26
+ if options[:among_siblings_of_level] == :not_siblings
27
+ subtree.each_node
28
+ else
29
+ nodes_of_level(options[:among_siblings_of_level])
30
+ end
31
+
32
+ nodes_to_visit.each do |node|
33
+ visited_nodes.each do |vn|
34
+ fields.each do |field|
35
+ next unless all_have?(field, [vn, node])
36
+
37
+ if same_values?(field, [vn, node])
38
+ k = vn.get(field) # repeated value
39
+
40
+ prepare_hash(failures, field, k)
41
+
42
+ add_failure_if_new(failures, field, k, options[:as_failure].call(vn))
43
+ add_failure( failures, field, k, options[:as_failure].call(node))
44
+
45
+ return result(failures) if options[:first_failure_only]
46
+ end
47
+ end
48
+ end
49
+ visited_nodes << node
50
+ end
51
+ end
52
+
53
+ result(failures)
54
+ end
55
+
56
+ private
57
+
58
+ def of_combination_of_fields(fields, options)
59
+ failures = Hash.new(Hash.new([]))
60
+
61
+ nodes_of_level(options[:within_subtrees_of_level]).each do |subtree|
62
+ visited_nodes = []
63
+ nodes_to_visit =
64
+ if options[:among_siblings_of_level] == :not_siblings
65
+ subtree.each_node
66
+ else
67
+ nodes_of_level(options[:among_siblings_of_level])
68
+ end
69
+
70
+ nodes_to_visit.each do |node|
71
+ visited_nodes.each do |vn|
72
+ next unless all_have_all?(fields, [vn, node])
73
+
74
+ if same_values_for_all?(fields, [vn, node])
75
+ k = fields.map { |f| vn.get(f) } # repeated combination of values
76
+
77
+ prepare_hash(failures, fields, k)
78
+
79
+ add_failure_if_new(failures, fields, k, options[:as_failure].call(vn))
80
+ add_failure( failures, fields, k, options[:as_failure].call(node))
81
+
82
+ return result(failures) if options[:first_failure_only]
83
+ end
84
+ end
85
+ visited_nodes << node
86
+ end
87
+ end
88
+
89
+ result(failures)
90
+ end
91
+
92
+ private
93
+
94
+ def all_have?(field, nodes)
95
+ all_have_all?([field], nodes)
96
+ end
97
+
98
+ def all_have_all?(fields, nodes)
99
+ nodes.all? { |n| fields.all? { |f| n.has?(f) } }
100
+ end
101
+
102
+ def same_values?(field, nodes)
103
+ same_values_for_all?([field], nodes)
104
+ end
105
+
106
+ def same_values_for_all?(fields, nodes)
107
+ fields
108
+ .map { |f| nodes.map { |n| n.get(f) } }
109
+ .all? { |vs| vs.uniq.length <= 1 }
110
+ end
111
+
112
+ def prepare_hash(hash, key, subkey)
113
+ hash[key] = {} unless hash.has_key?(key)
114
+ hash[key][subkey] = [] unless hash[key].has_key?(subkey)
115
+ end
116
+
117
+ def add_failure_if_new(failures, key, subkey, value)
118
+ add_failure(failures, key, subkey, value) unless failures[key][subkey].include?(value)
119
+ end
120
+
121
+ def add_failure(failures, key, subkey, value)
122
+ failures[key][subkey] << value
123
+ end
124
+
125
+ def result(failures)
126
+ {
127
+ is_valid: failures.empty?,
128
+ repeated: failures.each_with_object({}) { |(k,v), h| h[k] = v.keys },
129
+ failures: failures
130
+ }
131
+ end
132
+
133
+ end
134
+ end
@@ -3,7 +3,7 @@ module Forester
3
3
 
4
4
  def as_root_hash(options = {})
5
5
  default_options = {
6
- fields_to_include: fields, # all of them
6
+ fields_to_include: :all,
7
7
  max_level: :last,
8
8
  children_key: :children,
9
9
  stringify_keys: false,
@@ -1,7 +1,7 @@
1
1
  module Forester
2
2
  class Version
3
3
  MAJOR = 4
4
- MINOR = 0
4
+ MINOR = 1
5
5
  PATCH = 0
6
6
  PRE = nil
7
7
 
data/lib/forester.rb CHANGED
@@ -4,6 +4,7 @@ require 'securerandom'
4
4
  require 'yaml'
5
5
 
6
6
  require 'forester/tree_node_ext/aggregators'
7
+ require 'forester/tree_node_ext/validators'
7
8
  require 'forester/tree_node_ext/mutators'
8
9
  require 'forester/tree_node_ext/views'
9
10
 
@@ -12,10 +12,23 @@ class TestTreeNode < Minitest::Test
12
12
  end
13
13
 
14
14
  def test_values
15
- expected = (0..9).reduce(:+)
15
+ values = (0..9).to_a
16
+
17
+ expected = values.reduce(:+)
16
18
  actual = @@tree.reduce(0) { |acum, node| acum + node.get('value') }
17
19
 
18
20
  assert_equal expected, actual
21
+
22
+ assert_equal values, @@tree.get('value', { subtree: true })
23
+ end
24
+
25
+ def test_missing_values
26
+ assert_equal 0, @@tree.get('value')
27
+ assert_equal 'no', @@tree.get('whatever', { default: 'no' })
28
+ assert_equal 'no', @@tree.get('whatever', { default: 'missing' }) { 'no' }
29
+ assert_equal 'no', @@tree.get('whatever') { 'no' }
30
+ assert_equal 'no', @@tree.get('whatever') { |n| 'no' }
31
+ assert_equal 1, @@tree.get('whatever') { |n| n.get('value') + 1 }
19
32
  end
20
33
 
21
34
  def test_levels
@@ -0,0 +1,245 @@
1
+ require 'minitest/autorun'
2
+ require 'forester'
3
+
4
+ require_relative './simple_tree_helper'
5
+
6
+ class TestValidators < Minitest::Test
7
+
8
+ include SimpleTreeHelper
9
+
10
+ def test_validate_uniqueness_of_field_uniques
11
+ expected = {
12
+ is_valid: true,
13
+ repeated: {},
14
+ failures: {}
15
+ }
16
+
17
+ ['name', :name, 'special', 'ghost'].each do |field|
18
+ actual = @@tree.validate_uniqueness_of_field(field)
19
+ assert_equal expected, actual
20
+ end
21
+
22
+ end
23
+
24
+ def test_validate_uniqueness_of_field_color
25
+ expected = {
26
+ is_valid: false,
27
+ repeated: {
28
+ :color => ['Green', 'Yellow']
29
+ },
30
+ failures: {
31
+ :color => {
32
+ 'Green' => ['First node of level 1', 'Second node of level 1'],
33
+ 'Yellow' => ['First node of level 4', 'Second node of level 4']
34
+ }
35
+ }
36
+ }
37
+
38
+ actual = @@tree.validate_uniqueness_of_field(:color)
39
+ assert_equal expected, actual
40
+ end
41
+
42
+ def test_validate_uniqueness_of_field_color_first_failure_only
43
+ expected = {
44
+ is_valid: false,
45
+ repeated: {
46
+ :color => ['Green']
47
+ },
48
+ failures: {
49
+ :color => {
50
+ 'Green' => ['First node of level 1', 'Second node of level 1']
51
+ }
52
+ }
53
+ }
54
+
55
+ actual = @@tree.validate_uniqueness_of_field(:color, {
56
+ first_failure_only: true
57
+ })
58
+ assert_equal expected, actual
59
+ end
60
+
61
+ def test_validate_uniqueness_of_field_color_among_siblings_of_level_1
62
+ expected = {
63
+ is_valid: false,
64
+ repeated: {
65
+ :color => ['Green']
66
+ },
67
+ failures: {
68
+ :color => {
69
+ 'Green' => ['First node of level 1', 'Second node of level 1']
70
+ }
71
+ }
72
+ }
73
+
74
+ actual = @@tree.validate_uniqueness_of_field(:color, {
75
+ among_siblings_of_level: 1
76
+ })
77
+
78
+ assert_equal expected, actual
79
+ end
80
+
81
+ def test_validate_uniqueness_of_field_color_among_siblings_of_level_2
82
+ expected = {
83
+ is_valid: true,
84
+ repeated: {},
85
+ failures: {}
86
+ }
87
+
88
+ actual = @@tree.validate_uniqueness_of_field(:color, {
89
+ among_siblings_of_level: 2
90
+ })
91
+
92
+ assert_equal expected, actual
93
+ end
94
+
95
+ def test_validate_uniqueness_of_field_color_within_subtrees_of_level_1
96
+ expected = {
97
+ is_valid: false,
98
+ repeated: {
99
+ :color => ['Yellow']
100
+ },
101
+ failures: {
102
+ :color => {
103
+ 'Yellow' => ['First node of level 4', 'Second node of level 4']
104
+ }
105
+ }
106
+ }
107
+
108
+ actual = @@tree.validate_uniqueness_of_field(:color, {
109
+ within_subtrees_of_level: 1,
110
+ })
111
+
112
+ assert_equal expected, actual
113
+ end
114
+
115
+ def test_validate_uniqueness_of_field_color_within_subtrees_of_level_3
116
+ expected = {
117
+ is_valid: false,
118
+ repeated: {
119
+ :color => ['Yellow']
120
+ },
121
+ failures: {
122
+ :color => {
123
+ 'Yellow' => ['First node of level 4', 'Second node of level 4']
124
+ }
125
+ }
126
+ }
127
+
128
+ actual = @@tree.validate_uniqueness_of_field(:color, {
129
+ within_subtrees_of_level: 3,
130
+ })
131
+
132
+ assert_equal expected, actual
133
+ end
134
+
135
+ def test_validate_uniqueness_of_field_color_within_subtrees_of_level_4
136
+ expected = {
137
+ is_valid: true,
138
+ repeated: {},
139
+ failures: {}
140
+ }
141
+
142
+ actual = @@tree.validate_uniqueness_of_field(:color, {
143
+ within_subtrees_of_level: 4,
144
+ })
145
+
146
+ assert_equal expected, actual
147
+ end
148
+
149
+ def test_validate_uniqueness_of_fields_name_color
150
+ expected = {
151
+ is_valid: false,
152
+ repeated: {
153
+ 'color' => ['Green', 'Yellow']
154
+ },
155
+ failures: {
156
+ 'color' => {
157
+ 'Green' => ['First node of level 1', 'Second node of level 1'],
158
+ 'Yellow' => ['First node of level 4', 'Second node of level 4']
159
+ }
160
+ }
161
+ }
162
+
163
+ actual = @@tree.validate_uniqueness_of_fields(['name', 'color'])
164
+
165
+ assert_equal expected, actual
166
+ end
167
+
168
+ def test_validate_uniqueness_of_combination_of_fields_name_color
169
+ expected = {
170
+ is_valid: true,
171
+ repeated: {},
172
+ failures: {}
173
+ }
174
+
175
+ actual = @@tree.validate_uniqueness_of_fields(['name', 'color'], {
176
+ combination: true
177
+ })
178
+
179
+ assert_equal expected, actual
180
+ end
181
+
182
+ def test_validate_uniqueness_of_fields_color_tone_first_failure_only
183
+ expected = {
184
+ is_valid: false,
185
+ repeated: {
186
+ 'color' => ['Green']
187
+ },
188
+ failures: {
189
+ 'color' => {
190
+ 'Green' => ['First node of level 1', 'Second node of level 1']
191
+ }
192
+ }
193
+ }
194
+
195
+ actual = @@tree.validate_uniqueness_of_fields(['color', 'tone'], {
196
+ first_failure_only: true
197
+ })
198
+
199
+ assert_equal expected, actual
200
+ end
201
+
202
+ def test_validate_uniqueness_of_fields_color_tone
203
+ expected = {
204
+ is_valid: false,
205
+ repeated: {
206
+ 'color' => ['Green', 'Yellow'],
207
+ 'tone' => ['Dark']
208
+ },
209
+ failures: {
210
+ 'color' => {
211
+ 'Green' => ['First node of level 1', 'Second node of level 1'],
212
+ 'Yellow' => ['First node of level 4', 'Second node of level 4']
213
+ },
214
+ 'tone' => {
215
+ 'Dark' => ['First node of level 1', 'Second node of level 1']
216
+ }
217
+ }
218
+ }
219
+
220
+ actual = @@tree.validate_uniqueness_of_fields(['color', 'tone'])
221
+
222
+ assert_equal expected, actual
223
+ end
224
+
225
+ def test_validate_uniqueness_of_combination_of_fields_color_tone
226
+ expected = {
227
+ is_valid: false,
228
+ repeated: {
229
+ ['color', 'tone'] => [['Green', 'Dark']]
230
+ },
231
+ failures: {
232
+ ['color', 'tone'] => {
233
+ ['Green', 'Dark'] => ['First node of level 1', 'Second node of level 1']
234
+ }
235
+ }
236
+ }
237
+
238
+ actual = @@tree.validate_uniqueness_of_fields(['color', 'tone'], {
239
+ combination: true
240
+ })
241
+
242
+ assert_equal expected, actual
243
+ end
244
+
245
+ end
@@ -7,6 +7,8 @@ root:
7
7
  children:
8
8
  - name: First node of level 1
9
9
  value: 1
10
+ color: Green
11
+ tone: Dark
10
12
  children:
11
13
  - name: First node of level 2
12
14
  value: 2
@@ -15,12 +17,15 @@ root:
15
17
  - I want to be the very best
16
18
  - like no one ever was
17
19
  - name: Second node of level 2
20
+ special: No other node has this field
18
21
  value: 3
19
22
  strings:
20
23
  - I have a sibling to my left
21
24
  - She wants to catch them all
22
25
  - name: Second node of level 1
23
26
  value: 4
27
+ color: Green
28
+ tone: Dark
24
29
  children:
25
30
  - name: Third node of level 2
26
31
  value: 5
@@ -38,8 +43,10 @@ root:
38
43
  children:
39
44
  - name: First node of level 4
40
45
  value: 8
46
+ color: Yellow
41
47
  - name: Second node of level 4
42
48
  value: 9
49
+ color: Yellow
43
50
  strings:
44
51
  - Could forester handle trees with hundreds of levels?
45
52
  - Maybe.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forester
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.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-08-27 00:00:00.000000000 Z
11
+ date: 2016-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubytree
@@ -87,6 +87,7 @@ files:
87
87
  - lib/forester/tree_node.rb
88
88
  - lib/forester/tree_node_ext/aggregators.rb
89
89
  - lib/forester/tree_node_ext/mutators.rb
90
+ - lib/forester/tree_node_ext/validators.rb
90
91
  - lib/forester/tree_node_ext/views.rb
91
92
  - lib/forester/version.rb
92
93
  - test/simple_tree_helper.rb
@@ -94,6 +95,7 @@ files:
94
95
  - test/test_mutators.rb
95
96
  - test/test_tree_factory.rb
96
97
  - test/test_tree_node.rb
98
+ - test/test_validators.rb
97
99
  - test/test_views.rb
98
100
  - test/trees/simple_tree.yml
99
101
  homepage: http://rubygems.org/gems/forester
@@ -126,5 +128,6 @@ test_files:
126
128
  - test/test_mutators.rb
127
129
  - test/test_tree_factory.rb
128
130
  - test/test_tree_node.rb
131
+ - test/test_validators.rb
129
132
  - test/test_views.rb
130
133
  - test/trees/simple_tree.yml