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.