Why I wrote "massager" gem?

There are a lot of great gems out there that help to create structs and value objects from hashes and other unstructured data. But the problem is that data rarely comes in a form that we need it. It needs some massaging (See what I did there?) before it can be transformed to our value object or struct. For example we might have CSV file that looks like following:

ID;ALIAS;FIRSTNAME,LASTNAME,DATE  
1,XYZ,Janis,Miezitis,2016/12/12  

What we can do when this data comes in is to map the columns to parameters that we can pass to the our struct as follows:

class Person  
  def initialize(id:,alias:,first_name:)
    # Assign the values to appropriate attr_accessors
  end
end

csv_row # Contains the CSV data with headers  
attrs = {  
  id: csv_row.fetch("ID"),
  alias: csv_row.fetch("ALIAS"),
  first_name: csv_row.fetch("FIRSTNAME")
}

Person.new(attrs)  

It becomes worse when you need to do a preprocessing step where you need, let's say, parse the date.

attrs = {  
  date: Date.parse(csv_row.fetch("DATE")),
  ...
}

And what about combining two attributes together?:

attrs = {  
  full_name: "#{csv_row.fetch("FIRSTNAME")} #{csv_row.fetch("LASTNAME")}",
  ...
}

This is not ideal. Of course, in reality I would use some kind of mapper class to achieve this, but that is whole other discussion.

Another thing I like to do is to add type checks at the edges of my applications (Data uploads, HTTP params, API integration). And there is a perfect gem for this called dry-types.

So, I was starting to think about a gem that can achieve data manipulation with somewhat reasonable API. For the inspiration I looked at conformist gem that is designed specifically to work with CSV. I liked the idea, but I wanted something more generic, something that I can use with any hash-like structure and I wanted to have type checks in place. Thus I figured something like the following API would be what I need:

class Person  
  attribute :id, "ID", type: Types::Strict::Int
  attribute :key, "ALIAS", type: Types::Strict::String
  attribute :first_name, "FIRSTNAME", type: Types::Strict::String
end

Person.call(csv_row)  

Also I wanted to have support for combining multiple keys together before the type check is actually performed:

class Person  
  ...
  attribute :full_name, "FIRSTNAME", "LASTNAME", type: Types::Strict::Int do |first_name, last_name|
    "#{first_name} #{last_name}"
  end
end

Person.call(csv_row)  

And support for strict schema, so that if the key is really needed to be there, it would raise an error if it is not:

class Person  
  attribute :id, "ID", type: Types::Strict::Int, strict: true
end  

And that's it. From this API I built the massager gem. Please check out README for complete documentation and see dry-types gem for more info on how to do type checks and more.

Janis Miezitis

Read more posts by this author.

Subscribe to Janis Miezitis personal blog

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!