Skip to content

Commit

Permalink
Merge pull request #3 from leifg/convert_to_declarative
Browse files Browse the repository at this point in the history
Convert to declarative syntax
  • Loading branch information
leifg committed Jan 13, 2014
2 parents 6cd1d05 + 6735610 commit fef7a64
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 111 deletions.
93 changes: 37 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

[![Build Status](https://travis-ci.org/leifg/morfo.png?branch=master)](https://travis-ci.org/leifg/morfo) [![Coverage Status](https://coveralls.io/repos/leifg/morfo/badge.png?branch=master)](https://coveralls.io/r/leifg/morfo) [![Code Climate](https://codeclimate.com/github/leifg/morfo.png)](https://codeclimate.com/github/leifg/morfo) [![Dependency Status](https://gemnasium.com/leifg/morfo.png)](https://gemnasium.com/leifg/morfo) [![Gem Version](https://badge.fury.io/rb/morfo.png)](http://badge.fury.io/rb/morfo)

This Gem is inspired by the [active_importer](https://github.com/continuum/active_importer) Gem.

But instead of importing spreadsheets into models, you can morf (typo intended) arrays of Hashes into other arrays of hashes.
This gem acts like a universal converter from hashes into other hashes. You just define where your hash should get its data from and morfo will do the rest for you.

## Compatibility

Expand All @@ -28,10 +26,14 @@ Or install it yourself as:

In order to morf the hashes you have to provide a class that extends `Morf::Base`

Use the `map` method to specify what field you map to another field:
Use the `field` method to specify what fields exist and where they will get their data from:

### Simple Mapping

The most basic form is, just define another field from the input hash. The value will just be copied.

class Title < Morfo::Base
map :title, :tv_show_title
field :tv_show_title, from: :title
end

Afterwards use the `morf` method to morf all hashes in one array to the end result:
Expand All @@ -46,50 +48,12 @@ Afterwards use the `morf` method to morf all hashes in one array to the end resu
# {tv_show_title: 'Breaking Bad'},
# ]

It is also possible to map fields to multiple other fields

class MultiTitle < Morfo::Base
map :title, :tv_show_title
map :title, :show_title
end

MultiTitle.morf([
{title: 'The Walking Dead'} ,
{title: 'Breaking Bad'},
])

# [
# {tv_show_title: 'The Walking Dead', show_title: 'The Walking Dead'},
# {tv_show_title: 'Breaking Bad', show_title: 'Breaking Bad'},
# ]

## Transformations

For each mapping you can define a block, that will be called on every input:

class AndZombies < Morfo::Base
map :title, :title do |title|
"#{title} and Zombies"
end
end

AndZombies.morf([
{title: 'Pride and Prejudice'},
{title: 'Fifty Shades of Grey'},
])

# [
# {title: 'Pride and Prejudice and Zombies'},
# {title: 'Fifty Shades of Grey and Zombies'},
# ]

## Nested Values
If you want to have access to nested values, you'll have to provide an array as the key:

You can directly access nested values in the hashes:

class Name < Morfo::Base
map [:name, :first], :first_name
map [:name, :last], :last_name
field :first_name, from: [:name, :first]
field :last_name, from: [:name, :last]
end

Name.morf([
Expand All @@ -112,22 +76,39 @@ You can directly access nested values in the hashes:
# {first_name: 'Bruce',last_name: 'Wayne'},
# ]

## Transformations

It is also possible to store values in a nested hash:
Every field can also take a transformation block, so that the original input can be transformed.

class Wrapper < Morfo::Base
map :first_name, [:superhero, :name, :first]
map :last_name, [:superhero, :name, :last]
class AndZombies < Morfo::Base
field(:title, from: :title) {|title| "#{title} and Zombies"}
end

Name.morf([
{first_name: 'Clark',last_name: 'Kent'},
{first_name: 'Bruce',last_name: 'Wayne'},,
])
AndZombies.morf([
{title: 'Pride and Prejudice'},
{title: 'Fifty Shades of Grey'},
])

# [
# {title: 'Pride and Prejudice and Zombies'},
# {title: 'Fifty Shades of Grey and Zombies'},
# ]

As the second argument, the whole row is passed into the block. So you can even do transformation based on the whole row. Or you can leave out all the arguments and return a static value.

class NameConcatenator < Morfo::Base
field(:name) {|_, row| "#{row[:first_name]} #{row[:last_name]}"}
field(:status) { 'Best Friend' }
end

NameConcatenator.morf([
{first_name: 'Robin', last_name: 'Hood'},
{first_name: 'Sherlock', last_name: 'Holmes'},
])

# [
# { superhero: {name: { first: 'Clark', last: 'Kent'}}},
# { superhero: {name: { first: 'Bruce', last: 'Wayne'}}},
# {:name=>"Robin Hood", :status=>"Best Friend"},
# {:name=>"Sherlock Holmes", :status=>'Best Friend'}
# ]


Expand Down
8 changes: 4 additions & 4 deletions benchmarks/data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,28 +46,28 @@ def stringify_keys hash

class SimpleMappingSymbol < Morfo::Base
BenchmarkData.row.keys.each do |field|
map field, :"#{field}_mapped"
field(:"#{field}_mapped", from: field)
end
end

class SimpleMappingString < Morfo::Base
BenchmarkData.row_string_keys.keys.each do |field|
map field, "#{field}_mapped"
field("#{field}_mapped", from: field)
end
end

class NestedMappingSymbol < Morfo::Base
BenchmarkData.row_nested.each do |key, value|
value.keys.each do |field|
map [key, field], :"#{field}_mapped"
field(:"#{field}_mapped", from: [key, field])
end
end
end

class NestedMappingString < Morfo::Base
BenchmarkData.row_nested_string_keys.each do |key, value|
value.keys.each do |field|
map [key, field], "#{field}_mapped"
field("#{field}_mapped", from: [key, field])
end
end
end
Expand Down
51 changes: 11 additions & 40 deletions lib/morfo.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
require 'morfo/version'
require 'morfo/actions'

module Morfo
class Base
def self.map from, to, &transformation
mapping_actions << MapAction.new(from, to, transformation)
def self.field field_name, definition={}, &blk
if blk
mapping_actions << Morfo::Actions::TransformationAction.new(definition[:from], field_name, blk)
else
raise(
ArgumentError,
"No field to map from is specified for #{field_name.inspect}"
) unless definition[:from]
mapping_actions << Morfo::Actions::MapAction.new(definition[:from], field_name)
end
end

def self.morf input
Expand Down Expand Up @@ -31,42 +40,4 @@ def self.deep_merge! hash, other_hash, &block
hash
end
end

class MapAction
attr_reader :from
attr_reader :to
attr_reader :transformation

def initialize from, to, transformation
@from = from
@to = to
@transformation = transformation
end

def execute row
resulting_value = apply_transformation(extract_value(row))
resulting_value ? store_value(to, resulting_value) : {}
end

private
def extract_value row
Array(from).inject(row) do |resulting_value, key|
resulting_value ? resulting_value[key] : nil
end
end

def apply_transformation row
transformation ? transformation.call(row) : row
end

def store_value to, value
Array(to).reverse.inject({}) do |hash, key|
if hash.keys.first.nil?
hash.merge!(key => value)
else
{ key => hash }
end
end
end
end
end
56 changes: 56 additions & 0 deletions lib/morfo/actions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module Morfo
module Actions
module ValueMethods
def extract_value from, row
Array(from).inject(row) do |resulting_value, key|
resulting_value ? resulting_value[key] : nil
end
end

def store_value to, value
return {} if value.nil?

Array(to).reverse.inject({}) do |hash, key|
if hash.keys.first.nil?
hash.merge!(key => value)
else
{ key => hash }
end
end
end
end

class MapAction
include ValueMethods
attr_reader :from
attr_reader :to

def initialize from, to
@from = from
@to = to
end

def execute row
store_value(to, extract_value(from, row))
end
end

class TransformationAction
include ValueMethods
attr_reader :to
attr_reader :from
attr_reader :transformation

def initialize from, to, transformation
@from = from
@to = to
@transformation = transformation
end

def execute row
resulting_value = from ? extract_value(from, row) : nil
store_value(to, transformation.call(resulting_value,row))
end
end
end
end
Empty file added lib/morfo/actions/base.rb
Empty file.
Loading

0 comments on commit fef7a64

Please sign in to comment.