Factory Pattern – Ruby Style
Recently I was working on a framework for front end sites. The framework is packaged as a rails plugin, but unlike regular plugins it contains large blocks of logic. Framework models and controller classes are used to build a reusable abstraction layer above one particular API. A front end application then can subclass framework controller template classes to incorporate shared UI patterns in it’s own controllers. At the same time such applications introduce some variations in user experience. That’s the reason behind having many of them instead of one. And now I’m quizzed to find a nice way to make all the generic framework code play nicely with app specific extensions.
Monkey Patching
One possible way to add logic to library classes is to reopen class and add new methods directly, i.e. monkey-patch a class:
class UserTracking::SiteVisitor def initialize_with_fancy_links(request) initialize_without_fancy_links(request) @fancy_links = api_get_fancy_links(self.api_id) end alias_method_chain :initialize, :fancy_links def featured_links_with_fancy_links @fancy_links + featured_links_without_fancy_links end alias_method_chain :featured_links, :fancy_links end
This approach has some limitations. First, the default dependency loader doesn’t really know how to handle classes that are defined in several files. I had to add some explicit require statements in order to get properly loaded models. Second, I had to use alias_method_chain to add logic to methods defined in framework parts of classes. That sometimes produced rather ugly effects. Look how it affected constructor for example.
Injecting a Module
As a slightly modified version of previous approach, I was considering a style taken from active_support’s core extensions:
module SiteVisitorExtension def self.included(base) base.alias_method_chain :initialize, :fancy_links base.alias_method_chain :featured_links, :fancy_links base.send(:attr_accessor, :fancy_links) base.extend(ClassMethods) end def initialize_with_fancy_links(request) initialize_without_fancy_links(request) @fancy_links = api_get_fancy_links(self.api_id) end def featured_links_with_fancy_links @fancy_links + featured_links_without_fancy_links end module ClassMethods def find_in_cache(cookie_key) # ... end end end UserTracking::SiteVisitor.send(:include, SiteVisitorExtension)
It looks just a bit more clever, but adds extra syntax overhead for defining class methods or calling macro methods. Such things all go into def self.included and become messy with time.
So What’s The Problem?
But it wasn’t extra lines of code that kept me looking for other options. I didn’t like the fact that the inherent difference of one particular front end application, the very incentive to create one, was implemented by some kind of monkey patch. I think that described techniques are very good in their own domain. When, for example, one wants to add some formatting to log messages or stick caching into objects that should be unaware of the fact, these methods do their trick. But implementing parts of business logic by alias_method_chains is something I’d really like to avoid.
Subclassing
We don’t create application models by reopening ActiveRecord::Base, we subclass it. All the above examples get simplified dramatically if we use boring inheritance instead of fancy tricks with patching classes. Also, standard dependency loader works perfectly with such classes because they’re just plain regular.
class MySiteVisitor < UserTracking::SiteVisitor attr_accessor :fancy_links def initialize(request) super @fancy_links = api_get_fancy_links(self.api_id) end def featured_links @fancy_links + super end def self.find_in_cache(cookie_key) # ... end end
But can I easily use inheritance if framework creates instances of models inside itself? How can it know about my new shiny subclasses? People were facing such problems for quite a long time now. And for statically typed languages the answer always was often found near generative patterns or Factories. I used to think that in ruby I will never need a factory. Now I’m going to use this pattern and see how it works out.
Factory Module
Here’s a slightly adapted Factory pattern. I’m trying to be aware that I’m still using a highly dynamic language, and inventory of clever tricks is always at hand. But at the same time I don’t want to place any magic inside model classes – neither framework ancestors, nor app descendants. I mean that I don’t probably want to see some class BaseClass to magically appear as DescendantClass. Tricks like that can be fun but I’d like to see an explicit notion of whether framework code refers to some fixed class or is willing to instantiate it with the help of a factory.
A possible use case example may look like this:
framework code
# in user_tracking_plugin/app/models/user_tracking/site_visitor.rb class UserTracking::SiteVisitor def self.api_find_or_create_by_request(http_request) api_response = ... new(api_response) end def initialize(api_response) # ... end def featured_links # ... end end # in user_tracking_plugin/app/models/user_tracking/factory.rb module UserTracking create_factory end # in user_tracking_plugin/app/constrollers/user_tracking/controller_base.rb class UserTracking::ControllerBase before_filter :track_visitor protected def track_visitor @visitor = UserTracking::Factory::SiteVisitor.api_find_or_create_by_request(request) end end
application code
# in config/environment.rb config.after_initialize do UserTracking::Factory.add_mapping :site_visitor, :fancy_visitor end # in app/models/fancy_visitor.rb class FancyVisitor < UserTracking::SiteVisitor def initialize(request) super @fancy_links = another_api_get_very_special_links(self.api_id) end def featured_links @fancy_links + super # I love super end end
Another option for application code would be to place factory mappings setup in separate initializer instead of config.after_initialize block.
One important thing is to be aware how the factory module gets loaded. Standard class reloading mechanism that us used in development can drop all factory mappings. To avoid that rb file which creates factory module must be loaded explicitly (or just be placed in initializer) or reside under one of the load_once_paths (that is true for plugins by default).
Install
Implementation can be found on github. It is packaged as a separate plugin, so you can play with it by installing:
./script/plugin install git://github.com/bgipsy/factory_module.git
Additional details can be found by looking at spec/lib/proto_factory_spec.rb under the plugin tree.

Comments are closed for this entry.