2.1 Named Initializers

Going in blind

You'll often see Ruby code which looks something like this: ErrorMessage.new(:message => "I'm afraid I can't let you do that, Dave.", :status => NOT_AUTHORIZED). Some languages, such as Smalltalk and Python, have what are known as "named parameters", which allow a function call to name each parameter passed in. Ruby does not have named parameters but, as in our ErrorMessage example, we can achieve something similar through hashes.

Let's start with the concept, sans metaprogramming magic. Write a constructor for ErrorMessage which accepts a named hash for initializing @message, @status, and @suggestion.

Hint

Output Window

Ah, what a lovely, readable constructor. Now that we know what it looks like on the outside (which is always an important first step -- understand how an object or method will look through its interface before worrying about how it's implemented), we can use send or instance_variable_set to make the insides more concise. We can also abstract this concept out into a module at the same time so we can use this in multiple classes. Give that a shot: blindly assign to instance variables using the keys of the hash with instance_variable_set.

Hint

Output Window

This example demonstrates one concrete problem with this approach: @boondoggle was never meant to exist! But because we're using instance_variable_set, it's created anyway. Let's see how this situation changes when we use send:

Hint

Output Window

Salvation! We've prevented ourselves from setting the broken / nonexistent instance variable. This solution is certainly better but one problem remains: What if we want to set an instance variable for which there is no setter (as was created by attr_accessor)? Sometimes we want a constructor which sets state which is purely internal; it can't be changed, nor can it be accessed directly by the outside world. For that sort of constructor we'll need to get a little fancier.

See what you're made of

Take the idea from the prior section and expand on it by making it declarative. That is to say, we won't just include a mixin which creates the constructor automatically -- the mixin will include a declarative method which itself defines the constructor.

Define the mixin below, using Cheese as an example of how it will be consumed to test out your implementation.

Hint

Output Window

Here we're starting to really see the power of Ruby. We've created a declarative method as a one-line abstraction -- and it can be used in all the classes in our program. Even the implementation of this abstraction is only 7 lines of code. Impressive! Some very large Ruby projects have used this very abstraction to reduce code across multiple classes.

Experiment with this method yourself. You can try tweaking what it does: have hash_initialized create accessors, allow undeclared fields but throw them away, perform some additional setup specific to your program based on all the fields supplied.