h2. The Setup
Let’s say we have a system in production which manages college financial aid departments. Over the years it has accumulated a fairly complex object model for handling the many kinds of forms a financial aid department has to generate and process. It’s not the most elegant and well-factored model in the world, but it gets the job done.
Here are a few of the classes involved in the forms component of the system:
class Form < ActiveRecord::Base has_many :form_revisions # ... end class FormRevision < ActiveRecord::Base has_many :form_sections has_many :form_signoffs # ... end class FormSection < ActiveRecord::Base has_many :form_questions # ... end class FormQuestion < ActiveRecord::Base # ... end class FormSignoff < ActiveRecord::Base # ... end
h2. The Problem
One particular subsystem deals with student applications for financial aid. All of the application forms have a common essential format: a section listing acceptance criteria that must be met in order to qualify; a section listing exclusion criteria which might disqualify the student, and a field for the financial aid counselor to sign off that the student filled out the form correctly.
Currently whenever an administrator clicks the “new application form” button, the controller code which creates the new form does it something like this:
form = Form.create!(:name => form_name) first_version = FormRevision.create!(:form => form, :version => 1) acceptance_section = FormSection.create!(:name => "Acceptance Criteria") rejection_section = FormSection.create!(:name => "Rejection Criteria") first_version.form_sections < < acceptance_section first_version.form_sections << rejection_section first_version.form_signoffs.create!(:name => 'Counselor')
The process for adding a new acceptance or rejection criterion is similarly tedious:
form = Form.find_by_name(name) form_version = form.current_version section = form_version.sections.find_by_name('Acceptance Criteria') section.form_questions.create!(:text => question_text)
The tests for this logic (which is all contained in controllers) have to duplicate all of this setup in order to exercise the controllers with realistic data. Lately the devs have taken to using Factory Girl to make the setup easier, but it’s still a duplication, and it seems like there’s always some little detail of how the application code assembles a form that differs from how the test code does it. The other day one of the devs tried to debug one of these differences by manually assembling forms in the console, but he quickly got frustrated by all the steps necessary to get the form “just right”.
h2. Facade Methods
Clearly, there is an opportunity for simplification here. One option is to add some facade methods to the Form class which encapsulate the complexity of building application forms:
class Form def self.create_application_form! # ... end def add_criterion(question_text, kind=:acceptance) # ... end end
However, in our hypothetical financial aid system the Form class is already over 1000 lines long, and the developers have decided to draw a Picard Line on it. Not only that, but application forms are only one of many different kinds of forms that the system manages. If the Form class were to contain specialized code for every type of Form it manages, it would grow unmanageably large.
Subclassing is a possibility. But this system doesn’t use Rails Single Table Inheritance, so even if you saved an ApplicationForm it would come back as a plain Form next time you loaded it, and Ruby doesn’t provide any convenient way for us to downcast it to the correct type.
h2. Wrapper Facade
This is a situation where a Wrapper Facade may be called for.
(Note: I got the term form this paper by Doug Schmidt. I believe the pattern described here follows the spirit, if not the letter, of that work.)
It could look something like this:
require 'delegate' class ApplicationForm < DelegateClass(Form) def self.create! form = Form.create!(:name => form_name) first_version = FormRevision.create!(:form => form, :version => 1) acceptance_section = FormSection.create!(:name => "Acceptance Criteria") rejection_section = FormSection.create!(:name => "Rejection Criteria") first_version.form_sections < < acceptance_section first_version.form_sections << rejection_section first_version.form_signoffs.create!(:name => 'Counselor') self.new(form) end def add_criterion(question_text, kind=:acceptance) form_version = current_version # delegated to the underlying Form section_name = (kind == :acceptance) ? 'Acceptance Criteria' : 'Rejection Criteria' section = form_version.sections.find_by_name(section_name) section.form_questions.create!(:text => question_text) end end
Here we use the Ruby ‘delegate’ standard library to define a wrapper class which will delegate all undefined calls to an underlying Form instance.
Using the wrapper is straightforward, if slightly more verbose than using methods directly on the Form class:
# To create a form: form = ApplicationForm.create! form.add_criterion("Do you own an Escalade?", :rejection) # To modify a form: form = ApplicationForm.new(Form.find(form_id)) form.add_criterion("Can you count to ten without using your fingers?", :rejection)
h2. Advantages Over Other Approaches
Using a delegate class confers several useful advantages. For instance, it is very easy to construct unit tests that test just the functionality in the wrapper facade by mocking out the underlying Form instance.
describe ApplicationForm do before :each do @form = stub("form") @it = ApplicationForm.new(@form) end it "should be able to add eligibility sections to the form" do @form.should_receive(:current_version).and_return(stub("version")) # etc... @it.add_criterion("Test") end end
Using a wrapper instead of extending the @Form@ instance with a module means we can selectively override methods in the Form class if needed. Below, we override the @Form#name@ method with our own which appends some text to the name:
require 'delegate' class ApplicationForm < DelegateClass(Form) # ... def name __getobj__.name + " (Aid Application)" end end
Using a delegate also gives us our own namespace “sandbox” to play in. Any instance variables we use in implementing @ApplicationForm@ will be kept separate from the @Form@ instance variables, so we don’t have to worry about naming clashes.
And, of course, if we ever decide that some or all of the code in the wrapper does belong in the @Form@ class, it is simple enough to move it over.
To sum up, the Wrapper Facade is a useful tool to keep in your toolbox for situations where you want to simplify a particular scenario for a class, without adding any code to the class itself.