Use instance_eval to clean up your API

I recently wrote a small application to parse Apache log files. The output is this context is not really important, but rather the API of the application that I wrote.

Two things were required to parse a log file:

  1. the log file format
  2. the location of the log file

I wanted the initialize the object with block notation. So starting off, this is what we wrote, which is faily typical in terms of most internal DSLs:

LogParser::Application.new do |p|
  p.format = "%V %h %l %u %t \"%r\" %s"
  p.log_file = '/apache_path/access_log'
end

This would then result in a constructor that looks something like this…

module LogParser
  class Application
    
    def initialize(&block)
      block.call(self) if block_given?
    end

  end
end

That’s perfectly acceptable and it will work just fine. But it’s not a winner in terms of elegance. Enter instance_eval.

Just like call, instance_eval will execute a block, but it will change the value of self as it executes the block.

So, to get an API that looks like this…

LogParser::Application.new do
  format "%V %h %l %u %t \"%r\" %s"
  log_file '/apache_path/access_log'
end

…we must change the constructor to look like this…

module LogParser
  class Application
    
    def initialize(&block)
      instance_eval(block) if block_given?
    end

  end
end

The example above has a few caveats though. In the first example, we are assigning values to format and log_file using setter methods: format= and log_file=. These can be created manually or by using attr_accessor.

In the second example, we are calling methods format and log_file. It’s a subtle difference but something that should be considered when developing your own API.

Here’s the final code for the Application class used above…

module LogParser
  class Application

    def initialize(&block)
      instance_eval(&block) if block_given?
    end

    def format regex_pattern
      @format = regex_pattern
    end

    def log_file filepath
      @log_file = filepath
    end

    # rest of code omitted

  end
end

Hope this helps.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s