Class: ConfigToolkit::BaseConfig

Inherits:
Object
  • Object
show all
Defined in:
lib/configtoolkit/baseconfig.rb

Overview

This class can be subclassed to create a class representing a configuration. It provides configuration specification, loading, and dumping functionality. A BaseConfig configuration can contain “scalar” parameters (anything except an array or nested configuration class), ConstrainedArray parameters (arrays), and other BaseConfig child classes parameters (which can be thought of as hashes). Note that nested ConstrainedArray and BaseConfig config parameters recursively can contain anything the parent BaseConfig can contain, so nesting arrays and configuration classes to any depth is supported.

A BaseConfig child instance has getter, setter, and predicate methods for each parameter. The predicate method for a parameter returns whether or not the parameter has a value and is named param_name?. An optional parameter also has a method to clear its value from the configuration (after this the predicate method will return false); the clear method is named clear_param_name.

BaseConfig neither parses from nor writes to configuration files directly but instead does so through reader (Reader) and writer (Writer) classes specified by the programmer. This allows BaseConfig to support virtually any underlying configuration file format (including YAML, key/value pairs, etc). BaseConfig expects reader classes to contain a read method that sources configuration information and returns a Hash that maps Symbols or Strings (parameter names) to values, which can themselves be Hashes or Arrays. It expects writer classes to support a write method that in turn accepts such a Hash and writes it to some underlying stream. The reader and writer implementations used have no effect on BaseConfig’s validation functionality; data from any source (even specified programatically via setters) always is validated fully.

Programmers wishing to create their own configuration classes should extend BaseConfig and, in the body of their class’ definition, call add_required_param and add_optional_param for each parameter they wish to specify. If they wish to enforce a relationship between parameters, they also can override validate_all_values.

Direct Known Subclasses

ConfigToolkitConfig, KeyValueConfig

Defined Under Namespace

Classes: ParamSpec

Constant Summary collapse

NO_DEFAULT_VALUE =

This constant can be passed to add_optional_param in order to indicate that the parameter has no default value. The actual value of the constant does not matter in the least, so long as it never could be a real default value (a gemsym() function would be handy here to generate a unique symbol).

:baseconfig_no_default_value

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config_toolkit_config = nil, &initialization_block) ⇒ BaseConfig

Description:

This constructs a BaseConfig with no parameters set (each parameter initially has a nil value).

If a block is specified, then it will be passed the new instance and should set the BaseConfig’s parameters fully (as if load had been called); the BaseConfig’s specifications will be verified by this method after it executes the block (with a call to enforce_specs).

Example:

class Config < ConfigToolkit::BaseConfig
  add_required_param(:age, Fixnum)
end

empty_config = Config.new()

initialized_config = Config.new() do |config|
  config.age = 5
end

# This will raise an exception, because the age parameter is
# required but is not being set by the block passed to new.
initalized_config = Config.new() do |config|
end

Parameters:

config_toolkit_config

An optional ConfigToolkitConfig instance to configure the new configuration instance (this allows the loading/dumping process to be customized). If this parameter is not specified by the caller, then the value of BaseConfig.default_config_toolkit_config will be used.

&initialization_block

A block that fully initializes the parameters of the new instance



520
521
522
523
524
525
526
527
528
529
530
531
# File 'lib/configtoolkit/baseconfig.rb', line 520

def initialize(config_toolkit_config = nil,
               &initialization_block)
  @containing_object_name = ""
  @config_toolkit_config = config_toolkit_config

  clear_all_values()

  if(block_given?())
    yield self
    enforce_specs()
  end
end

Class Attribute Details

.param_spec_listObject (readonly)

This is an Array (class instance variable) containing the parameter specifications (ParamSpecs) in the order in which they were specified in the BaseConfig child class.



139
140
141
# File 'lib/configtoolkit/baseconfig.rb', line 139

def param_spec_list
  @param_spec_list
end

.param_spec_lookup_tableObject (readonly)

This is a Hash (class instance variable) mapping paramater names (Symbols) to parameter specifications (ParamSpecs)



145
146
147
# File 'lib/configtoolkit/baseconfig.rb', line 145

def param_spec_lookup_table
  @param_spec_lookup_table
end

Instance Attribute Details

#containing_object_nameObject (readonly)

This is a String containing the name of the object that contains this instance’s configuration. For example, if all of this instance’s configuration is under production and webserver in the configuration file, then the containing_object_name would be production.webserver.



481
482
483
# File 'lib/configtoolkit/baseconfig.rb', line 481

def containing_object_name
  @containing_object_name
end

Class Method Details

.inherited(child_class) ⇒ Object

Description:

This is BaseConfig’s implementation of the inherited hook, which is called by the Ruby interpreter whenever BaseConfig is extended. This function initializes the class instance variables of the new child class.

Parameters:

child_class

The new child class



159
160
161
162
163
164
165
166
167
168
# File 'lib/configtoolkit/baseconfig.rb', line 159

def self.inherited(child_class) # :nodoc:
  #
  # Initialize the class instance variables.
  #
  child_class.class_eval do
    @param_spec_list = []
    @param_spec_lookup_table = {}
    @default_config_toolkit_config = nil
  end
end

.load(reader, containing_object_name = "", config_toolkit_config = nil) ⇒ Object

Description:

This class method creates a new config instance and calls load on it with the specified arguments. This is a second “constructor” for the class.

Parameters:

See the parameter list for load.

config_toolkit_config

An optional ConfigToolkitConfig instance to configure the new configuration instance (this allows the loading/dumping process to be customized). If this parameter is not specified by the caller, then the value of BaseConfig.default_config_toolkit_config will be used.

Returns:

The new instance, preloaded!



1132
1133
1134
1135
1136
# File 'lib/configtoolkit/baseconfig.rb', line 1132

def self.load(reader, containing_object_name = "", config_toolkit_config = nil)
  instance = new(config_toolkit_config)
  instance.load(reader, containing_object_name)
  return instance
end

.load_group(reader, containing_object_name = "", config_toolkit_config = nil) ⇒ Object

Description:

This class method loads arbitrarily many configs from reader. This should be used when each of containing_object_name‘s elements is a different instance of this class’ configuration. This call returns a Hash containing elements mapping configuration names (Symbols) to configuration instance.

Parameters:

See the parameter list for load.

config_toolkit_config

An optional ConfigToolkitConfig instance to configure the new configuration instance (this allows the loading/dumping process to be customized). If this parameter is not specified by the caller, then the value of BaseConfig.default_config_toolkit_config will be used.

Returns:

A Hash of names (Symbols) mapping to loaded configuration instances



1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
# File 'lib/configtoolkit/baseconfig.rb', line 1158

def self.load_group(reader, containing_object_name = "", config_toolkit_config = nil)
  containing_object_hash = load_containing_object_hash(reader, containing_object_name)

  config_group = {}
  if(containing_object_hash != nil)
    containing_object_hash.each do |key, value|
      value_containing_object_name = "#{containing_object_name}.#{key}"

      if(!value.is_a?(Hash))
        message =  "error: #{self}.load_group found "
        message << "#{value.class} "
        message << "rather than Hash when reading the "
        message << "#{value_containing_object_name} containing object"
        raise Error, message
      end

      instance = new(config_toolkit_config)

      #
      # Have to use send in order to call private method.
      # I think that not being able to call private instance methods
      # from class methods is a bug.
      #
      instance.send(:load_impl, value, value_containing_object_name)
      config_group[key.to_sym()] = instance
    end
  end

  return config_group
end

Instance Method Details

#==(rhs) ⇒ Object

Description:

Equality operator for BaseConfig; this method iterates through the configuration’s parameters and, for each parameter, compares the value of self and rhs using the value’s equality operator.

Parameters:

rhs

The instance to compare with self

Returns:

True if and only if the parameter values of self and rhs are equal



679
680
681
682
683
684
685
686
687
688
689
690
691
# File 'lib/configtoolkit/baseconfig.rb', line 679

def ==(rhs)
  if(rhs == nil)
    return false
  end
  
  self.class.param_spec_list.each do |param_spec|
    if(send(param_spec.name) != rhs.send(param_spec.name))
      return false
    end
  end

  return true
end

#dump(writer) ⇒ Object

Description:

This method writes self to a configuration file. It does this by passing the contents of self to writer. That is, it constructs a Hash containing entries for the parameters and passes the Hash to the write method of writer, which should write the information to some underlying medium (most likely writing a configuration file). The method checks the configuration against its specifications (via a call to enforce_specs) before dumping anything.

Parameters:

writer

The writer to which to dump (see Writer)



790
791
792
793
794
795
796
797
798
799
800
801
802
# File 'lib/configtoolkit/baseconfig.rb', line 790

def dump(writer)
  #
  # If the require_symbol_parameter_names? method returns true,
  # then Symbol parameter names will be passed to the writer;
  # otherwise, String parameter names will be passed to the writer.
  #
  use_symbol_parameter_names = writer.require_symbol_parameter_names?()
  
  config_hash = {}
  dump_impl(config_hash, use_symbol_parameter_names)

  writer.write(config_hash, @containing_object_name)
end

#enforce_specsObject

Description:

This method enforces the BaseConfig’s specifications. In particular, it:

  • Checks that all required parameters have been set. If a required parameter is missing, then this method raises an Error.

  • Sets all optional parameters without values and with default values to their default values.

  • Calls validate_all_values.

This method is called at the end of a load operation (verifying that a valid configuration was loaded from a reader) and before a dump operation (verifying that a valid configuration will be dumped).



596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
# File 'lib/configtoolkit/baseconfig.rb', line 596

def enforce_specs
  #
  # Iterate through the parameters without values.  If any are
  # required parameters, then raise (after completing the
  # iteration in order to get the full list of missing
  # parameters).  Otherwise, set all optional, missing
  # parameters to their default values.
  #
  missing_params = []

  self.class.param_spec_list.each do |param_spec|
    if(!has_value(param_spec.name))
      if(param_spec.required?)
        missing_params.push(param_spec.name)
      elsif(param_spec.default_value?)
        #
        # Even the default values are set through the setter.
        #
        send("#{param_spec.name}=", param_spec.default_value)
      end
    end
  end

  if(!missing_params.empty?)
    if(@containing_object_name.empty?)
      param_prefix = ""
    else
      param_prefix = "#{@containing_object_name}."
    end

    missing_params_str = missing_params.map do |param_name|
      "#{param_prefix}#{param_name}"
    end.join(", ")

    raise Error, "missing parameter(s): #{missing_params_str}"
  end

  begin
    validate_all_values()
  rescue Error => e
    #
    # Rescue the error, add some information to the error message, and
    # throw a repackaged error.
    #
    message = "#{self.class}#validate_all_values error"
    
    if(!@containing_object_name.empty?)
      message << " for #{@containing_object_name}"
    end
    
    message << ": #{e.message}"
    raise Error, message, e.backtrace()
  end
end

#load(reader, containing_object_name = "") ⇒ Object

Description:

This method loads the contents of a configuration file into self. It loads self from reader (with containing object containing_object_name), deleting all prior parameter values.

Parameters:

reader

The reader (see Reader) from which to load the parameter values. This method will call the read method of reader, which will return a Hash containing, in the most nested containing object entry, the configuration parameter values for self.

containing_object_name

The containing object name



1104
1105
1106
1107
1108
1109
1110
1111
1112
# File 'lib/configtoolkit/baseconfig.rb', line 1104

def load(reader, containing_object_name = "")
  #
  # Not being able to call private class methods from one of the
  # class' instance methods is a language bug.
  #
  containing_object_hash = self.class.send(:load_containing_object_hash, reader, containing_object_name)

  load_impl(containing_object_hash, containing_object_name)
end

#raise_error(reason) ⇒ Object

Description:

This method raises an Error with message reason. It is meant to be called from user-defined parameter validation blocks (see the arguments to add_param). This is a Hotel California type of call; it does not return.

Parameters:

reason

The reason for the error

Raises:



662
663
664
# File 'lib/configtoolkit/baseconfig.rb', line 662

def raise_error(reason)
  raise Error, reason, caller()
end

#to_sObject

Returns:

String representation of self



1193
1194
1195
1196
1197
1198
1199
1200
# File 'lib/configtoolkit/baseconfig.rb', line 1193

def to_s
  string_stream = StringIO.new()

  writer = PrettyPrintWriter.new(string_stream)
  dump(writer)
  
  return string_stream.string()
end

#validate_all_valuesObject

Description:

This method enforces constraints between parameters; unless overriden, it is a no-op. It is called after all values have been loaded during a load operation or at the start of a dump operation. If a child class wishes to enforce a particular constraint, it should re-implement this method; if a constraint is violated, the method should call raise_error in order to raise an Error.



1211
1212
1213
# File 'lib/configtoolkit/baseconfig.rb', line 1211

def validate_all_values
  # This must be re-implemented in child classes in order to do anything
end