A Real World Metaprogramming Case Study

Metaprogramming is one of the unique features of Ruby. Metaprogramming is an interesting concept, and a tool can save a lot of time. It can also be confusing for developers and difficult to maintain if not used properly. Here is an example of how I used Metaprogramming to solve a real-world problem and save a few hours coding.

Serializers With Duplicate Code

I’m working on an application that used for managing vacation bookings. The application needs an API to integrate with some business partners. As part of the specification, dates should be returned in the API in a “YYYY-MM-DD” format.

To build the API, I’ll be using ActiveRecord::Serializer to customize the JSON output.

Here’s a snippet from the reservation serializer class:

<p>class ReservationSerializer &lt;&lt; ActiveModel::Serializer<br />
   attributes :starts_on, :ends_on, :posted_on, :reserved_on</p>
<p>   def starts_on<br />
     object.starts_on.strftime(&quot;%F&quot;)<br />
   end<br />
   def ends_on<br />
     object.ends_on.strftime(&quot;%F&quot;)<br />
   end<br />
   def posted_on<br />
     object.posted_on.strftime(&quot;%F&quot;)<br />
   end<br />
   def reserved_on<br />
     object.reserved_on.strftime(&quot;%F&quot;)<br />
   end<br />

This method works because you can override any attribute in a serializer class. The problem is that this is going to be a lot of duplicate code. There are also several other API endpoints that need to deal with dates.

Wouldn’t be nice if we could format dates without writing a bunch of duplicate methods?

Creating a Super Class That Can Create Methods

In Ruby, parent classes have the ability to modify its children. We are going to use this ability to define methods on our serializers without writing them by hand.

We’ll start by creating an ApplicationSerializer from which each of out serializers will inherit.

<br />
class ApplicationSerializer &lt;&lt; ActiveModel::Serializer<br />
 end<br />

Inside of this superclass, we are going to define a formatted_date class method that we will use in our subclasses.

Here’s what the final result looks like:

<br />
  def self.formatted_date(options = {})<br />
     attribute = options[:attribute] || nil<br />
     format = options[:format] || &quot;%F&quot;</p>
<p>     if attribute.present?<br />
       define_method(attribute) do<br />
         object[attribute].strftime(format)<br />
       end<br />
     end<br />
   end<br />

Let’s break this down:

  • First, we pass an options hash that will take the attribute and the format we will use. We are defaulting to the YYYY-MM-DD format, but this gives other developers the option to override that if needed.
  • Then, We check that an attribute is passed, and if so, we call the define_method method. We will use this to create methods in our subclass. Note that the created method’s name will be the same as the attribute.
  • Finally, We pass a block to define_method. This block will become the body of the new method. Note that the block uses the object property, which belongs to the subclass and not ApplicationSerializer.

Creating Our Dynamic Methods

Inside of our ReservationSerializer class, we change the super class to ApplicationSerializer. Changing the parent class gives us the added ability to create new date attribute methods on the fly. Now writing this:

<br />
  formatted_date :attribute =&gt; :starts_on<br />

is exactly the same as writing this:

<br />
 def starts_on<br />
   object.starts_on.strftime(&quot;%F&quot;)<br />
 end<br />

Our Updated Class

Let’s take a look at our new ReservationSerializer:

<br />
 class ReservationSerializer &lt; ApplicationSerializer<br />
   attributes :starts_on, :ends_on, :posted_on, :reserved_on</p>
<p>   formatted_date :attribute =&gt; :starts_on<br />
   formatted_date :attribute =&gt; :ends_on<br />
   formatted_date :attribute =&gt; :posted_on<br />
   formatted_date :attribute =&gt; :reserved_on<br />
 end<br />

Not only is this cleaner to read and less duplicate code, but we have this functionality available in all of our serializers.

Miss your last deadline? Download a Free Chapter of “Dependable: Deliver Software on Time and within Budget”
Click Here to Leave a Comment Below 0 comments

Leave a Reply: