In Progress
Unit 1, Lesson 21
In Progress

Include Namespace Module

Video transcript & code

In the last episode, we took a look at how to organize generally useful utility methods into a module, using the module_function macro to expose them at both the module and instance level.

module ROFLWeb
  module ModelUtils
    module_function

    def unique_id(object)
      if object.respond_to?(:to_unique_id)
        object.to_unique_id
      else
        "#{object.class.name}+#{object.hash.to_s(16)}"
      end
    end
  end

  module UrlHelpers
    include ModelUtils
    # ...
  end

  class MemoryRepository
    include ModelUtils
    # ...
  end

end

One possible objection to this approach is that if there are only a few such "utility functions", creating a module just for them may seem like overkill. Another is that for a client coder, who is using our framework, to find these methods he or she has to dig a couple layers deep into our module hierarchy. If they find the methods at all, they might legitimately feel like they are depending on implementation details of our library.

In order to make these utility methods more accessible to client coders, we might move them to the outermost "namespace" module, like this:

module ROFLWeb
  module_function

  def unique_id(object)
    if object.respond_to?(:to_unique_id)
      object.to_unique_id
    else
      "#{object.class.name}+#{object.hash.to_s(16)}"
    end
  end
  # ...
end

Now client coders reading the documentation or code of our library can easily see these helper methods right at the top level. And through the magic of module_function, these methods are available both as included instance methods, and as fully-qualified module-level methods:

puts ROFLWeb.unique_id("Hello, world")
include ROFLWeb
puts unique_id("Hello, world")

But now our own internal classes and modules have no utility module to include!

…or do they? It may seem counter-intuitive, but in fact Ruby has no problem with including a "containing" module into a nested module or class.

module ROFLWeb
  module_function

  def unique_id(object)
    if object.respond_to?(:to_unique_id)
      object.to_unique_id
    else
      "#{object.class.name}+#{object.hash.to_s(16)}"
    end
  end

  module UrlHelpers
    include ROFLWeb
    # ...
  end

  class MemoryRepository
    include ROFLWeb
    # ...
  end

end

This isn't an excuse for filling a top-level namespace module with dozens of utility methods. But when there are only a few, this is a low-ceremony way to make them readily accessible to both internal code and client code alike.

That's all I have today. Happy hacking!

Responses