From Decorator to Interior Designer: Advanced Decorating.
Prelude
Recently as part of my work, one of the tasks I had to do was to normalize the 'displayable' output for multiple bodies/models across the system, that were being output to the view, through JSON responses, Official printed documents, as well as sent over to official government bodies through online applications. All the bodies had a xxx_name
and xxx_code
attribute, that were often being output together. Due to the previous working style being fairly split up and the lack of a common agreed format within that team, each body was being displayed in its own unique (although sometimes fairly similar) way. So I was brought in to overhaul many aspects of the system, DRY it up, as well as teach the team to work together, and to think outside of their own personal task scope, to create DRY'er team friendly code. This is my second blog on Decorating the right way, building from my the first one with some slightly more advanced concepts. If you haven't read my first blog about it, check it out.
The Problem Space
This task was a huge one, it involved overhauls of nearly every part of the system, from CSS and JS to Models and core businiess logic, however the purpose of this blog is to build on my previous decorator blog's concepts, and show some slightly more advanced decorating concepts with Bottled Decorators using the small and specific example of normalizing the name/code outputs explained in the prelude. This is actually partly what inspired me to create my bottled_decorators gem.
So the basic idea was that there are two formats eventually decided on for outputting the information. The first was code:name
, and the second, when displaying the other way around was name(code)
. A simple enough task. There were quite a few models that would use this format, but for the sake of brevity lets whittle it down to four:
- Office
- Department
- Contractor
- Insurance
And each of these models had an equivalent xxxx_name
and xxxx_code
field, ie a Department
has a department_name
and a department_code
.
The Naive Approach
What was currently happening, was a mish-mash of string interpolation directly in the views, methods in the models, and some attempts of decoration with the active decorator gem, of course one decorator per model as the active decorator gem encourages. So the code base for this particular problem was about as DRY as a monsoon with the consistency of the aftermath of a ridiculously spicy curry night out with the lads. Now what I wanted to do straight off was remove the redundant prefixes of the attributes, let each model have just a name
and a code
attribute, and create a single decorator that outputs the two above formats, done and dusted. However the project was already in production and it has a country wide customer base, so running migrations to change the column names for quite a few of the core business models in the database was not an option, no matter how well tested and strong it was made, it was made clear from the get-go that this was not an available route, which I can kind of understand, and therefore didn't want to push the subject matter and cause any friction. There goes the simplest approach of Drying up the code base. However all was not lost. The namings were still consistent, just with varying prefixes, so a nice clear path for a single decorator solution was still available, and staring me in the face.
The Decorated Approach
Using bottled_decorators
with some method missing magic, we can create some marvelously reusable and DRY decorators. Our first step is to create our decorator and add the methods that format our name and code fields into a String:
First thing to do is to slip the bottled_decorators
gem into our Gemfile and install the gem with bundler:
gem 'bottled_decorators'
bundle install
Then create our Decorator:
rails g bottled_decorator NameFormattable code_and_name name_and_code
This will give us a new decorator with the empty methods input for us:
class NameFormattable
include BottledDecorator
def code_and_name
end
def name_and_code
end
end
As mentioned earlier we have consistency in the naming of the attributes, all we need to do is handle the prefixes, there are a few ways we could do this, one obvious way would be to pass a prefix option to the decorator when we are wrapping the model, like so: NameFormattable.call(@department, prefix: :department).code_and_name
a fairly reasonable approach, but instead of always writing out the prefix: option, its much more fun if we use method_missing
so we can call a variety of code_and_name
variation methods and naturally take the prefix, enabling us to do:
@department = NameFormattable.call(Department.find(params[:id]))
@department.department_code_and_name
# => 123abc:ABCDepartment
smooth and sexy!
How we do it:
First, add the method_missing to handle the missing methods:
class NameFormattable
include BottledDecorator
def code_and_name
end
def name_and_code
end
private
def available_methods
/#{self.class.instance_methods(false).join('|')}/
end
def method_missing(method_name, *args)
@prefix = method_name.to_s.gsub(available_methods, '').to_sym
target = method_name.to_s.gsub(/#{@prefix}_/, '').to_sym
return send(target) if target =~ available_methods
super
end
end
So now we are catching all unkown method calls, and the ones that match the criteria for our decorator set a prefix instance variable before calling the relevant method. All method calls not meeting the criteria get passed on to the super method.
note: method missing should always fall back on super, but with Bottled Decorators this is especially true, as Bottled Decorators run on method missing to allow for the continued use of the original model/components methods.
Now we are ready to add the insides of our decorator's core methods:
class NameFormattable
include BottledDecorator
def code_and_name
"#{prefixed_code}:#{prefixed_name}"
end
def name_and_code
"#{prefixed_name}(#{prefixed_code})"
end
private
def prefixed_name
send(:"#{@prefix}_name")
end
def prefixed_code
send(:"#{@prefix}_code")
end
def available_methods
/#{self.class.instance_methods(false).join('|')}/
end
def method_missing(method_name, *args)
@prefix = method_name.to_s.gsub(available_methods, '').to_sym
target = method_name.to_s.gsub(/#{@prefix}_/, '').to_sym
return send(target) if target =~ available_methods
super
end
end
Nothing fancy here, just some simple String interpolation to output the formats we want. With this the main functionality of the decorator is done, we can now call any prefixed variation of our methods and get the output we want.
The final step is to make our prefixed variation methods recognisable, because right now if we do something like:
NameFormattable.call(@department).respond_to? :department_code_and_name
We are going to get a big fat false
sent our way, and doing:
NameFormattable.call(@department).method(:department_code_and_name)
Will give us a NoMethodError. So, lets add our respond_to?
and respond_to_missing?
methods:
class NameFormattable
include BottledDecorator
def code_and_name
"#{prefixed_code}:#{prefixed_name}"
end
def name_and_code
"#{prefixed_name}(#{prefixed_code})"
end
def respond_to?(method_name)
method_available?(method_name) || super
end
private
def prefixed_name
return send(:"#{@prefix}_name")
end
def prefixed_code
return send(:"#{@prefix}_code")
end
def method_available?(method)
method =~ available_methods
end
def available_methods
/#{self.class.instance_methods(false).join('|')}/
end
def method_missing(method_name, *args)
@prefix = method_name.to_s.gsub(available_methods, '').to_sym
target = method_name.to_s.gsub(/#{@prefix}_/, '').to_sym
return send(target) if method_available? target
super
end
def respond_to_missing?(method_name, include_private = false)
method_available?(method_name) || super
end
end
We have also DRY'd up the available methods check in the process. Now our prefixed methods are also recognisable to respond_to?
and method
methods.
note: again, make sure that both respond_to?
and respond_to_missing?
fall back on super.
We now have a short but versatile and reusable decorator ready to go, logic that was doing slight variations of the same job in many different places and classes is now all being handled in one, easy to manage decorator that we can use to wrap any component we like:
# With a Department:
NameFormattable.call(@department).department_code_and_name
# => '123abc:ABCDepartment'
# With a Contractor:
@contractor = NameFormattable.call(@contractor)
@contractor.contractor_name
# => 'Mr. C'
@contractor.contractor_code
# => 'def456'
@contractor.contractor_name_and_code
# => 'Mr. C (def456)'
@contractor.to_json
# => "{\"id\":1,\"contractor_name\":\"Mr. C\",\"contractor_code\":\"def456\",\"contractor_name_and_code\":\"Mr. C (def456)\"}"
# With multiple Offices:
@offices = NameFormattable.call(Office.all)
@offices.map(&:office_code_and_name)
# => ['code01:Office A', 'code02:Office B', 'code03:Office C']
Conclusion
We have now brought law and order to what is a simple task, but due to the repetitiveness of its original implementation, was a maintenance nightmare. True Decorators are meant for this purpose, to DRY re-usable code that expand on a model / components attributes. Bottled Decorators are an attempt at taking away the misconception that decorators are supposed to be uniquely available to the view (because they are not, they should be available and re-usable anywhere), and I hope a foot in the right direction of bringing the true meaning of the Decorator design pattern back to the ruby on rails community.
For more information about Bottled Decorators and decorators in general take a look the the Bottled Decorators gem repository, and don't forget to check out my first blog on the subject if you didn't already!
