morfo 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 435026b5435920e085878e500852cef28210364a
4
- data.tar.gz: 358f66a812811a70b7e7d6f9791d5462a578c4a7
3
+ metadata.gz: 7bb20ae90a7b011588d8e096694b69fab5664684
4
+ data.tar.gz: 00b3eb367ac96a797105118974ca2562a35d4163
5
5
  SHA512:
6
- metadata.gz: 382af73b1c1953b0660c7c255b6f1276bc295448654df87e47f9dca16a2d98743d09544e172917c22ed1034877faffc947e07324da114c6b64960dcf080cc4dc
7
- data.tar.gz: 7fce9a8d94b8b1e4d67d27157a9a01a5211b05203f7d072bc28eb0d3c052e52730f30d08d5a600807a86efe1756a6b5b3e8b54676677203b6c34418648976f3c
6
+ metadata.gz: c84f9323b71740ee3056033a25974a7daefedd3e29e92b40138bf5591e1b3a186349527d4cf13865c8a91bcbd260f01587857917dae74eca157351250de3f321
7
+ data.tar.gz: d052cce267ede1514bb6bb70c2db3eabbc3c4f01273d337c95b399986a683f0516ea2175fc43b0ad6fbf9003a1789fc62054ac498d6cc5747a886c564985db9e
data/README.md CHANGED
@@ -2,9 +2,7 @@
2
2
 
3
3
  [![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)
4
4
 
5
- This Gem is inspired by the [active_importer](https://github.com/continuum/active_importer) Gem.
6
-
7
- But instead of importing spreadsheets into models, you can morf (typo intended) arrays of Hashes into other arrays of hashes.
5
+ 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.
8
6
 
9
7
  ## Compatibility
10
8
 
@@ -28,10 +26,14 @@ Or install it yourself as:
28
26
 
29
27
  In order to morf the hashes you have to provide a class that extends `Morf::Base`
30
28
 
31
- Use the `map` method to specify what field you map to another field:
29
+ Use the `field` method to specify what fields exist and where they will get their data from:
30
+
31
+ ### Simple Mapping
32
+
33
+ The most basic form is, just define another field from the input hash. The value will just be copied.
32
34
 
33
35
  class Title < Morfo::Base
34
- map :title, :tv_show_title
36
+ field :tv_show_title, from: :title
35
37
  end
36
38
 
37
39
  Afterwards use the `morf` method to morf all hashes in one array to the end result:
@@ -46,50 +48,12 @@ Afterwards use the `morf` method to morf all hashes in one array to the end resu
46
48
  # {tv_show_title: 'Breaking Bad'},
47
49
  # ]
48
50
 
49
- It is also possible to map fields to multiple other fields
50
-
51
- class MultiTitle < Morfo::Base
52
- map :title, :tv_show_title
53
- map :title, :show_title
54
- end
55
-
56
- MultiTitle.morf([
57
- {title: 'The Walking Dead'} ,
58
- {title: 'Breaking Bad'},
59
- ])
60
-
61
- # [
62
- # {tv_show_title: 'The Walking Dead', show_title: 'The Walking Dead'},
63
- # {tv_show_title: 'Breaking Bad', show_title: 'Breaking Bad'},
64
- # ]
65
-
66
- ## Transformations
67
-
68
- For each mapping you can define a block, that will be called on every input:
69
-
70
- class AndZombies < Morfo::Base
71
- map :title, :title do |title|
72
- "#{title} and Zombies"
73
- end
74
- end
75
-
76
- AndZombies.morf([
77
- {title: 'Pride and Prejudice'},
78
- {title: 'Fifty Shades of Grey'},
79
- ])
80
-
81
- # [
82
- # {title: 'Pride and Prejudice and Zombies'},
83
- # {title: 'Fifty Shades of Grey and Zombies'},
84
- # ]
85
-
86
- ## Nested Values
51
+ If you want to have access to nested values, you'll have to provide an array as the key:
87
52
 
88
- You can directly access nested values in the hashes:
89
53
 
90
54
  class Name < Morfo::Base
91
- map [:name, :first], :first_name
92
- map [:name, :last], :last_name
55
+ field :first_name, from: [:name, :first]
56
+ field :last_name, from: [:name, :last]
93
57
  end
94
58
 
95
59
  Name.morf([
@@ -112,22 +76,39 @@ You can directly access nested values in the hashes:
112
76
  # {first_name: 'Bruce',last_name: 'Wayne'},
113
77
  # ]
114
78
 
79
+ ## Transformations
115
80
 
116
- It is also possible to store values in a nested hash:
81
+ Every field can also take a transformation block, so that the original input can be transformed.
117
82
 
118
- class Wrapper < Morfo::Base
119
- map :first_name, [:superhero, :name, :first]
120
- map :last_name, [:superhero, :name, :last]
83
+ class AndZombies < Morfo::Base
84
+ field(:title, from: :title) {|title| "#{title} and Zombies"}
121
85
  end
122
86
 
123
- Name.morf([
124
- {first_name: 'Clark',last_name: 'Kent'},
125
- {first_name: 'Bruce',last_name: 'Wayne'},,
126
- ])
87
+ AndZombies.morf([
88
+ {title: 'Pride and Prejudice'},
89
+ {title: 'Fifty Shades of Grey'},
90
+ ])
91
+
92
+ # [
93
+ # {title: 'Pride and Prejudice and Zombies'},
94
+ # {title: 'Fifty Shades of Grey and Zombies'},
95
+ # ]
96
+
97
+ 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.
98
+
99
+ class NameConcatenator < Morfo::Base
100
+ field(:name) {|_, row| "#{row[:first_name]} #{row[:last_name]}"}
101
+ field(:status) { 'Best Friend' }
102
+ end
103
+
104
+ NameConcatenator.morf([
105
+ {first_name: 'Robin', last_name: 'Hood'},
106
+ {first_name: 'Sherlock', last_name: 'Holmes'},
107
+ ])
127
108
 
128
109
  # [
129
- # { superhero: {name: { first: 'Clark', last: 'Kent'}}},
130
- # { superhero: {name: { first: 'Bruce', last: 'Wayne'}}},
110
+ # {:name=>"Robin Hood", :status=>"Best Friend"},
111
+ # {:name=>"Sherlock Holmes", :status=>'Best Friend'}
131
112
  # ]
132
113
 
133
114
 
@@ -0,0 +1,74 @@
1
+ module BenchmarkData
2
+ extend self
3
+
4
+ def nested_wrapper
5
+ :person
6
+ end
7
+
8
+ def row
9
+ {
10
+ first_name: 'Jazmyn',
11
+ last_name: 'Willms',
12
+ gender: 'female',
13
+ phone_number: '485-675-9228',
14
+ cell_phone: '1-172-435-9402 x4907',
15
+ street_name: 'Becker Inlet',
16
+ street_number: '15a',
17
+ city: 'Carolynchester',
18
+ zip: '38189',
19
+ country: 'USA',
20
+ }
21
+ end
22
+
23
+ def row_string_keys
24
+ stringify_keys(row)
25
+ end
26
+
27
+ def row_nested
28
+ {
29
+ nested_wrapper => row
30
+ }
31
+ end
32
+
33
+ def row_nested_string_keys
34
+ {
35
+ nested_wrapper.to_s => row_string_keys
36
+ }
37
+ end
38
+
39
+ private
40
+ def stringify_keys hash
41
+ hash.keys.each do |key|
42
+ hash[key.to_s] = hash.delete(key)
43
+ end
44
+ hash
45
+ end
46
+
47
+ class SimpleMorferSymbol < Morfo::Base
48
+ BenchmarkData.row.keys.each do |field|
49
+ field(:"#{field}_mapped", from: field)
50
+ end
51
+ end
52
+
53
+ class SimpleMorferString < Morfo::Base
54
+ BenchmarkData.row_string_keys.keys.each do |field|
55
+ field("#{field}_mapped", from: field)
56
+ end
57
+ end
58
+
59
+ class NestedMorferSymbol < Morfo::Base
60
+ BenchmarkData.row_nested.each do |key, value|
61
+ value.keys.each do |field|
62
+ field(:"#{field}_mapped", from: [key, field])
63
+ end
64
+ end
65
+ end
66
+
67
+ class NestedMorferString < Morfo::Base
68
+ BenchmarkData.row_nested_string_keys.each do |key, value|
69
+ value.keys.each do |field|
70
+ field("#{field}_mapped", from: [key, field])
71
+ end
72
+ end
73
+ end
74
+ end
data/benchmarks/run.rb ADDED
@@ -0,0 +1,46 @@
1
+ require 'morfo'
2
+ require 'benchmark'
3
+ require './benchmarks/data'
4
+
5
+ iterations = 100
6
+ batch_size = 10000
7
+
8
+ definitions = [
9
+ {
10
+ label: 'Simple (strings)',
11
+ row: BenchmarkData.row_string_keys,
12
+ morf_class: BenchmarkData::SimpleMorferString
13
+ },
14
+ {
15
+ label: 'Simple (symbols)',
16
+ row: BenchmarkData.row,
17
+ morf_class: BenchmarkData::SimpleMorferSymbol
18
+ },
19
+ {
20
+ label: 'Nested (strings)',
21
+ row: BenchmarkData.row_nested_string_keys,
22
+ morf_class: BenchmarkData::NestedMorferString
23
+ },
24
+ {
25
+ label: 'Nested (symbols)',
26
+ row: BenchmarkData.row_nested,
27
+ morf_class: BenchmarkData::NestedMorferSymbol
28
+ },
29
+ ]
30
+
31
+ definitions.each do |defintition|
32
+ defintition.merge!(
33
+ data: Array.new(batch_size){ defintition[:row] }
34
+ )
35
+ end
36
+
37
+ Benchmark.bm(20) do |x|
38
+ definitions.each do |defintition|
39
+ x.report(defintition[:label]) do
40
+ iterations.times do
41
+ defintition[:morf_class].morf(defintition[:data])
42
+ end
43
+ end
44
+ end
45
+ end
46
+
File without changes
@@ -0,0 +1,56 @@
1
+ module Morfo
2
+ module Actions
3
+ module ValueMethods
4
+ def extract_value from, row
5
+ Array(from).inject(row) do |resulting_value, key|
6
+ resulting_value ? resulting_value[key] : nil
7
+ end
8
+ end
9
+
10
+ def store_value to, value
11
+ return {} if value.nil?
12
+
13
+ Array(to).reverse.inject({}) do |hash, key|
14
+ if hash.keys.first.nil?
15
+ hash.merge!(key => value)
16
+ else
17
+ { key => hash }
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ class MapAction
24
+ include ValueMethods
25
+ attr_reader :from
26
+ attr_reader :to
27
+
28
+ def initialize from, to
29
+ @from = from
30
+ @to = to
31
+ end
32
+
33
+ def execute row
34
+ store_value(to, extract_value(from, row))
35
+ end
36
+ end
37
+
38
+ class TransformationAction
39
+ include ValueMethods
40
+ attr_reader :to
41
+ attr_reader :from
42
+ attr_reader :transformation
43
+
44
+ def initialize from, to, transformation
45
+ @from = from
46
+ @to = to
47
+ @transformation = transformation
48
+ end
49
+
50
+ def execute row
51
+ resulting_value = from ? extract_value(from, row) : nil
52
+ store_value(to, transformation.call(resulting_value,row))
53
+ end
54
+ end
55
+ end
56
+ end
data/lib/morfo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Morfo
2
- VERSION = '0.0.3'
2
+ VERSION = '0.1.0'
3
3
  end
data/lib/morfo.rb CHANGED
@@ -1,9 +1,18 @@
1
1
  require 'morfo/version'
2
+ require 'morfo/actions'
2
3
 
3
4
  module Morfo
4
5
  class Base
5
- def self.map from, to, &transformation
6
- mapping_actions << MapAction.new(from, to, transformation)
6
+ def self.field field_name, definition={}, &blk
7
+ if blk
8
+ mapping_actions << Morfo::Actions::TransformationAction.new(definition[:from], field_name, blk)
9
+ else
10
+ raise(
11
+ ArgumentError,
12
+ "No field to get value from is specified for #{field_name.inspect}"
13
+ ) unless definition[:from]
14
+ mapping_actions << Morfo::Actions::MapAction.new(definition[:from], field_name)
15
+ end
7
16
  end
8
17
 
9
18
  def self.morf input
@@ -31,43 +40,4 @@ module Morfo
31
40
  hash
32
41
  end
33
42
  end
34
-
35
- class MapAction
36
- attr_reader :from
37
- attr_reader :to
38
- attr_reader :transformation
39
-
40
- def initialize from, to, transformation
41
- @from = from
42
- @to = to
43
- @transformation = transformation
44
- end
45
-
46
- def execute row
47
- resulting_value = apply_transformation(extract_value(row))
48
- resulting_value ? store_value(to, resulting_value) : {}
49
- end
50
-
51
- private
52
- def extract_value row
53
- Array(from).inject(row) do |resulting_value, key|
54
- resulting_value ? resulting_value[key] : nil
55
- end
56
- end
57
-
58
- def apply_transformation row
59
- transformation ? transformation.call(row) : row
60
- end
61
-
62
- def store_value to, value
63
- Array(to).reverse.inject({}) do |hash, key|
64
- if hash.keys.first.nil?
65
- hash.merge!(key => value)
66
- else
67
- { key => hash }
68
- end
69
- end
70
- #{ to => value }
71
- end
72
- end
73
43
  end
@@ -31,12 +31,24 @@ describe Morfo::Base do
31
31
  end
32
32
 
33
33
  describe '#morf' do
34
+ context 'errors' do
35
+ subject(:no_from) do
36
+ class NilMorfer < Morfo::Base
37
+ field :my_field, {}
38
+ end
39
+ NilMorfer
40
+ end
41
+ it 'raises error for nil field' do
42
+ expect{no_from.morf([])}.to raise_error(ArgumentError)
43
+ end
44
+ end
45
+
34
46
  context '1 to 1 conversion' do
35
47
  subject do
36
- class TitleMapper < Morfo::Base
37
- map :title, :tv_show_title
48
+ class TitleMorfer < Morfo::Base
49
+ field :tv_show_title, from: :title
38
50
  end
39
- TitleMapper
51
+ TitleMorfer
40
52
  end
41
53
 
42
54
  it 'maps title correctly' do
@@ -53,12 +65,10 @@ describe Morfo::Base do
53
65
 
54
66
  context '1 to 1 conversion with transformation' do
55
67
  subject do
56
- class NumCastMapper < Morfo::Base
57
- map :cast, :cast_num do |cast|
58
- cast.size
59
- end
68
+ class NumCastMorfer < Morfo::Base
69
+ field(:cast_num, from: :cast){|v,r| v.size}
60
70
  end
61
- NumCastMapper
71
+ NumCastMorfer
62
72
  end
63
73
 
64
74
  it 'calls transformation correctly' do
@@ -69,11 +79,11 @@ describe Morfo::Base do
69
79
 
70
80
  context '1 to many conversion' do
71
81
  subject do
72
- class MutliTitleMapper < Morfo::Base
73
- map :title, :title
74
- map :title, :also_title
82
+ class MutliTitleMorfer < Morfo::Base
83
+ field :title, from: :title
84
+ field :also_title, from: :title
75
85
  end
76
- MutliTitleMapper
86
+ MutliTitleMorfer
77
87
  end
78
88
 
79
89
  it 'maps title to multiple fields' do
@@ -85,17 +95,24 @@ describe Morfo::Base do
85
95
  context 'nested conversion' do
86
96
  context 'nested source' do
87
97
  subject(:valid_path) do
88
- class ImdbRatingMapper < Morfo::Base
89
- map [:ratings, :imdb], :rating
98
+ class ImdbRatingMorfer < Morfo::Base
99
+ field :rating, from: [:ratings, :imdb]
100
+ end
101
+ ImdbRatingMorfer
102
+ end
103
+
104
+ subject(:valid_path_with_transformation) do
105
+ class ImdbRatingMorfer < Morfo::Base
106
+ field(:rating, from: [:ratings, :imdb]){|v| "Rating: #{v}"}
90
107
  end
91
- ImdbRatingMapper
108
+ ImdbRatingMorfer
92
109
  end
93
110
 
94
111
  subject(:invalid_path) do
95
- class InvalidImdbRatingMapper < Morfo::Base
96
- map [:very, :long, :path, :that, :might, :not, :exist], :rating
112
+ class InvalidImdbRatingMorfer < Morfo::Base
113
+ field :rating, from: [:very, :long, :path, :that, :might, :not, :exist]
97
114
  end
98
- InvalidImdbRatingMapper
115
+ InvalidImdbRatingMorfer
99
116
  end
100
117
 
101
118
  it 'maps nested attributes' do
@@ -103,6 +120,11 @@ describe Morfo::Base do
103
120
  expect(valid_path.morf(input)).to eq(expected_output)
104
121
  end
105
122
 
123
+ it 'maps nested attributes with transformation' do
124
+ expected_output = input.map{|v| {rating: "Rating: #{v[:ratings][:imdb]}"} }
125
+ expect(valid_path_with_transformation.morf(input)).to eq(expected_output)
126
+ end
127
+
106
128
  it 'doesn\'t raise error for invalid path' do
107
129
  expected_output = [{},{}]
108
130
  expect(invalid_path.morf(input)).to eq(expected_output)
@@ -111,11 +133,11 @@ describe Morfo::Base do
111
133
 
112
134
  context 'nested destination' do
113
135
  subject do
114
- class WrapperMapper < Morfo::Base
115
- map :title, [:tv_show, :title]
116
- map :channel, [:tv_show, :channel]
136
+ class WrapperMorfer < Morfo::Base
137
+ field([:tv_show, :title], from: :title)
138
+ field([:tv_show, :channel], from: :channel){|v| "Channel: #{v}"}
117
139
  end
118
- WrapperMapper
140
+ WrapperMorfer
119
141
  end
120
142
 
121
143
  it 'maps to nested destination' do
@@ -123,7 +145,7 @@ describe Morfo::Base do
123
145
  {
124
146
  tv_show: {
125
147
  title: v[:title],
126
- channel: v[:channel],
148
+ channel: "Channel: #{v[:channel]}",
127
149
  }
128
150
  }
129
151
  }
@@ -131,5 +153,37 @@ describe Morfo::Base do
131
153
  end
132
154
  end
133
155
  end
156
+
157
+ context 'calculations' do
158
+ subject do
159
+ class TitlePrefixMorfer < Morfo::Base
160
+ field(:title_with_channel){|v,r| "#{r[:title]}, (#{r[:channel]})"}
161
+ end
162
+ TitlePrefixMorfer
163
+ end
164
+
165
+ it 'maps calculation correctly' do
166
+ expected_output = input.map{|r|
167
+ {
168
+ title_with_channel: "#{r[:title]}, (#{r[:channel]})"
169
+ }
170
+ }
171
+ expect(subject.morf(input)).to eq(expected_output)
172
+ end
173
+ end
174
+
175
+ context 'static values' do
176
+ subject do
177
+ class StaticTitleMorfer < Morfo::Base
178
+ field(:new_title){ 'Static Title' }
179
+ end
180
+ StaticTitleMorfer
181
+ end
182
+
183
+ it 'maps static value correctly' do
184
+ expected_output = input.map{|r| {new_title: 'Static Title'} }
185
+ expect(subject.morf(input)).to eq(expected_output)
186
+ end
187
+ end
134
188
  end
135
189
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: morfo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leif Gensert
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-11 00:00:00.000000000 Z
11
+ date: 2014-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -73,7 +73,11 @@ files:
73
73
  - LICENSE.txt
74
74
  - README.md
75
75
  - Rakefile
76
+ - benchmarks/data.rb
77
+ - benchmarks/run.rb
76
78
  - lib/morfo.rb
79
+ - lib/morfo/actions.rb
80
+ - lib/morfo/actions/base.rb
77
81
  - lib/morfo/version.rb
78
82
  - morfo.gemspec
79
83
  - spec/lib/morfo_spec.rb