Home
Head's Up: I'm in the middle of upgrading my site. Most things are in place, but there are something missing and/or broken including image alt text. Please bear with me while I'm getting things fixed.

Configuration Files and Multiple Ruby Object Constructors

I mostly write command line apps. Mostly. It's the nature of the gig, even though I work on a relatively big website 1 . There are lots of moving parts, many of which are well suited for automation, that combine to produce the overall site. As someone who likes avoiding hard - coded variables, I use configuration files to tell my various apps/scripts/services/software what to do.

The simplest way to instantiate a Ruby object with parameters (like a config file path) is to send them directly to the [TODO: Code shorthand span ] method. For example 2 :

class Robot

  attr_reader :config

  def initialize(config_path)
    @config = parse_config(config_path)
  end
  
  def parse_config(config_path)
    YAML.load(ERB.new(File.read(config_path)).result)
  end
end

robot = Robot.new("~/robot-config.yml"))

That approach works but makes testing problematic. Unit tests for individual method can't be run without loading a config file. An undesirable tight coupling is created. If the object validates the config, changes to its format can trigger multiple, otherwise unrelated tests. A strong "Shotgun Surgery" Code Smell 3 indicating a great refactoring opportunity.

My first attempt at a remedy was to make the config file optional. For example :

class Robot

  attr_reader :config

  def initialize(config_path = nil)
    if config_path
      @config = parse_config(config_path)
    else 
      @config = {}
    end
  end
  
  def parse_config(config_path)
    YAML.load(ERB.new(File.read(config_path)).result)
  end
end

robot = Robot.new("~/robot-config.yml"))

robot_for_unit_tests = Robot.new

That works, but feels rough. Conditional logic has become a red flag ever since attending Sandi Metz's wonderful Practical Object - Oriented Development (POOD) course 4. Also, having recently kicked around with The Big Nerd Ranch's Objective-C book5, I'd just seen examples of using multiple object constructors. After some investigation6 , experiments, and hacking, I ended up with this approach :

class Robot

  attr_reader :config

  def initialize
    @config = {} # defaults can be applied here too.
  end
  
  def initialize_with_config config_path
    initialize
    @config = parse_config(config_path)
  end
  
  def self.new_with_config config_path
    forerunner = allocate
    forerunner.send(:initialize_with_config, config_path)
    forerunner  
  end
  
  def parse_config(config_path)
    YAML.load(ERB.new(File.read(config_path)).result)
  end
end

The primary way to instantiate objects changes from :

robot = Robot.new("~/robot-config.yml"))

To use the newly created class method :

robot = Robot.new_with_config("~/robot-config.yml"))

The [TODO: Code shorthand span ] class method bounces to the [TODO: Code shorthand span ] object method which in turn calls the default [TODO: Code shorthand span ] object method before doing its work.

Unit tests can now use the built - in [TODO: Code shorthand span ] without params or worrying about a config file. An added bonus is the nice way this separates setting defaults (and any other initialization requirements) from loading the config.

This approach isn't limited to config files. It works any time there's a need to create objects with different parameters/options from a single class. The few extra extra lines for the class methods are totally worth the clean separation provided by multiple constructors.

_ If you're interested in more details on how this works, check out Ruby Constructors](http://www.verygoodindicators.com/blog/2015/03/15/ruby-contructors/) from [@terminalbreaker . _

_ Footnotes _

1. Well, I really work on a few big sites : PGATOUR.COM](http://www.pgatour.com), [PresidentsCup.com](http://www.presidentscup.com), and [WorldGolfChampionships.com but they are all directly related and powered by the same tools.

2. The examples in this post contain working code with two caveats. First, they need [TODO: Code shorthand span ] and ` require 'yaml' ` . Those lines were omitted to save a little vertical height. And, of course, a config file sitting at ` ~/robot - config.yml ` . I also used a minimal approach with no error handling to keep the size down. So, while they work, they are only proof - of - concept examples.

3. The Wikipedia Shotgun Surgery is a pretty formal. The simplified way I've heard it described and think about it is : An update in one place that requires modifying multiple classes/methods/functions in other places.

4. I can't recommend Sandi's Practical Object-Oriented Design Course

5. I haven't read any other Objective - C books. So, I don't have a basis for comparison, but [Objective - C Programming : The Big Nerd Ranch Guide](https : //www.bignerdranch.com/we - write/objective - c - programming/) seems like a decent intro. More than anything, it makes me want to attend a course at the ranch.

6. This was a rare case where I wasn't able to score a hit on StackOverflow. I found the solution in Ruby Constructors](http://www.verygoodindicators.com/blog/2015/03/15/ruby-contructors/) and the related [Constructors, Intro to Ruby Classes Part II slides](http://www.slideshare.net/ciscoleal/constructors-28459017). Both by [Juan '@terminalbreaker' Leal .

_ P.S. Yes, I know making two calls works just fine. Something like : _

robot = Robot.new
robot.prase_config("~/robot-config.yml"))

_ But since production scripts require the config, I prefer to make it happen in one line. _