Apr 17 2008

ruby application configuration settings

Category: Rubyetd @ 11:53 am

In this article I want to discuss a way of storing and retrieving the configuration settings of a ruby application. The first thing you need to decide is whether you want to store your settings in a database, a XML file, a YAML,…

Since this is not an easy choice we can mitigate the impact of making the decision upfront by doing some interface based design.

I am going to use dradis as an example, but the code and philosophy are project independent.

First we put an interface together with all the methods that our configuration handling implementation will require. The ParserInterface should be implemented by all the different configuration parsers (xml, yaml, etc.). The idea is that the application and it’s modules will only access methods defined in this interface:-

module ParserInterface
  # Given an option name (as a symbol) this function
  # retrieves the value stored in the configuration
  # file for it.
  def get_option(key)
    raise 'unimplemented!'
  end

  # Store the given +value+ under the +key+ in the config
  # provider.
  def put_option(key, value)
    raise 'unimplemented!'
  end

  # Store the configuration settings in the backend
  # provider.
  def save
    raise 'unimplemented!'
  end
end

We have defined methods for storing, retrieving and saving the configuration. It is true that the save may not be necessary if the implementation stores information in, for example, a database, however the advantage of having this method is that we are allowing our implementation to have a cached copy of the configuration settings in memory and only write them to the backend once the save method is called.

Ruby does not have native support for interfaces, this is why the ParserInterface is a ruby module and only defines methods that raise exceptions. We will include this module in our implementations, in doing so, the implementing classes will respond to the get_option, put_option and save methods straight away, but if the application calls any of them an exception will be thrown unless a valid implementation has been provided.

For dradis we are using an XML file as the backend configuration storage. The XMLParser class. The full contents on the file can be accessed through the subversion repository in: /dradis/client/branches/orko2.0-etd/core/config.rb. I have included below the interesting bits and pieces:-

# to handle the XML part of it
require 'rexml/document'

class XMLParser
  # include the interface
  include ParserInterface

#[...]

# copy from the file into a hash in memory (initialize)
    @src = REXML::Document.new(File.new(@file))
    @options = {}
    @src.elements.each('dradis/option') do |element|
      @options[element.attributes['name'].to_sym] = element.attributes['value']
    end

# get_option and put_option are straightforward

# [...]

  def save
    # if no change has been made to the configuration, do
    # not bother overwriting the file
    return unless @modified
    @options.each do |name, value|
      elements = @src.get_elements("dradis/option[@name='#{name}']")
      if (elements.size.zero?)
        # a new element needs to be created
        @src.root.add_element( 'option', { 'name' => name, 'value' => value})
      else
        # update the existing element
        elements.first.attributes['value'] = value
      end
    end

    # use the Pretty formater to indent the file :)
    fmt = REXML::Formatters::Pretty.new(2)
    fmt.write( @src, File.new(@file, 'w') )
  end

# [...]
end # class

The only performance trick that I am using is the @modified variable that flags whether a change has been made to the configuration settings during the current session or not. If no change was made, there is no need to dump the @options hash back into the file.

Share and Enjoy: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • del.icio.us
  • Slashdot
  • Technorati