ultra_settings 0.0.1.rc1 → 1.0.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.
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module UltraSettings
6
+ # Utility functions for coercing values to other data types.
7
+ class Coerce
8
+ # rubocop:disable Lint/BooleanSymbol
9
+ FALSE_VALUES = Set.new([
10
+ false, 0,
11
+ "0", :"0",
12
+ "f", :f,
13
+ "F", :F,
14
+ "false", :false,
15
+ "FALSE", :FALSE,
16
+ "off", :off,
17
+ "OFF", :OFF
18
+ ]).freeze
19
+ # rubocop:enable Lint/BooleanSymbol
20
+
21
+ class << self
22
+ # Cast a value to a specific type.
23
+ #
24
+ # @param value [Object]
25
+ # @param type [Symbol]
26
+ # @return [Object]
27
+ def coerce_value(value, type)
28
+ return nil if value.nil? || value == ""
29
+
30
+ case type
31
+ when :integer
32
+ value.is_a?(Integer) ? value : value.to_s&.to_i
33
+ when :float
34
+ value.is_a?(Float) ? value : value.to_s&.to_f
35
+ when :boolean
36
+ Coerce.boolean(value)
37
+ when :datetime
38
+ Coerce.time(value)
39
+ when :array
40
+ Array(value).map(&:to_s)
41
+ when :symbol
42
+ value.to_s.to_sym
43
+ else
44
+ value.to_s
45
+ end
46
+ end
47
+
48
+ # Cast variations of booleans (i.e. "true", "false", 1, 0, etc.) to actual boolean objects.
49
+ #
50
+ # @param value [Object]
51
+ # @return [Boolean]
52
+ def boolean(value)
53
+ if value == false
54
+ false
55
+ elsif blank?(value)
56
+ nil
57
+ else
58
+ !FALSE_VALUES.include?(value)
59
+ end
60
+ end
61
+
62
+ # Cast a value to a Time object.
63
+ #
64
+ # @param value [Object]
65
+ # @return [Time]
66
+ def time(value)
67
+ value = nil if value.nil? || value.to_s.empty?
68
+ return nil if value.nil?
69
+ time = if value.is_a?(Numeric)
70
+ Time.at(value)
71
+ elsif value.respond_to?(:to_time)
72
+ value.to_time
73
+ else
74
+ Time.parse(value.to_s)
75
+ end
76
+ if time.respond_to?(:in_time_zone) && Time.respond_to?(:zone)
77
+ time = time.in_time_zone(Time.zone)
78
+ end
79
+ time
80
+ end
81
+
82
+ # @return [Boolean] true if the value is nil or empty.
83
+ def blank?(value)
84
+ return true if value.nil?
85
+ if value.respond_to?(:empty?)
86
+ value.empty?
87
+ else
88
+ value.to_s.empty?
89
+ end
90
+ end
91
+
92
+ # @return [Boolean] true if the value is not nil and not empty.
93
+ def present?(value)
94
+ !blank?(value)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "singleton"
4
-
5
3
  module UltraSettings
6
4
  class Configuration
7
5
  include Singleton
@@ -9,24 +7,33 @@ module UltraSettings
9
7
  ALLOWED_NAME_PATTERN = /\A[a-z_][a-zA-Z0-9_]*\z/
10
8
  ALLOWED_TYPES = [:string, :symbol, :integer, :float, :boolean, :datetime, :array].freeze
11
9
 
12
- class_attribute :environment_variables_disabled, instance_accessor: false, default: false
13
-
14
- class_attribute :runtime_settings_disabled, instance_accessor: false, default: false
15
-
16
- class_attribute :yaml_config_disabled, instance_accessor: false, default: false
17
-
18
- class_attribute :env_var_delimiter, instance_accessor: false, default: "_"
19
-
20
- class_attribute :setting_delimiter, instance_accessor: false, default: "."
21
-
22
- class_attribute :env_var_upcase, instance_accessor: false, default: true
23
-
24
- class_attribute :setting_upcase, instance_accessor: false, default: false
25
-
26
- class_attribute :yaml_config_directory, instance_accessor: false, default: "config"
27
-
28
10
  class << self
29
- def define(name, type: :string, default: nil, default_if: nil, static: false, setting: nil, env_var: nil, yaml_key: nil)
11
+ # Define a field on the configuration. This will create a getter method for the field.
12
+ # The field value will be read from the environment, runtime settings, or a YAML file
13
+ # and coerced to the specified type. Empty strings will be converted to nil.
14
+ #
15
+ # @param name [Symbol, String] The name of the field.
16
+ # @param type [Symbol] The type of the field. Valid types are :string, :symbol, :integer,
17
+ # :float, :boolean, :datetime, and :array. The default type is :string. The :array type
18
+ # will return an array of strings.
19
+ # @param description [String] A description of the field.
20
+ # @param default [Object] The default value of the field.
21
+ # @param default_if [Proc, Symbol] A proc that returns true if the default value should be used.
22
+ # By default, the default value will be used if the field evaluates to nil. You can also set
23
+ # this to a symbol with the name of an instance method to call.
24
+ # @param static [Boolean] If true, the field value should never be changed. This is useful for
25
+ # fields that are used at startup to set static values in the application. Static field cannot
26
+ # be read from runtime settings.
27
+ # @param runtime_setting [String, Symbol] The name of the runtime setting to use for the field.
28
+ # By default this will be the underscored name of the class plus a dot plus the field name
29
+ # (i.e. MyServiceConfiguration#foo becomes "my_service.foo").
30
+ # @param env_var [String, Symbol] The name of the environment variable to use for the field.
31
+ # By default this will be the underscored name of the class plus an underscore plus the field name
32
+ # all in uppercase (i.e. MyServiceConfiguration#foo becomes "MY_SERVICE_FOO").
33
+ # @param yaml_key [String, Symbol] The name of the YAML key to use for the field. By default
34
+ # this is the name of the field.
35
+ # @return [void]
36
+ def field(name, type: :string, description: nil, default: nil, default_if: nil, static: nil, runtime_setting: nil, env_var: nil, yaml_key: nil)
30
37
  name = name.to_s
31
38
  type = type.to_sym
32
39
  static = !!static
@@ -39,27 +46,25 @@ module UltraSettings
39
46
  raise ArgumentError.new("Invalid type: #{type.inspect}")
40
47
  end
41
48
 
42
- unless default_if.nil? || default_if.is_a?(Proc)
43
- raise ArgumentError.new("default_if must be a Proc")
49
+ unless default_if.nil? || default_if.is_a?(Proc) || default_if.is_a?(Symbol)
50
+ raise ArgumentError.new("default_if must be a Proc or Symbol")
44
51
  end
45
52
 
46
53
  defined_fields[name] = Field.new(
47
54
  name: name,
48
55
  type: type,
56
+ description: description,
49
57
  default: default,
50
58
  default_if: default_if,
51
- env_var: env_var,
52
- setting_name: setting,
53
- yaml_key: yaml_key,
54
- env_var_prefix: env_var_prefix,
55
- env_var_upcase: env_var_upcase,
56
- setting_prefix: setting_prefix,
57
- setting_upcase: setting_upcase
59
+ env_var: construct_env_var(name, env_var),
60
+ runtime_setting: (static ? nil : construct_runtime_setting(name, runtime_setting)),
61
+ yaml_key: construct_yaml_key(name, yaml_key),
62
+ static: static
58
63
  )
59
64
 
60
65
  class_eval <<-RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Security/Eval
61
66
  def #{name}
62
- __get_value__(#{name.inspect}, #{static.inspect})
67
+ __get_value__(#{name.inspect})
63
68
  end
64
69
  RUBY
65
70
 
@@ -68,10 +73,41 @@ module UltraSettings
68
73
  end
69
74
  end
70
75
 
76
+ # List of the defined fields for the configuration.
77
+ #
78
+ # @return [Array<UltraSettings::Field>]
79
+ def fields
80
+ defined_fields.values
81
+ end
82
+
83
+ # Check if the field is defined on the configuration.
84
+ #
85
+ # @param name [Symbol, String] The name of the field.
86
+ # @return [Boolean]
87
+ def include?(name)
88
+ name = name.to_s
89
+ return true if defined_fields.include?(name)
90
+
91
+ if superclass <= Configuration
92
+ superclass.include?(name)
93
+ else
94
+ false
95
+ end
96
+ end
97
+
98
+ # Override the default environment variable prefix. By default this wil be
99
+ # the underscored name of the class plus an underscore
100
+ # (i.e. MyServiceConfiguration has a prefix of "MY_SERVICE_").
101
+ #
102
+ # @param value [String]
103
+ # @return [void]
71
104
  def env_var_prefix=(value)
72
105
  @env_var_prefix = value&.to_s
73
106
  end
74
107
 
108
+ # Get the environment variable prefix.
109
+ #
110
+ # @return [String]
75
111
  def env_var_prefix
76
112
  unless defined?(@env_var_prefix)
77
113
  @env_var_prefix = default_env_var_prefix
@@ -79,89 +115,310 @@ module UltraSettings
79
115
  @env_var_prefix
80
116
  end
81
117
 
82
- def setting_prefix=(value)
83
- @setting_prefix = value&.to_s
118
+ # Override the default runtime setting prefix. By default this wil be
119
+ # the underscored name of the class plus a dot (i.e. MyServiceConfiguration
120
+ # has a prefix of "my_service.").
121
+ #
122
+ # @param value [String]
123
+ # @return [void]
124
+ def runtime_setting_prefix=(value)
125
+ @runtime_setting_prefix = value&.to_s
84
126
  end
85
127
 
86
- def setting_prefix
87
- unless defined?(@setting_prefix)
88
- @setting_prefix = default_setting_prefix
128
+ # Get the runtime setting prefix.
129
+ #
130
+ # @return [String]
131
+ def runtime_setting_prefix
132
+ unless defined?(@runtime_setting_prefix)
133
+ @runtime_setting_prefix = default_runtime_setting_prefix
89
134
  end
90
- @setting_prefix
135
+ @runtime_setting_prefix
91
136
  end
92
137
 
138
+ # Override the default YAML config path. By default this will be the
139
+ # file matching the underscored name of the class in the configuration
140
+ # directory (i.e. MyServiceConfiguration has a default config path of
141
+ # "my_service.yml").
142
+ #
143
+ # @param value [String, Pathname]
144
+ # @return [void]
93
145
  def configuration_file=(value)
94
146
  value = Pathname.new(value) if value.is_a?(String)
95
- value = Rails.root + value if value && !value.absolute?
96
147
  @configuration_file = value
97
148
  end
98
149
 
150
+ # Get the YAML file path.
151
+ #
152
+ # @return [Pathname, nil]
99
153
  def configuration_file
100
154
  unless defined?(@configuration_file)
101
155
  @configuration_file = default_configuration_file
102
156
  end
103
- @configuration_file
157
+ return nil? unless @configuration_file
158
+
159
+ path = @configuration_file
160
+ if path.relative? && yaml_config_path
161
+ path = yaml_config_path.join(path)
162
+ end
163
+ path.expand_path
164
+ end
165
+
166
+ # Set to true to disable loading configuration from environment variables.
167
+ #
168
+ # @param value [Boolean]
169
+ # @return [void]
170
+ def environment_variables_disabled=(value)
171
+ set_inheritable_class_attribute(:@environment_variables_disabled, !!value)
172
+ end
173
+
174
+ # Check if loading configuration from environment variables is disabled.
175
+ #
176
+ # @return [Boolean]
177
+ def environment_variables_disabled?
178
+ get_inheritable_class_attribute(:@environment_variables_disabled, false)
179
+ end
180
+
181
+ # Set to true to disable loading configuration from runtime settings.
182
+ #
183
+ # @param value [Boolean]
184
+ # @return [void]
185
+ def runtime_settings_disabled=(value)
186
+ set_inheritable_class_attribute(:@runtime_settings_disabled, !!value)
187
+ end
188
+
189
+ # Check if loading configuration from runtime settings is disabled.
190
+ #
191
+ # @return [Boolean]
192
+ def runtime_settings_disabled?
193
+ get_inheritable_class_attribute(:@runtime_settings_disabled, false)
194
+ end
195
+
196
+ # Set to true to disable loading configuration from YAML files.
197
+ #
198
+ # @param value [Boolean]
199
+ # @return [void]
200
+ def yaml_config_disabled=(value)
201
+ set_inheritable_class_attribute(:@yaml_config_disabled, !!value)
202
+ end
203
+
204
+ # Check if loading configuration from YAML files is disabled.
205
+ #
206
+ # @return [Boolean]
207
+ def yaml_config_disabled?
208
+ get_inheritable_class_attribute(:@yaml_config_disabled, false)
209
+ end
210
+
211
+ # Set the environment variable delimiter used to construct the environment
212
+ # variable name for a field. By default this is an underscore.
213
+ #
214
+ # @param value [String]
215
+ def env_var_delimiter=(value)
216
+ set_inheritable_class_attribute(:@env_var_delimiter, value.to_s)
217
+ end
218
+
219
+ # Get the environment variable delimiter.
220
+ #
221
+ # @return [String]
222
+ def env_var_delimiter
223
+ get_inheritable_class_attribute(:@env_var_delimiter, "_")
224
+ end
225
+
226
+ # Set the runtime setting delimiter used to construct the runtime setting
227
+ # name for a field. By default this is a dot.
228
+ #
229
+ # @param value [String]
230
+ # @return [void]
231
+ def runtime_setting_delimiter=(value)
232
+ set_inheritable_class_attribute(:@runtime_setting_delimiter, value.to_s)
233
+ end
234
+
235
+ # Get the runtime setting delimiter.
236
+ #
237
+ # @return [String]
238
+ def runtime_setting_delimiter
239
+ get_inheritable_class_attribute(:@runtime_setting_delimiter, ".")
104
240
  end
105
241
 
242
+ # Set to true to upcase the environment variable name for a field. This
243
+ # is true by default.
244
+ #
245
+ # @param value [Boolean]
246
+ # @return [void]
247
+ def env_var_upcase=(value)
248
+ set_inheritable_class_attribute(:@env_var_upcase, !!value)
249
+ end
250
+
251
+ # Check if the environment variable name for a field should be upcased.
252
+ #
253
+ # @return [Boolean]
254
+ def env_var_upcase?
255
+ get_inheritable_class_attribute(:@env_var_upcase, true)
256
+ end
257
+
258
+ # Set to true to upcase the runtime setting name for a field. This
259
+ # is false by default.
260
+ #
261
+ # @param value [Boolean]
262
+ # @return [void]
263
+ def runtime_setting_upcase=(value)
264
+ set_inheritable_class_attribute(:@runtime_setting_upcase, !!value)
265
+ end
266
+
267
+ # Check if the runtime setting name for a field should be upcased.
268
+ #
269
+ # @return [Boolean]
270
+ def runtime_setting_upcase?
271
+ get_inheritable_class_attribute(:@runtime_setting_upcase, false)
272
+ end
273
+
274
+ # Set the directory where YAML files will be loaded from. By default this
275
+ # is the current working directory.
276
+ #
277
+ # @param value [String, Pathname]
278
+ # @return [void]
279
+ def yaml_config_path=(value)
280
+ value = Pathname.new(value) if value.is_a?(String)
281
+ value = value.expand_path if value&.relative?
282
+ set_inheritable_class_attribute(:@yaml_config_path, value)
283
+ end
284
+
285
+ # Get the directory where YAML files will be loaded from.
286
+ #
287
+ # @return [Pathname, nil]
288
+ def yaml_config_path
289
+ get_inheritable_class_attribute(:@yaml_config_path, nil)
290
+ end
291
+
292
+ # Set the environment namespace used in YAML file name. By default this
293
+ # is "development". Settings from the specific environment hash in the YAML
294
+ # file will be merged with base settings specified in the "shared" hash.
295
+ #
296
+ # @param value [String]
297
+ # @return [void]
298
+ def yaml_config_env=(value)
299
+ set_inheritable_class_attribute(:@yaml_config_env, value)
300
+ end
301
+
302
+ # Get the environment namespace used in YAML file name.
303
+ #
304
+ # @return [String]
305
+ def yaml_config_env
306
+ get_inheritable_class_attribute(:@yaml_config_env, "development")
307
+ end
308
+
309
+ # Override field values within a block.
310
+ #
311
+ # @param values [Hash<Symbol, Object>]] List of fields with the values they
312
+ # should return within the block.
313
+ # @return [Object] The value returned by the block.
314
+ def override!(values, &block)
315
+ instance.override!(values, &block)
316
+ end
317
+
318
+ # Load the YAML file for this configuration and return the values for the
319
+ # current environment.
320
+ #
321
+ # @return [Hash]
106
322
  def load_yaml_config
107
323
  return nil unless configuration_file
108
- return nil unless configuration_file.exist?
324
+ return nil unless configuration_file.exist? && configuration_file.file?
109
325
 
110
- Rails.application.config_for(configuration_file)
326
+ YamlConfig.new(configuration_file, yaml_config_env).to_h
111
327
  end
112
328
 
113
329
  private
114
330
 
115
331
  def defined_fields
116
332
  unless defined?(@defined_fields)
117
- @defined_fields = {}
118
- if superclass < Configuration
119
- superclass.send(:defined_fields).each do |name, field|
120
- @defined_fields[name] = Field.new(
121
- name: field.name,
122
- type: field.type,
123
- default: field.default,
124
- default_if: field.default_if,
125
- env_var: field.env_var,
126
- setting_name: field.setting_name,
127
- yaml_key: field.yaml_key,
128
- env_var_prefix: env_var_prefix,
129
- env_var_upcase: env_var_upcase,
130
- setting_prefix: setting_prefix,
131
- setting_upcase: setting_upcase
132
- )
133
- end
333
+ fields = {}
334
+ if superclass <= Configuration
335
+ fields = superclass.send(:defined_fields).dup
134
336
  end
337
+ @defined_fields = fields
135
338
  end
136
339
  @defined_fields
137
340
  end
138
341
 
139
342
  def root_name
140
- name.sub(/Configuration\z/, "")
343
+ name.sub(/Configuration\z/, "").split("::").collect do |part|
344
+ part.gsub(/([A-Z])(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << "_" }.downcase
345
+ end.join("/")
346
+ end
347
+
348
+ def set_inheritable_class_attribute(name, value)
349
+ instance_variable_set(name, value)
350
+ end
351
+
352
+ def get_inheritable_class_attribute(name, default = nil)
353
+ if instance_variable_defined?(name)
354
+ instance_variable_get(name)
355
+ elsif self != Configuration
356
+ superclass.send(:get_inheritable_class_attribute, name, default)
357
+ else
358
+ default
359
+ end
141
360
  end
142
361
 
143
362
  def default_configuration_file
144
- path = Pathname.new(yaml_config_directory)
145
- path = Rails.root + path if defined?(Rails) && !path.absolute?
146
- path.join(*"#{root_name.underscore}.yml".split("/"))
363
+ path = Pathname.new(yaml_config_path)
364
+ path.join(*"#{root_name}.yml".split("/"))
147
365
  end
148
366
 
149
367
  def default_env_var_prefix
150
- prefix = root_name.underscore.gsub("/", env_var_delimiter) + env_var_delimiter
151
- prefix = prefix.upcase if env_var_upcase
368
+ prefix = root_name.gsub("/", env_var_delimiter) + env_var_delimiter
369
+ prefix = prefix.upcase if env_var_upcase?
152
370
  prefix
153
371
  end
154
372
 
155
- def default_setting_prefix
156
- prefix = root_name.underscore.gsub("/", setting_delimiter) + setting_delimiter
157
- prefix = prefix.upcase if setting_upcase
373
+ def default_runtime_setting_prefix
374
+ prefix = root_name.gsub("/", runtime_setting_delimiter) + runtime_setting_delimiter
375
+ prefix = prefix.upcase if runtime_setting_upcase?
158
376
  prefix
159
377
  end
378
+
379
+ def construct_env_var(name, env_var)
380
+ return nil if env_var == false
381
+ return nil if environment_variables_disabled? && env_var.nil?
382
+
383
+ env_var = nil if env_var == true
384
+
385
+ if env_var.nil?
386
+ env_var = "#{env_var_prefix}#{name}"
387
+ env_var = env_var.upcase if env_var_upcase?
388
+ end
389
+
390
+ env_var
391
+ end
392
+
393
+ def construct_runtime_setting(name, runtime_setting)
394
+ return nil if runtime_setting == false
395
+ return nil if runtime_settings_disabled? && runtime_setting.nil?
396
+
397
+ runtime_setting = nil if runtime_setting == true
398
+
399
+ if runtime_setting.nil?
400
+ runtime_setting = "#{runtime_setting_prefix}#{name}"
401
+ runtime_setting = runtime_setting.upcase if runtime_setting_upcase?
402
+ end
403
+
404
+ runtime_setting
405
+ end
406
+
407
+ def construct_yaml_key(name, yaml_key)
408
+ return nil if yaml_key == false
409
+ return nil if yaml_config_disabled? && yaml_key.nil?
410
+
411
+ yaml_key = nil if yaml_key == true
412
+ yaml_key = name if yaml_key.nil?
413
+
414
+ yaml_key
415
+ end
160
416
  end
161
417
 
162
418
  def initialize
163
419
  @mutex = Mutex.new
164
420
  @memoized_values = {}
421
+ @override_values = {}
165
422
  end
166
423
 
167
424
  def [](name)
@@ -169,30 +426,60 @@ module UltraSettings
169
426
  end
170
427
 
171
428
  def include?(name)
172
- self.class.send(:defined_fields).include?(name.to_s)
429
+ self.class.include?(name.to_s)
173
430
  end
174
431
 
175
- private
432
+ def override!(values, &block)
433
+ save_val = @override_values[Thread.current.object_id]
176
434
 
177
- def __get_value__(name, static)
178
- if static && @memoized_values.include?(name)
179
- return @memoized_values[name]
435
+ temp_values = (save_val || {}).dup
436
+ values.each do |key, value|
437
+ temp_values[key.to_s] = value
438
+ end
439
+
440
+ begin
441
+ @mutex.synchronize do
442
+ @override_values[Thread.current.object_id] = temp_values
443
+ end
444
+ yield
445
+ ensure
446
+ @mutex.synchronize do
447
+ @override_values[Thread.current.object_id] = save_val
448
+ end
180
449
  end
450
+ end
181
451
 
452
+ def __source__(name)
453
+ field = self.class.send(:defined_fields)[name]
454
+ source = field.source(env: ENV, settings: UltraSettings.__runtime_settings__, yaml_config: __yaml_config__)
455
+ source || :default
456
+ end
457
+
458
+ private
459
+
460
+ def __get_value__(name)
182
461
  field = self.class.send(:defined_fields)[name]
183
462
  return nil unless field
184
463
 
185
- if !Rails.application.initialized? && !static
186
- raise UltraSettings::NonStaticValueError.new("Cannot access non-static field #{name} during initialization")
464
+ if field.static? && @memoized_values.include?(name)
465
+ return @memoized_values[name]
187
466
  end
188
467
 
189
- env = ENV unless self.class.environment_variables_disabled?
190
- settings = __runtime_settings__ unless static || self.class.runtime_settings_disabled?
191
- yaml_config = __yaml_config__ unless self.class.yaml_config_disabled?
468
+ if @override_values[Thread.current.object_id]&.include?(name)
469
+ value = field.coerce(@override_values[Thread.current.object_id][name])
470
+ else
471
+ env = ENV if field.env_var
472
+ settings = UltraSettings.__runtime_settings__ if field.runtime_setting
473
+ yaml_config = __yaml_config__ if field.yaml_key
192
474
 
193
- value = field.value(yaml_config: yaml_config, env: env, settings: settings)
475
+ value = field.value(yaml_config: yaml_config, env: env, settings: settings)
476
+ end
477
+
478
+ if __use_default?(value, field.default_if)
479
+ value = field.default
480
+ end
194
481
 
195
- if static
482
+ if field.static?
196
483
  @mutex.synchronize do
197
484
  if @memoized_values.include?(name)
198
485
  value = @memoized_values[name]
@@ -205,8 +492,20 @@ module UltraSettings
205
492
  value
206
493
  end
207
494
 
208
- def __runtime_settings__
209
- SuperSettings
495
+ def __use_default?(value, default_if)
496
+ return true if value.nil?
497
+
498
+ if default_if.is_a?(Proc)
499
+ default_if.call(value)
500
+ elsif default_if.is_a?(Symbol)
501
+ begin
502
+ send(default_if, value)
503
+ rescue NoMethodError
504
+ raise NoMethodError, "default_if method `#{default_if}' not defined for #{self.class.name}"
505
+ end
506
+ else
507
+ false
508
+ end
210
509
  end
211
510
 
212
511
  def __yaml_config__