forester 4.0.0 → 4.1.0

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