On Module Integrity

Goodness, such strong but mixed feelings provoked by this article: Modules called, they want their integrity back. On the one hand: yes! But on the other: no!

Having used Ruby for upwards of ten years, I have no trouble thinking of #include (and its callback partner-in-crime #included) as just another method. To some degree I think it’s bringing biases in from less dynamic languages to expect Ruby built-in methods like #include to behave like immutable keywords.

To that end, I think I have a looser idea of the “correct” semantics of #include than Josh Cheek does. #include / #included includes new functionality into a class (or module). How they do that is typically to add the referenced module to the inheritance chain and copy in constants. But I have no fundamental problem with more exotic methods of bringing in new functionality.

On the other hand, I do have a slight problem with adding zillions of public class methods in order to establish a some kind of declarative mini-language at class level; methods which then overstay their welcome and clutter up the class API. A chief offender is my usual punching-bag, ActiveRecord:

Holy mackeral! A whopping 328 methods added to the public class API! And that’s calculated after taking into account all the methods which ActiveSupport adds to every class in the system; before requiring active_record the public class method count is 97.

Most of those methods are declarative “macros” which will only ever be used in the class definition: methods like has_many, belongs_to, and validates. But they linger around long after the class is fully defined, like that guy who’s still snoring on your couch at noon the day after a party.

I’ve started to experiment with alternatives to littering classes with “construction macros” which stick around after they have outlived their usefulness. Here’s one approach I’ve been playing with:

At the risk of being a little bit more verbose, this pattern controls the context in which the additional “macro” methods are active. As an added bonus, it gives the implementor a convenient point, at the end of the block execution, to do any post-processing of the data structures that were set up by the builder macros.

Some of Josh’s alternatives reminded me of these experiments. I definitely think we share some concerns, although I think we’re coming at it from slightly different angles. For me, it’s more about avoiding namespace pollution: staying out of the global namespace, avoiding adding methods to base classes (cough acts_as_* cough), and not cluttering up classes with methods which serve no purpose once the class setup is finished.