Recently, I developed a configuration DSL that would populate a database. I
found myself writing a lot of repetitive code to handle the DSL syntax, and
thought that this would be a good case to programatically generate the methods.

The basic syntax was of the form:

1
2
3
4
5
6

deviceDeviceClassdodevice_string_attribute"meaning of life"device_integer_attribute42device_boolean_attributetrue...end

One of the requirements was to throw an error if the wrong type of argument
was passed as a parameter. The initial approach was to add conditions to check
for the type and range, but I found this to be extremely repetitive. Enter
metaprogramming. This allowed me to generate a base function template, and
automatically generate the code for that function based on some parameters.
The C language has a very similar feature, where I’d define a repetitive block
as a macro and call that macro multiple times with different parameters.

Obviously, the above snippet is a contrived example, but it serves to give you
an idea of how to do something like this in C (the backslashes serve to continue
the macro definition).

Ruby has the fantastic define_method. With it, I could do exactly what I had
done in the C instance.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

classDeviceClassdefself.make_attribute(name,accept_class,accept_range)define_functionname.to_sdo|arg|ifaccept_classandnotarg.is_a?accept_classraise"Argument is of the wrong type (expected #{accept_class})"endifaccept_rangeandnotaccept_range.include?argraise"Argument out of range (expected #{accept_range})"endinstance_variable_set("@#{name}",arg)endendmake_attribute(:device_string_attribute,String,nil)make_attribute(:device_integer_attribute,Integer,(0..100))make_attribute(:device_boolean_attribute,nil,[true,false])end

The accept_class and accept_range parameters serve to define the accepted type
and range of values that can be set in the DSL. The only limitation is that
since Ruby doesn’t have an explicit class for Boolean data types, we are limited
to verifying that the user entered true or false, by checking the range.

Certainly, this was a limited example, and it does hamper performance slightly
due to the multiple checks, but it serves to demonstrate how you can follow the
DRY principle and make a lot of code that is mostly common but differs in subtle
ways.