Ruby’s meta-programming allows us to avoid boilerplate code and
duplication by relying on conventions for class names, file names and
directory structure. Although depending on class names can be
constricting in some situations, careful use of conventions will make
your applications less tedious and more bug-proof.
The controller is manually mapping a given strategy name to an object
that can perform the strategy with the given name. In most cases, a
strategy name directly maps to a class of the same name.
We can use the constantize method from Rails to retrieve
a class by name:
This will find the MostRecent class from the string
"most_recent", and so on. This means we can rely on a
convention for our summarizer strategies: Each named strategy will map
to a class implementing that strategy. The controller can use the class as
an abstract factory and obtain a summarizer.
However, we can’t immediately start using constantize in
our example, because there’s one outlier case: The
UserAnswer class is referenced using
"your_answers" instead of "user_answer", and
UserAnswer takes different parameters than the other two
Before refactoring the code to rely on our new convention, let’s
refactor to obey it. All our names should map directly to class names
and each class should accept the same parameters:
Conventions are most valuable when they’re completely consistent.
The convention is slightly forced in this case because
UserAnswer needs different parameters than the other two
strategies. This means that we now need to add no-op
initializer methods to the other two classes:
This isn’t a deal-breaker, but it makes the other classes a little
noisier and adds the risk that a developer will waste time trying to
remove the unused parameter.
Every compromise made weakens the convention, and having a weak
convention is worse than having no convention. If you have to change the
convention for every class you add that follows it, try something
Another drawback to this solution is that it’s entirely class-based,
which means you can’t assemble strategies at run-time. This means that
reuse requires inheritance.
Also, while this class-based approach is convenient when developing
an application, it’s more likely to cause frustration when writing a
library. Forcing developers to pass a class name instead of an object
limits the amount of runtime information strategies can use. In our
example, only a user was required. When you control both
sides of the API, it’s fine to assume that this is safe. When writing a
library that will interface with other developers’ applications, it’s
better not to rely on class names.
The canonical reference for writing fantastic Rails applications from authors who have created hundreds.