Ruby modules, the template pattern, and dynamic closure application

This post demonstrates how to apply a set of closures to various components (i.e. Ruby on Rails controllers) in a very flexible, indirect, and dynamic fashion. The final result involves using modules to implement a template pattern and invoking closures bound to new contexts.

Abusing Kernel#caller (remembering Java Reflection)

I recently had an issue where I needed to know the caller of a method. The goal was to apply one or more closures cached in a singleton configuration object to a controller or other component. I was originally on the right track, but I got sidetracked by a google search. I was reminded of Kernel#caller. The mess that ensued looked like this:

 1 require 'active_support/inflector'
 2 
 3 module Arbitrary2
 4   class MyController
 5 
 6     def some_action
 7       NeedsToKnowCaller.apply_config
 8     end
 9 
10   end
11 end
12 
13 module NeedsToKnowCaller
14 
15   SIG = /controllers\/([^\.]*).*`([^']*)'/
16 
17   class << self
18 
19     # overriding Kernel#caller only for sake of repeatability on blog
20     # during normal use this override would not exist
21     def caller
22       ["/arbitrary/project/app/controllers/arbitrary2/my_controller.rb:20:in `some_action'"]
23     end
24 
25     # extract signature for use elsewhere...
26     # dont want to explicitly declare caller as a param but could...
27     def apply_config
28       md = caller[0].match(SIG)
29       sig = "#{md[1].classify}.#{md[2]}"
30       # could do something with eval here... timeout...
31       puts sig
32     end
33 
34   end
35 end
36 
37 Arbitrary2::MyController.new.some_action
38 # printed value is "Arbitrary2::MyController.some_action"

What happens here is that some_action is called in an instance of MyController. This calls apply_config in the NeedsToKnowCaller module which is able to look up the class that called it. This implementation is left incomplete. It is not at all a dead end, but it is an expensive and unnecessary bad road to continue down.

Ruby Modules and the Template Design Pattern

As soon as I was finished tracking down the caller I remembered what I was really trying to do in the first place. I did not just need the name of the caller. I needed the context of the caller as well so that I could invoke a closure in the context of the caller. The solution above is terrible. I blame my history with Java reflection for this brief lapse of judgement. For anyone looking up Kernel#caller or a regex to be used with it, please stop right here. Ruby has better options in the form of modules.

Modules can be used for much more than just bags of functions or namespaces. The template pattern typically looks a little different than this, but that is essentially what I ended up reinventing. Instead of using inheritance, I ended up using mixins in a slightly different way than I usually do.

 1 module NeedsToKnowCaller
 2 
 3   class << self
 4 
 5     # actual impl much more complex...
 6     def config(key)
 7       # hard coded for clarity
 8       -> { root_path }
 9     end
10 
11     def apply_config_to _caller, key
12       _caller.instance_exec &config(key)
13     end
14 
15   end
16 
17   module ControllerMethods
18 
19     # dont want to explicitly declare caller as a param but could...
20     def apply_config(key)
21        NeedsToKnowCaller.apply_config_to self, key
22     end
23 
24   end
25 
26 end
27 
28 module Arbitrary2
29   class MyController
30     include NeedsToKnowCaller::ControllerMethods
31 
32     def some_action
33       apply_config :varies
34     end
35 
36     def root_path
37       "would normally come from rails routing"
38     end
39   end
40 end
41 
42 Arbitrary2::MyController.new.some_action
43 # => "would normally come from rails routing" 

In this example, any controller that includes NeedsToKnowCaller::ControllerMethods will be able to call NeedsToKnowCaller.apply_config_to(self) with just apply_config. Apply_config_to will then call config which returns a lambda which is invoked in the context of the instance that was passed into apply_config_to. So an instance of MyController calls apply_config which indirectly calls root_path. The config method in my actual implementation is much more involved.

This example might suffer from oversimplification. In the context of a large project, small details like cutting a derivable parameter out of a method can be really helpful. In a small project this wouldn't make much sense.

What I do find interesting is how well Ruby facilitates some older design patterns. I learned the template pattern with Java. In my experience it always looks the same in Java. A subclass adds something around a superclass method. Here a collection of semi-related classes call another singleton class that is not inherited at all. The idea is the same. The implementation is totally different. Furthermore, the ease with which closures can be reapplied to other contexts is extremely powerful.

comments powered by Disqus