This post is part of a series of reviews on the book Design Patterns in Ruby. Check out the Introduction post for a full table of contents along with some generic principles regarding Design Patterns.

Convention over configuration was a concept largely introduced by Ruby on Rails, and is certainly one of the key features of its success. The meaning behind it is simple: instead of working around some central piece of heavy configuration defined by the user, why not introduce a simple, default way to do things and put most of the configuration aside? Instead of supplying the users the ability to define a rulebook to your system, you define the rulebook when designing it, except that this rulebook is usually a set of stardands that people would follow anyway.

Obviously you can’t predict every single way people are gonna use your system, but you can predict the most simple, common cases and make those easy to work with. Of course, case a user wants something more complicated, than he can have a harder time figuring it out or configuring it himself. This concept is largely used in GUIs to favor user accessibility: in browsers you can easily mark some page as a favorite, but to, say, export those favorites to some file requires a bit more work.

There are a few guidelines that GUI designers follow to create easy-to-use interfaces, which the Convention Over Configuration pattern focuses on applying:

  • Anticipate needs: figure out what users do the most and make it the default way, the first paragraphs made this very clear.
  • Let them say it once: design a convention that lessens the need of repetition: when people tend to always do something in a specific way, that means something, usually that it’s the right way to go, so listen and don’t ask again.
  • Provide a template: conventions might be a bit overwhelming to a simple user who just wants to get started, so supply him something that he can get started and follow up on those conventions as he goes.

To see all this in action, let’s develop a small application following the convention over configuration principles. To keep it simple, we want to create reports and generate them in a certain format, but we also want to make the application extendable by easily allowing other formats in.

class Report

	def initialize(title)
		@title = title
	end

	def method_missing(name, *args)
		foo = name.to_s.split('_')
		super(name, *args) unless foo.shift == 'to'
		format_name = "#{foo.first.capitalize}Format"
		begin
			format_class = self.class.const_get(format_name)
			format_class.new.format(@title)
		rescue
			puts "Please define #{format_name} class."
		end
	end
	
end

We’re using the method_missing trick here to dynamically generate instances of formatting classes and calling the format method on them. So if I have an HtmlFormat class with a format method, to_html should make a call to it:

class HtmlFormat
	def format(title)
		puts "<html><body><h1>Header</h1>"
		puts "<p>#{title}</p>"
		puts "<div class='footer'>Footer</div></body></html>"
	end
end

report = Report.new('My Report')
puts report.to_html

As you can see, we’re defining a convention by saying that formatting classes should have the fortmat name followed by ‘Format’, and each of these classes should have a format method that returns a text formatted in its specific formatted name.
Notice how there’s no need to configure anything: we don’t have some file telling what formats are supported and which class those formats are in, by following a convention, all this is gracefully understood by our code.

Suppose we wanted to add even another format, as we’ve seen, it’s as easy as naming the class right an defining a single method:

class PlainFormat
	def format(title)
		puts "Header\n\n"
		puts "#{title}\n\n"
		puts "Footer"
	end
end

report = Report.new('My Report')
puts report.to_plain

Cool, we have all that functionality working, but its obviously not gonna be all in a single file, so we need to also organize our directory architecture by also defining a convention for it:

Now each format class should have it’s own filename and be under the formats/ directory. Notice how this convention we’re defining is something a good engineer would do anyway.
Having the directory setup, all we need is to load those files into our Report class so we don’t get requiring errors when instantiating them:

class Report

	def initialize(title)
		@title = title
		load_formats
	end

	def method_missing(name, *args)
		foo = name.to_s.split('_')
		super(name, *args) unless foo.shift == 'to'
		format_name = "#{foo.first.capitalize}Format"
		begin
			format_class = self.class.const_get(format_name)
			format_class.new.format(@title)
		rescue
			puts "Please define #{format_name} class."
		end
	end
	
	def load_formats
		dir = File.dirname(__FILE__)
		pattern = File.join(dir, 'formats', '*.rb')
		Dir.glob(pattern).each{|file| require file}
	end
	
end

Cool, we have our whole convention setup without requiring a single piece of configuration, but we’re still missing the guideline that states we must provide a template, so let’s do that. Suppose the user doesn’t know/care what the convention is and just wants to get started he could use a scaffolding feature, supply a format as an argument and start coding format logic. Here’s a really simple way to implement that:

format_name = ARGV[0]
class_name = format_name.capitalize + 'Format'

File.open(File.join('formats', format_name + '_format.rb'), 'w') do |f|
	f.write %Q!
class #{class_name}

	def format(title)
		#Code to format title
	end
end
	!
end

With this code in a file saved as format_scaffold.rb, a call like:

ruby format_scaffold.rb xml

Will generate an XmlFormat class with a format method ready to be edited.

As you might have noticed, this pattern, along with the DSL pattern, relies heavily on runtime evaluation of code and program introspection to work, both only possible due to Ruby’s dynamism.