FieldMapper
Data Mapping & Transformation
Uses
- Mapping data between 2 or more formats
- Transforming data with complex rules
- Defining data standards
Overview
FieldMapper introduces a new term for a model-like data structure to avoid possible confusion with libraries such as ActiveRecord.
This new term is: Plat
A plat defines the following:
- Fields
- Datatypes
- Mappings
- Transformation rules
Datatype declarations exist to support implicit type casting of values. The supported datatypes are:
- String
- Boolean
- Time
- Integer
- Float
- Money
- Plat
- List (of any listed type)
Usage
gem install field_mapper
# Gemfile
gem "field_mapper"
Suppose we want to perform a mapping between Facebook users & Twitter users.
-
First we need to define a standard user class.
class StandardUser < FieldMapper::Standard::Plat field :name, type: String field :screen_name, type: String field :info, type: String field :website, type: String field :age, type: Integer # field with allowed values field :gender, type: String do value "F" value "M" end # field with a default value field :net_worth, type: Money, default: 0 # field that holds a list of plats field :friends, type: FieldMapper::Types::List[StandardUser], default: [] end
-
Next we define a Facebook user class that maps onto our standard.
class FacebookUser < FieldMapper::Custom::Plat # note that we set the standard set_standard StandardUser # fields are mapped to the standard field :name, standard: :name field :bio, standard: :info field :website, standard: :website field :net_worth, standard: :net_worth # field with mapped values field :gender, standard: :gender do value "female", standard: "F" value "male", standard: "M" end # some fields don't map to a standard field :birthday, type: Time field :friends, standard: :friends, type: FieldMapper::Types::List[FacebookUser], # <- lists must define type even when mapped to a standard default: [] # field with complex transformation rules field :username, standard: :screen_name, custom_to_standard: -> (value, standard_instance: nil) { # value passed is the custom value # value returned is the standard value "Facebook:#{value.to_s.strip}" }, standard_to_custom: -> (value, standard_instance: nil) { # value passed is the standard value # value returned is the custom value value.to_s.split(/:/).last } end
-
Then we define a Twitter user class that maps onto our standard.
class TwitterUser < FieldMapper::Custom::Plat set_standard StandardUser field :name, standard: :name field :description, standard: :info field :url, standard: :website field :followers_count, type: Integer field :screen_name, standard: :screen_name, custom_to_standard: -> (value, standard_instance: nil) { "Twitter:#{value.to_s.strip}" }, standard_to_custom: -> (value, standard_instance: nil) { value.to_s.split(/:/).last } # callback method that runs after tranformation def after_convert(from: nil, to: nil) if from.respond_to? :friends self.followers_count = from.friends.length end end end
-
Now we can construct a Facebook user.
zuck = FacebookUser.new( name: "Mark Zuckerberg", username: "zuck", bio: "Creator of Facebook", website: "http://www.facebook.com/zuck", gender: "male", age: 29, net_worth: "$29,000,000,000 USD", # value will be cast to a Money birthday: "1984-05-14" # value will be cast to a Time ) zuck.friends << FacebookUser.new(name: "Priscilla Chan")
-
We can also transform our Facebook user to a standard user.
converter = FieldMapper::Custom::Converter.new(zuck) standard_zuck = converter.convert_to_standard
-
We can also transform our Facebook user to a Twitter user.
converter = FieldMapper::Custom::Converter.new(zuck) twitter_zuck = converter.convert_to(TwitterUser)
-
We can transform a standard user into both a Facebook & Twitter user.
converter = FieldMapper::Standard::Converter.new(standard_zuck) zuck_from_standard = converter.convert_to(FacebookUser) twitter_zuck_from_standard = converter.convert_to(TwitterUser)
-
We can emit our objects as a Hash.
zuck_hash = zuck.to_hash { "_node_id" => 70260402777020, "_flat" => false, "name" => "Mark Zuckerberg", "username" => "zuck", "bio" => "Creator of Facebook", "website" => "http://www.facebook.com/zuck", "gender" => "male", "net_worth" => "$29,000,000,000.00 USD", "friends" => [ { "_node_id" => 70260401841760, "_flat" => false, "name" => "Priscilla Chan", "username" => nil, "bio" => nil, "website" => nil, "gender" => nil, "net_worth" => nil, "friends" => [], "birthday" => nil } ], "birthday" => "1984-05-14T06:00:00Z" } zuck_flat_hash = zuck.to_hash(flatten: true) { "_node_id" => 70260402777020, "_flat" => true, "name" => "Mark Zuckerberg", "username" => "zuck", "bio" => "Creator of Facebook", "website" => "http://www.facebook.com/zuck", "gender" => "male", "net_worth" => "$29,000,000,000.00 USD", "friends" => "[{\"_node_id\":70260401841760,\"_flat\":true,\"name\":\"Priscilla Chan\",\"username\":null,\"bio\":null,\"website\":null,\"gender\":null,\"net_worth\":null,\"friends\":[],\"birthday\":null}]", "birthday" => "1984-05-14T06:00:00Z" }
-
We can also reconstruct instances from a Hash.
zuck_from_hash = FacebookUser.new(zuck_hash) zuck_from_flat_hash = FacebookUser.new(zuck_flat_hash)
This is powerful stuff. I invite you to play around & experiment. Read through the tests for more detail.