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!

SNS

Contact Me

  • john.hayes.reed@gmail.com

Recent Activity

Attended Kansai Ruby Kaigi 2017

A Ruby conference in the kansai region of japan, held in Osaka. Listened to a variety of presentations from many people in the Ruby community. This years theme was Community and Business.

Bottled observers gem release

The first production version of bottled_observers has been released (v0.1.0)


New blog post!

A blog about more advanced decorating concepts in ruby.


Bottled decorators gem update

v0.1.5 of bottled decorators has been released.


New blog post!

A blog post about the concept of class-instance variables


Website Design Update

johnhayesreed.com has had a makeover with Bootstrap 4

New blog post!

A new blog about design patterns in rails - Decorators


Bottled decorators gem release

The first production version of bottled_decorators has been released (v0.1.4)


New blog post!

A new blog about desing patterns in rails - Services


Bottled services gem release

The first production version of bottled_services has been released (v0.1.3)


Ruby Rampage 2016

Took part in 2016's Ruby Rampage 48 hour Hackathon.