qonfig 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +457 -137
  4. data/lib/qonfig/command_set.rb +7 -1
  5. data/lib/qonfig/commands/add_nested_option.rb +3 -1
  6. data/lib/qonfig/commands/add_option.rb +2 -1
  7. data/lib/qonfig/commands/base.rb +2 -1
  8. data/lib/qonfig/commands/compose.rb +16 -10
  9. data/lib/qonfig/commands/expose_yaml.rb +2 -1
  10. data/lib/qonfig/commands/load_from_env.rb +2 -1
  11. data/lib/qonfig/commands/load_from_json.rb +2 -1
  12. data/lib/qonfig/commands/load_from_self.rb +2 -1
  13. data/lib/qonfig/commands/load_from_yaml.rb +2 -1
  14. data/lib/qonfig/data_set/lock.rb +46 -0
  15. data/lib/qonfig/data_set.rb +92 -23
  16. data/lib/qonfig/errors.rb +8 -0
  17. data/lib/qonfig/plugins/toml/commands/expose_toml.rb +2 -1
  18. data/lib/qonfig/plugins/toml/commands/load_from_toml.rb +2 -1
  19. data/lib/qonfig/settings/builder.rb +20 -4
  20. data/lib/qonfig/settings/callbacks.rb +43 -0
  21. data/lib/qonfig/settings/key_matcher.rb +175 -0
  22. data/lib/qonfig/settings/lock.rb +3 -3
  23. data/lib/qonfig/settings.rb +144 -33
  24. data/lib/qonfig/validator/basic.rb +53 -0
  25. data/lib/qonfig/validator/builder/attribute_consistency.rb +181 -0
  26. data/lib/qonfig/validator/builder.rb +169 -0
  27. data/lib/qonfig/validator/collection.rb +73 -0
  28. data/lib/qonfig/validator/dsl.rb +51 -0
  29. data/lib/qonfig/validator/method_based.rb +49 -0
  30. data/lib/qonfig/validator/predefined/common.rb +53 -0
  31. data/lib/qonfig/validator/predefined/registry.rb +83 -0
  32. data/lib/qonfig/validator/predefined/registry_control_mixin.rb +43 -0
  33. data/lib/qonfig/validator/predefined.rb +41 -0
  34. data/lib/qonfig/validator/proc_based.rb +53 -0
  35. data/lib/qonfig/validator.rb +58 -0
  36. data/lib/qonfig/version.rb +1 -1
  37. data/lib/qonfig.rb +1 -0
  38. data/qonfig.gemspec +1 -1
  39. metadata +19 -5
  40. data/lib/qonfig/data_set/validator.rb +0 -7
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.13.0
5
+ class Qonfig::Settings::KeyMatcher
6
+ # @return [String]
7
+ #
8
+ # @api private
9
+ # @since 0.13.0
10
+ SCOPE_SPLITTER = '.'
11
+
12
+ # @return [String]
13
+ #
14
+ # @api private
15
+ # @since 0.13.0
16
+ MATCHER_SCOPE_SPLITTER = '\.'
17
+
18
+ # @return [String]
19
+ #
20
+ # @api private
21
+ # @since 0.13.0
22
+ GENERIC_PART_PATTERN = '*'
23
+
24
+ # @return [String]
25
+ #
26
+ # @api private
27
+ # @since 0.13.0
28
+ GENERIC_REGEXP_PATTERN = '[^\.]+\.'
29
+
30
+ # @return [String]
31
+ #
32
+ # @api private
33
+ # @since 0.13.0
34
+ INFINITE_PART_PATTERN = '#'
35
+
36
+ # @return [String]
37
+ #
38
+ # @api private
39
+ # @since 0.13.0
40
+ INFINITE_REGEXP_PATTERN = '\.*.*'
41
+
42
+ # @param scope_pattern [String]
43
+ # @return [void]
44
+ #
45
+ # @raise [Qonfig::ArgumentError]
46
+ #
47
+ # @api private
48
+ # @since 0.13.0
49
+ def initialize(scope_pattern)
50
+ raise Qonfig::ArgumentError unless scope_pattern.is_a?(String)
51
+
52
+ @scope_pattern = scope_pattern
53
+ @scope_pattern_size = count_scope_pattern_size(scope_pattern)
54
+ @pattern_matcher = build_pattern_matcher(scope_pattern)
55
+ end
56
+
57
+ # @param setting_key_pattern [String]
58
+ # @return [Boolean]
59
+ #
60
+ # @api private
61
+ # @since 0.13.0
62
+ def match?(setting_key_pattern)
63
+ return false unless comparable_event_scopes?(setting_key_pattern)
64
+ !!pattern_matcher.match(setting_key_pattern)
65
+ end
66
+
67
+ private
68
+
69
+ # @return [Regexp]
70
+ #
71
+ # @api private
72
+ # @since 0.13.0
73
+ attr_reader :pattern_matcher
74
+
75
+ # @return [String]
76
+ #
77
+ # @api private
78
+ # @since 0.13.0
79
+ attr_reader :scope_pattern
80
+
81
+ # @return [Integer, Float::INFINITY]
82
+ #
83
+ # @api private
84
+ # @since 0.13.0
85
+ attr_reader :scope_pattern_size
86
+
87
+ # @param scope_pattern [String]
88
+ # @return [Integer, Float::INFINITY]
89
+ #
90
+ # @api private
91
+ # @since 0.13.0
92
+ def count_scope_pattern_size(scope_pattern)
93
+ return Float::INFINITY if scope_pattern == INFINITE_PART_PATTERN
94
+ return Float::INFINITY if scope_pattern.include?('.#')
95
+ return Float::INFINITY if scope_pattern.include?('#.')
96
+ return Float::INFINITY if scope_pattern.include?('.#.')
97
+
98
+ scope_pattern.split(SCOPE_SPLITTER).size
99
+ end
100
+
101
+ # @param setting_key_pattern [String]
102
+ # @return [Integer]
103
+ #
104
+ # @api private
105
+ # @since 0.13.0
106
+ def count_setting_key_pattern_size(setting_key_pattern)
107
+ setting_key_pattern.split(SCOPE_SPLITTER).size
108
+ end
109
+
110
+ # @param setting_key_pattern [String]
111
+ # @return [Boolean]
112
+ #
113
+ # @api private
114
+ # @since 0.13.0
115
+ def comparable_event_scopes?(setting_key_pattern)
116
+ # NOTE: Integer#finite?, Integer#infinite?, Float#finite?, Float#nfinite?
117
+ # Cant be used (backward compatability with old ruby versions)
118
+ return true if scope_pattern_size == Float::INFINITY
119
+ scope_pattern_size == count_setting_key_pattern_size(setting_key_pattern)
120
+ end
121
+
122
+ # @param pattern [String, NilClass]
123
+ # @return [Boolean]
124
+ #
125
+ # @api private
126
+ # @since 0.13.0
127
+ def non_generic_pattern?(pattern = nil)
128
+ return false unless pattern
129
+ pattern != GENERIC_REGEXP_PATTERN && pattern != INFINITE_REGEXP_PATTERN
130
+ end
131
+
132
+ # "\.test\.created\.today\." => "test\.created\.today"
133
+ #
134
+ # @param regexp_string [String]
135
+ # @option left [Boolean]
136
+ # @option right [Boolean]
137
+ # @return [String]
138
+ #
139
+ # @api private
140
+ # @since 0.13.0
141
+ def strip_regexp_string(regexp_string, left: false, right: false)
142
+ pattern = regexp_string
143
+ pattern = pattern[2..-1] if left && pattern[0..1] == MATCHER_SCOPE_SPLITTER
144
+ pattern = pattern[0..-3] if right && pattern[-2..-1] == MATCHER_SCOPE_SPLITTER
145
+ pattern
146
+ end
147
+
148
+ # @param scope_pattern [String]
149
+ # @return [Regexp]
150
+ #
151
+ # @api private
152
+ # @since 0.13.0
153
+ def build_pattern_matcher(scope_pattern)
154
+ routing_parts = scope_pattern.split(SCOPE_SPLITTER)
155
+
156
+ regexp_string = routing_parts.each_with_object([]) do |routing_part, regexp_parts|
157
+ case routing_part
158
+ when GENERIC_PART_PATTERN
159
+ regexp_parts << GENERIC_REGEXP_PATTERN
160
+ when INFINITE_PART_PATTERN
161
+ if non_generic_pattern?(regexp_parts.last)
162
+ regexp_parts[-1] = strip_regexp_string(regexp_parts.last, right: true)
163
+ end
164
+
165
+ regexp_parts << INFINITE_REGEXP_PATTERN
166
+ else
167
+ regexp_parts << (Regexp.escape(routing_part) + MATCHER_SCOPE_SPLITTER)
168
+ end
169
+ end.join
170
+
171
+ regexp_string = strip_regexp_string(regexp_string, left: true, right: true)
172
+
173
+ Regexp.new('\A' + regexp_string + '\z')
174
+ end
175
+ end
@@ -17,7 +17,7 @@ class Qonfig::Settings::Lock
17
17
  # @api private
18
18
  # @since 0.2.0
19
19
  def thread_safe_definition(&instructions)
20
- definition_lock.synchronize(&instructions)
20
+ definition_lock.owned? ? yield : definition_lock.synchronize(&instructions)
21
21
  end
22
22
 
23
23
  # @param instructions [Proc]
@@ -26,7 +26,7 @@ class Qonfig::Settings::Lock
26
26
  # @api private
27
27
  # @since 0.2.0
28
28
  def thread_safe_access(&instructions)
29
- access_lock.synchronize(&instructions)
29
+ access_lock.owned? ? yield : access_lock.synchronize(&instructions)
30
30
  end
31
31
 
32
32
  # @param instructions [Proc]
@@ -35,7 +35,7 @@ class Qonfig::Settings::Lock
35
35
  # @api private
36
36
  # @since 0.2.0
37
37
  def thread_safe_merge(&instructions)
38
- merge_lock.synchronize(&instructions)
38
+ merge_lock.owned? ? yield : merge_lock.synchronize(&instructions)
39
39
  end
40
40
 
41
41
  private
@@ -2,11 +2,13 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
- # rubocop:disable Metrics/ClassLength
6
- class Qonfig::Settings
5
+ # rubocop:disable Metrics/ClassLength, Layout/ClassStructure
6
+ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_METHODS constant
7
+ require_relative 'settings/callbacks'
7
8
  require_relative 'settings/lock'
8
9
  require_relative 'settings/builder'
9
10
  require_relative 'settings/key_guard'
11
+ require_relative 'settings/key_matcher'
10
12
 
11
13
  # @return [Proc]
12
14
  #
@@ -26,11 +28,49 @@ class Qonfig::Settings
26
28
  # @since 0.1.0
27
29
  attr_reader :__options__
28
30
 
31
+ # @return [Qonfig::Settings::Callbacks]
32
+ #
33
+ # @api private
34
+ # @since 0.13.0
35
+ attr_reader :__mutation_callbacks__
36
+
29
37
  # @api private
30
38
  # @since 0.1.0
31
- def initialize
39
+ def initialize(__mutation_callbacks__)
32
40
  @__options__ = {}
33
41
  @__lock__ = Lock.new
42
+ @__mutation_callbacks__ = __mutation_callbacks__
43
+ end
44
+
45
+ # @param block [Proc]
46
+ # @return [Enumerable]
47
+ #
48
+ # @yield [key, value]
49
+ # @yieldparam key [String]
50
+ # @yieldparam value [Object]
51
+ #
52
+ # @api private
53
+ # @since 0.13.0
54
+ def __each_setting__(&block)
55
+ __lock__.thread_safe_access do
56
+ __each_key_value_pair__(&block)
57
+ end
58
+ end
59
+
60
+ # @param initial_setting_key [String, NilClass]
61
+ # @param block [Proc]
62
+ # @return [Enumerable]
63
+ #
64
+ # @yield [key, value]
65
+ # @yieldparam key [String]
66
+ # @yieldparam value [Object]
67
+ #
68
+ # @api private
69
+ # @since 0.13.0
70
+ def __deep_each_setting__(initial_setting_key = nil, &block)
71
+ __lock__.thread_safe_access do
72
+ __deep_each_key_value_pair__(initial_setting_key, &block)
73
+ end
34
74
  end
35
75
 
36
76
  # @param key [Symbol, String]
@@ -39,7 +79,7 @@ class Qonfig::Settings
39
79
  #
40
80
  # @api private
41
81
  # @since 0.1.0
42
- def __define_setting__(key, value)
82
+ def __define_setting__(key, value) # rubocop:disable Metrics/AbcSize
43
83
  __lock__.thread_safe_definition do
44
84
  key = __indifferently_accessable_option_key__(key)
45
85
 
@@ -48,13 +88,15 @@ class Qonfig::Settings
48
88
  case
49
89
  when !__options__.key?(key)
50
90
  __options__[key] = value
51
- when __options__[key].is_a?(Qonfig::Settings) && value.is_a?(Qonfig::Settings)
91
+ when __is_a_setting__(__options__[key]) && __is_a_setting__(value)
52
92
  __options__[key].__append_settings__(value)
53
93
  else
54
94
  __options__[key] = value
55
95
  end
56
96
 
57
- __define_accessor__(key)
97
+ __define_option_reader__(key)
98
+ __define_option_writer__(key)
99
+ __define_option_predicate__(key)
58
100
  end
59
101
  end
60
102
 
@@ -71,6 +113,14 @@ class Qonfig::Settings
71
113
  end
72
114
  end
73
115
 
116
+ # @return [void]
117
+ #
118
+ # @api private
119
+ # @since 0.13.0
120
+ def __invoke_mutation_callbacks__
121
+ __mutation_callbacks__.call
122
+ end
123
+
74
124
  # @param key [Symbol, String]
75
125
  # @return [Object]
76
126
  #
@@ -90,13 +140,13 @@ class Qonfig::Settings
90
140
  __lock__.thread_safe_access { __set_value__(key, value) }
91
141
  end
92
142
 
93
- # @param options_map [Hash]
143
+ # @param settings_map [Hash]
94
144
  # @return [void]
95
145
  #
96
146
  # @api private
97
147
  # @since 0.3.0
98
- def __apply_values__(options_map)
99
- __lock__.thread_safe_access { __set_values_from_map__(options_map) }
148
+ def __apply_values__(settings_map)
149
+ __lock__.thread_safe_access { __set_values_from_map__(settings_map) }
100
150
  end
101
151
 
102
152
  # @param keys [Array<String, Symbol>]
@@ -191,7 +241,7 @@ class Qonfig::Settings
191
241
  __options__.freeze
192
242
 
193
243
  __options__.each_value do |value|
194
- value.__freeze__ if value.is_a?(Qonfig::Settings)
244
+ value.__freeze__ if __is_a_setting__(value)
195
245
  end
196
246
  end
197
247
  end
@@ -204,6 +254,15 @@ class Qonfig::Settings
204
254
  __lock__.thread_safe_access { __options__.frozen? }
205
255
  end
206
256
 
257
+ # @param value [Any]
258
+ # @return [Boolean]
259
+ #
260
+ # @api private
261
+ # @since 0.13.0
262
+ def __is_a_setting__(value)
263
+ value.is_a?(Qonfig::Settings)
264
+ end
265
+
207
266
  private
208
267
 
209
268
  # @return [Qonfig::Settings::Lock]
@@ -212,7 +271,47 @@ class Qonfig::Settings
212
271
  # @since 0.2.0
213
272
  attr_reader :__lock__
214
273
 
215
- # @param options_map [Hash]
274
+ # @param block [Proc]
275
+ # @return [Enumerable]
276
+ #
277
+ # @yield [setting_key, setting_value]
278
+ # @yieldparam key [String]
279
+ # @yieldparam value [Object]
280
+ #
281
+ # @api private
282
+ # @since 0.13.0
283
+ def __each_key_value_pair__(&block)
284
+ __options__.each_pair(&block)
285
+ end
286
+
287
+ # @param initial_setting_key [String, NilClass]
288
+ # @param block [Proc]
289
+ # @return [Enumerable]
290
+ #
291
+ # @yield [setting_key, setting_value]
292
+ # @yieldparam setting_key [String]
293
+ # @yieldparam setting_value [Object]
294
+ #
295
+ # @api private
296
+ # @since 0.13.0
297
+ def __deep_each_key_value_pair__(initial_setting_key = nil, &block)
298
+ enumerator = Enumerator.new do |yielder|
299
+ __each_key_value_pair__ do |setting_key, setting_value|
300
+ final_setting_key =
301
+ initial_setting_key ? "#{initial_setting_key}.#{setting_key}" : setting_key
302
+
303
+ if __is_a_setting__(setting_value)
304
+ setting_value.__deep_each_setting__(final_setting_key, &block)
305
+ else
306
+ yielder.yield(final_setting_key, setting_value)
307
+ end
308
+ end
309
+ end
310
+
311
+ block_given? ? enumerator.each(&block) : enumerator
312
+ end
313
+
314
+ # @param settings_map [Hash]
216
315
  # @return [void]
217
316
  #
218
317
  # @raise [Qonfig::ArgumentError]
@@ -220,21 +319,21 @@ class Qonfig::Settings
220
319
  #
221
320
  # @api private
222
321
  # @since 0.3.0
223
- def __set_values_from_map__(options_map)
322
+ def __set_values_from_map__(settings_map)
224
323
  ::Kernel.raise(
225
324
  Qonfig::ArgumentError, 'Options map should be represented as a hash'
226
- ) unless options_map.is_a?(Hash)
325
+ ) unless settings_map.is_a?(Hash)
227
326
 
228
- options_map.each_pair do |key, value|
327
+ settings_map.each_pair do |key, value|
229
328
  current_value = __get_value__(key)
230
329
 
231
330
  # NOTE: some duplications here was made only for the better code readability
232
331
  case
233
- when !current_value.is_a?(Qonfig::Settings)
332
+ when !__is_a_setting__(current_value)
234
333
  __set_value__(key, value)
235
- when current_value.is_a?(Qonfig::Settings) && value.is_a?(Hash)
334
+ when __is_a_setting__(current_value) && value.is_a?(Hash)
236
335
  current_value.__apply_values__(value)
237
- when current_value.is_a?(Qonfig::Settings) && !value.is_a?(Hash)
336
+ when __is_a_setting__(current_value) && !value.is_a?(Hash)
238
337
  ::Kernel.raise(
239
338
  Qonfig::AmbiguousSettingValueError,
240
339
  "Can not redefine option <#{key}> that contains nested options"
@@ -255,12 +354,10 @@ class Qonfig::Settings
255
354
  ) if __options__.frozen?
256
355
 
257
356
  __options__.each_pair do |key, value|
258
- if value.is_a?(Qonfig::Settings)
259
- value.__clear__
260
- else
261
- __options__[key] = nil
262
- end
357
+ __is_a_setting__(value) ? value.__clear__ : __options__[key] = nil
263
358
  end
359
+
360
+ __invoke_mutation_callbacks__
264
361
  end
265
362
 
266
363
  # @param key [String, Symbol]
@@ -301,14 +398,16 @@ class Qonfig::Settings
301
398
  ::Kernel.raise(Qonfig::FrozenSettingsError, 'Can not modify frozen settings')
302
399
  end
303
400
 
304
- if __options__[key].is_a?(Qonfig::Settings)
401
+ if __is_a_setting__(__options__[key])
305
402
  ::Kernel.raise(
306
403
  Qonfig::AmbiguousSettingValueError,
307
404
  "Can not redefine option <#{key}> that contains nested options"
308
405
  )
309
406
  end
310
407
 
311
- __options__[key] = value
408
+ (__options__[key] = value)
409
+
410
+ __invoke_mutation_callbacks__
312
411
  end
313
412
 
314
413
  # @param keys [Array<Symbol, String>]
@@ -328,12 +427,12 @@ class Qonfig::Settings
328
427
  case
329
428
  when rest_keys.empty?
330
429
  result
331
- when !result.is_a?(Qonfig::Settings)
430
+ when !__is_a_setting__(result)
332
431
  ::Kernel.raise(
333
432
  Qonfig::UnknownSettingError,
334
433
  'Setting with required digging sequence does not exist!'
335
434
  )
336
- when result.is_a?(Qonfig::Settings)
435
+ when __is_a_setting__(result)
337
436
  result.__dig__(*rest_keys)
338
437
  end
339
438
  end
@@ -350,7 +449,7 @@ class Qonfig::Settings
350
449
  {}.tap do |result|
351
450
  __deep_access__(*keys).tap do |setting|
352
451
  required_key = __indifferently_accessable_option_key__(keys.last)
353
- result[required_key] = setting.is_a?(Qonfig::Settings) ? setting.__to_h__ : setting
452
+ result[required_key] = __is_a_setting__(setting) ? setting.__to_h__ : setting
354
453
  end
355
454
  end
356
455
  end
@@ -386,7 +485,7 @@ class Qonfig::Settings
386
485
  transform_key: transform_key,
387
486
  transform_value: transform_value
388
487
  )
389
- when value.is_a?(Qonfig::Settings)
488
+ when __is_a_setting__(value)
390
489
  hash[final_key] = value.__to_hash__(
391
490
  transform_key: transform_key,
392
491
  transform_value: transform_value
@@ -402,16 +501,30 @@ class Qonfig::Settings
402
501
  # @return [void]
403
502
  #
404
503
  # @api private
405
- # @since 0.1.0
406
- def __define_accessor__(key)
504
+ # @since 0.13.0
505
+ def __define_option_reader__(key)
407
506
  define_singleton_method(key) do
408
507
  self.[](key)
409
508
  end
509
+ end
410
510
 
511
+ # @param key [Symbol, String]
512
+ # @return [void]
513
+ #
514
+ # @api private
515
+ # @since 0.13.0
516
+ def __define_option_writer__(key)
411
517
  define_singleton_method("#{key}=") do |value|
412
518
  self.[]=(key, value)
413
519
  end
520
+ end
414
521
 
522
+ # @param key [Symbol, String]
523
+ # @return [void]
524
+ #
525
+ # @api private
526
+ # @since 0.13.0
527
+ def __define_option_predicate__(key)
415
528
  define_singleton_method("#{key}?") do
416
529
  !!self.[](key)
417
530
  end
@@ -442,7 +555,6 @@ class Qonfig::Settings
442
555
  KeyGuard.new(key).prevent_core_method_intersection!
443
556
  end
444
557
 
445
- # rubocop:disable Layout/ClassStructure
446
558
  # @return [Array<String>]
447
559
  #
448
560
  # @api private
@@ -452,6 +564,5 @@ class Qonfig::Settings
452
564
  private_instance_methods(false) |
453
565
  %i[super define_singleton_method self]
454
566
  ).map(&:to_s).freeze
455
- # rubocop:enable Layout/ClassStructure
456
567
  end
457
- # rubocop:enable Metrics/ClassLength
568
+ # rubocop:enable Metrics/ClassLength, Layout/ClassStructure
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.13.0
5
+ class Qonfig::Validator::Basic
6
+ # @return [String, Symbol, NilClass]
7
+ #
8
+ # @api private
9
+ # @since 0.13.0
10
+ attr_reader :setting_key_matcher
11
+
12
+ # @param setting_key_matcher [Qonfig::Settings::KeyMatcher, NilClass]
13
+ # @return [void]
14
+ #
15
+ # @api private
16
+ # @since 0.13.0
17
+ def initialize(setting_key_matcher)
18
+ @setting_key_matcher = setting_key_matcher
19
+ end
20
+
21
+ # @param data_set [Qonfig::DataSet]
22
+ # @return [Boolean]
23
+ #
24
+ # @api private
25
+ # @since 0.13.0
26
+ def validate(data_set)
27
+ setting_key_provided? ? validate_concrete(data_set) : validate_full(data_set)
28
+ end
29
+
30
+ private
31
+
32
+ # @return [Boolean]
33
+ #
34
+ # @api private
35
+ # @since 0.13.0
36
+ def setting_key_provided?
37
+ !setting_key_matcher.nil?
38
+ end
39
+
40
+ # @param data_set [Qonfig::DataSet]
41
+ # @return [Any]
42
+ #
43
+ # @api private
44
+ # @since 0.13.0
45
+ def validate_full(data_set); end
46
+
47
+ # @param data_set [Qonfig::DataSet]
48
+ # @return [Any]
49
+ #
50
+ # @api private
51
+ # @since 0.13.0
52
+ def validate_concrete(data_set); end
53
+ end