morfo 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +11 -11
- data/Guardfile +2 -2
- data/README.md +180 -64
- data/Rakefile +2 -2
- data/benchmarks/data.rb +10 -10
- data/benchmarks/run.rb +7 -7
- data/lib/morfo.rb +6 -15
- data/lib/morfo/builder.rb +34 -0
- data/lib/morfo/tools.rb +34 -0
- data/lib/morfo/version.rb +1 -1
- data/morfo.gemspec +14 -13
- data/spec/lib/morfo/builder_spec.rb +114 -0
- data/spec/lib/morfo/tools_spec.rb +91 -0
- data/spec/lib/morfo_spec.rb +51 -230
- data/spec/spec_helper.rb +7 -5
- data/spec/support/shared_context.rb +34 -0
- data/spec/support/shared_examples.rb +248 -0
- metadata +27 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87b740fa3940052dc425842ec2f8a8c1b39a8955
|
4
|
+
data.tar.gz: ce6d9032e4b76325233bcad0e4ed2a29dba17c15
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0baa0e37cf6809e502bed94eb4da771d3572b51970918d9666d1ea4bdcc2c9872b046f78df3f2035727da35a33affbe3daa3c51c9d050864c61cd6361580aa8
|
7
|
+
data.tar.gz: e1e50f08391db40f95d7ecb81908c19e9e3774d996ca0fa0da757e1f7cd4a1f3c582bbc34cdb0b86ca2ab3dcc840f9b715f24aafb37c05a7bc3f6dd4a395196e
|
data/CONTRIBUTING.md
CHANGED
@@ -2,6 +2,6 @@
|
|
2
2
|
|
3
3
|
1. Fork it
|
4
4
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
5
|
-
3. Commit your changes (`git commit -am
|
5
|
+
3. Commit your changes (`git commit -am "Add some feature"`)
|
6
6
|
4. Push to the branch (`git push origin my-new-feature`)
|
7
7
|
5. Create new Pull Request
|
data/Gemfile
CHANGED
@@ -1,19 +1,19 @@
|
|
1
|
-
source
|
1
|
+
source "https://rubygems.org"
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in morfo.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
6
|
group :test, :development do
|
7
|
-
gem
|
8
|
-
gem
|
9
|
-
gem
|
10
|
-
gem
|
11
|
-
gem
|
12
|
-
gem
|
7
|
+
gem "coveralls", require: false
|
8
|
+
gem "guard"
|
9
|
+
gem "guard-rspec"
|
10
|
+
gem "simplecov"
|
11
|
+
gem "pry"
|
12
|
+
gem "rubinius-coverage", platform: :rbx
|
13
13
|
|
14
|
-
gem
|
15
|
-
gem
|
16
|
-
gem
|
14
|
+
gem "rb-inotify", require: false
|
15
|
+
gem "rb-fsevent", require: false
|
16
|
+
gem "rb-fchange", require: false
|
17
17
|
end
|
18
18
|
|
19
|
-
gem
|
19
|
+
gem "json"
|
data/Guardfile
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# A sample Guardfile
|
2
2
|
# More info at https://github.com/guard/guard#readme
|
3
3
|
|
4
|
-
guard :rspec, cmd:
|
4
|
+
guard :rspec, cmd: "bundle exec rspec", all_on_start: true do
|
5
5
|
watch(%r{^spec/.+_spec\.rb$})
|
6
6
|
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
-
watch(
|
7
|
+
watch("spec/spec_helper.rb") { "spec" }
|
8
8
|
end
|
data/README.md
CHANGED
@@ -12,7 +12,7 @@ This gem is currently only tested on Ruby 2.0 (including 2.0 mode of JRuby and R
|
|
12
12
|
|
13
13
|
Add this line to your application's Gemfile:
|
14
14
|
|
15
|
-
gem
|
15
|
+
gem "morfo"
|
16
16
|
|
17
17
|
And then execute:
|
18
18
|
|
@@ -32,83 +32,199 @@ Use the `field` method to specify what fields exist and where they will get thei
|
|
32
32
|
|
33
33
|
The most basic form is copying the value from another field.
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
```ruby
|
36
|
+
class Title < Morfo::Base
|
37
|
+
field(:tv_show_title)from(:title)
|
38
|
+
end
|
39
|
+
```
|
38
40
|
|
39
41
|
Afterwards use the `morf` method to morf all hashes in one array to the end result:
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
```ruby
|
44
|
+
Title.morf([
|
45
|
+
{ title: "The Walking Dead" },
|
46
|
+
{ title: "Breaking Bad" },
|
47
|
+
])
|
45
48
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
49
|
+
# [
|
50
|
+
# { tv_show_title: "The Walking Dead" },
|
51
|
+
# { tv_show_title: "Breaking Bad" },
|
52
|
+
# ]
|
53
|
+
```
|
50
54
|
|
51
55
|
If you want to have access to nested values, just provide the path to that field comma separated.
|
52
56
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
57
|
+
```ruby
|
58
|
+
class Name < Morfo::Base
|
59
|
+
field(:first_name).from(:name, :first)
|
60
|
+
field(:last_name).from(:name, :last)
|
61
|
+
end
|
62
|
+
|
63
|
+
Name.morf([
|
64
|
+
{
|
65
|
+
name: {
|
66
|
+
first: "Clark",
|
67
|
+
last: "Kent",
|
68
|
+
},
|
69
|
+
},
|
70
|
+
{
|
71
|
+
name: {
|
72
|
+
first: "Bruce",
|
73
|
+
last: "Wayne",
|
74
|
+
},
|
75
|
+
},
|
76
|
+
])
|
77
|
+
|
78
|
+
# [
|
79
|
+
# { first_name: "Clark", last_name: "Kent" },
|
80
|
+
# { first_name: "Bruce", last_name: "Wayne" },
|
81
|
+
# ]
|
82
|
+
```
|
83
|
+
|
84
|
+
### Transformations
|
80
85
|
|
81
86
|
It's also possible to transform the value in any way ruby lets you transform a value. just provide a block in the `transformed` method.
|
82
87
|
|
83
|
-
|
84
|
-
|
85
|
-
|
88
|
+
```ruby
|
89
|
+
class AndZombies < Morfo::Base
|
90
|
+
field(:title).from(title).transformed {|title| "#{title} and Zombies"}
|
91
|
+
end
|
86
92
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
93
|
+
AndZombies.morf([
|
94
|
+
{ title: "Pride and Prejudice" },
|
95
|
+
{ title: "Fifty Shades of Grey" },
|
96
|
+
])
|
91
97
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
98
|
+
# [
|
99
|
+
# { title: "Pride and Prejudice and Zombies" },
|
100
|
+
# { title: "Fifty Shades of Grey and Zombies" },
|
101
|
+
# ]
|
102
|
+
```
|
96
103
|
|
97
|
-
|
104
|
+
### Calculations
|
98
105
|
|
99
106
|
If the value of your field should be based on multiple fields of the input row, yoy can specify a calculation block via the `calculated` method. As an argument the whole input row is passed in.
|
100
107
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
108
|
+
```ruby
|
109
|
+
class NameConcatenator < Morfo::Base
|
110
|
+
field(:name).calculated {|row| "#{row[:first_name]} #{row[:last_name]}"}
|
111
|
+
field(:status).calculated {"Best Friend"}
|
112
|
+
end
|
113
|
+
|
114
|
+
NameConcatenator.morf([
|
115
|
+
{ first_name: "Robin", last_name: "Hood" },
|
116
|
+
{ first_name: "Sherlock", last_name: "Holmes" },
|
117
|
+
])
|
118
|
+
|
119
|
+
# [
|
120
|
+
# { name: "Robin Hood", status: "Best Friend" },
|
121
|
+
# { name: "Sherlock Holmes", status: "Best Friend" }
|
122
|
+
# ]
|
123
|
+
```
|
124
|
+
|
125
|
+
### Builder
|
126
|
+
|
127
|
+
On top of creating transformers with Ruby classes, it is also possible to build transformers with a hash syntax (which could then be serialized as json and stored somewhere else).
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
morfer = Morfo::Builder.new([
|
131
|
+
{ field: :first_name, from: [:name, :first] },
|
132
|
+
{ field: :last_name, from: [:name, :last] },
|
133
|
+
])
|
134
|
+
|
135
|
+
morfer.morf([
|
136
|
+
{
|
137
|
+
name: {
|
138
|
+
first: "Clark",
|
139
|
+
last: "Kent",
|
140
|
+
},
|
141
|
+
},
|
142
|
+
{
|
143
|
+
name: {
|
144
|
+
first: "Bruce",
|
145
|
+
last: "Wayne",
|
146
|
+
},
|
147
|
+
},
|
148
|
+
])
|
149
|
+
|
150
|
+
# [
|
151
|
+
# { first_name: "Clark", last_name: "Kent" },
|
152
|
+
# { first_name: "Bruce", last_name: "Wayne" },
|
153
|
+
# ]
|
154
|
+
```
|
155
|
+
|
156
|
+
The builder includes all other features such as calculation and transformation
|
157
|
+
|
158
|
+
#### Builder Transformations
|
159
|
+
|
160
|
+
To transform a value, use the placeholder %{value}
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
class AndZombies < Morfo::Base
|
164
|
+
field(:title).from(title).transformed {|title| "#{title} and Zombies"}
|
165
|
+
end
|
166
|
+
|
167
|
+
morfer = Morfo::Builder.new([
|
168
|
+
{ field: :title, from: :title, transformed: "%{title} and Zombies" },
|
169
|
+
])
|
170
|
+
|
171
|
+
morfer.morf([
|
172
|
+
{ title: "Pride and Prejudice" },
|
173
|
+
{ title: "Fifty Shades of Grey" },
|
174
|
+
])
|
175
|
+
|
176
|
+
# [
|
177
|
+
# { title: "Pride and Prejudice and Zombies" },
|
178
|
+
# { title: "Fifty Shades of Grey and Zombies" },
|
179
|
+
# ]
|
180
|
+
```
|
181
|
+
|
182
|
+
#### Builder Calculations
|
183
|
+
|
184
|
+
To get access to the other fields use the [ruby string format syntax](http://ruby-doc.org/core-2.2.0/String.html#method-i-25).
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
morfer = Morfo::Builder.new([
|
188
|
+
{ field: :name, calculated: "%{first_name} %{last_name}" },
|
189
|
+
{ field: :status, calculated: "Best Friend" },
|
190
|
+
])
|
191
|
+
|
192
|
+
morfer.morf([
|
193
|
+
{ first_name: "Robin", last_name: "Hood" },
|
194
|
+
{ first_name: "Sherlock", last_name: "Holmes" },
|
195
|
+
])
|
196
|
+
|
197
|
+
# [
|
198
|
+
# { name: "Robin Hood", status: "Best Friend" },
|
199
|
+
# { name: "Sherlock Holmes", status: "Best Friend" }
|
200
|
+
# ]
|
201
|
+
```
|
202
|
+
|
203
|
+
It's even possible to get access to nested keys, using a dot as separator:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
morfer = Morfo::Builder.new([
|
207
|
+
{ field: :name, calculated: "%{name.first} %{name.last}" },
|
208
|
+
])
|
209
|
+
|
210
|
+
morfer.morf([
|
211
|
+
{
|
212
|
+
name: {
|
213
|
+
first: "Clark",
|
214
|
+
last: "Kent",
|
215
|
+
},
|
216
|
+
},
|
217
|
+
{
|
218
|
+
name: {
|
219
|
+
first: "Bruce",
|
220
|
+
last: "Wayne",
|
221
|
+
},
|
222
|
+
},
|
223
|
+
])
|
224
|
+
|
225
|
+
# [
|
226
|
+
# { name: "Clark Kent" },
|
227
|
+
# { name: "Bruce Wayne" },
|
228
|
+
# ]
|
229
|
+
```
|
110
230
|
|
111
|
-
# [
|
112
|
-
# {:name=>"Robin Hood", :status=>"Best Friend"},
|
113
|
-
# {:name=>"Sherlock Holmes", :status=>'Best Friend'}
|
114
|
-
# ]
|
data/Rakefile
CHANGED
data/benchmarks/data.rb
CHANGED
@@ -7,16 +7,16 @@ module BenchmarkData
|
|
7
7
|
|
8
8
|
def row
|
9
9
|
{
|
10
|
-
first_name:
|
11
|
-
last_name:
|
12
|
-
gender:
|
13
|
-
phone_number:
|
14
|
-
cell_phone:
|
15
|
-
street_name:
|
16
|
-
street_number:
|
17
|
-
city:
|
18
|
-
zip:
|
19
|
-
country:
|
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
20
|
}
|
21
21
|
end
|
22
22
|
|
data/benchmarks/run.rb
CHANGED
@@ -1,28 +1,28 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "morfo"
|
2
|
+
require "benchmark"
|
3
|
+
require "./benchmarks/data"
|
4
4
|
|
5
5
|
iterations = 100
|
6
6
|
batch_size = 10000
|
7
7
|
|
8
8
|
definitions = [
|
9
9
|
{
|
10
|
-
label:
|
10
|
+
label: "Simple (strings)",
|
11
11
|
row: BenchmarkData.row_string_keys,
|
12
12
|
morf_class: BenchmarkData::SimpleMorferString
|
13
13
|
},
|
14
14
|
{
|
15
|
-
label:
|
15
|
+
label: "Simple (symbols)",
|
16
16
|
row: BenchmarkData.row,
|
17
17
|
morf_class: BenchmarkData::SimpleMorferSymbol
|
18
18
|
},
|
19
19
|
{
|
20
|
-
label:
|
20
|
+
label: "Nested (strings)",
|
21
21
|
row: BenchmarkData.row_nested_string_keys,
|
22
22
|
morf_class: BenchmarkData::NestedMorferString
|
23
23
|
},
|
24
24
|
{
|
25
|
-
label:
|
25
|
+
label: "Nested (symbols)",
|
26
26
|
row: BenchmarkData.row_nested,
|
27
27
|
morf_class: BenchmarkData::NestedMorferSymbol
|
28
28
|
},
|
data/lib/morfo.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "active_support/core_ext/hash"
|
2
|
+
require "morfo/version"
|
3
|
+
require "morfo/tools"
|
4
|
+
require "morfo/actions"
|
5
|
+
require "morfo/builder"
|
3
6
|
|
4
7
|
module Morfo
|
5
8
|
class Base
|
@@ -16,7 +19,7 @@ module Morfo
|
|
16
19
|
def self.morf_single input
|
17
20
|
output = {}
|
18
21
|
mapping_actions.each do |field_path, action|
|
19
|
-
deep_merge!(
|
22
|
+
output.deep_merge!(store_value(action.execute(input), field_path))
|
20
23
|
end
|
21
24
|
output
|
22
25
|
end
|
@@ -37,17 +40,5 @@ module Morfo
|
|
37
40
|
end
|
38
41
|
end
|
39
42
|
end
|
40
|
-
|
41
|
-
def self.deep_merge! hash, other_hash, &block
|
42
|
-
other_hash.each_pair do |k,v|
|
43
|
-
tv = hash[k]
|
44
|
-
if tv.is_a?(Hash) && v.is_a?(Hash)
|
45
|
-
hash[k] = deep_merge!(tv, v, &block)
|
46
|
-
else
|
47
|
-
hash[k] = block && tv ? block.call(k, tv, v) : v
|
48
|
-
end
|
49
|
-
end
|
50
|
-
hash
|
51
|
-
end
|
52
43
|
end
|
53
44
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Morfo
|
2
|
+
class Builder
|
3
|
+
def initialize(definitions)
|
4
|
+
@definitions = definitions
|
5
|
+
end
|
6
|
+
|
7
|
+
def build
|
8
|
+
# WTF??? `definitions` is not accessible inside class
|
9
|
+
# so this javascript technique is necesseray
|
10
|
+
tmp_definitions = definitions.map { |h| h.symbolize_keys }
|
11
|
+
Class.new(Morfo::Base) do
|
12
|
+
tmp_definitions.each do |definition|
|
13
|
+
f = field(*definition[:field])
|
14
|
+
|
15
|
+
if definition[:from]
|
16
|
+
f = f.from(*definition[:from])
|
17
|
+
end
|
18
|
+
|
19
|
+
if definition[:calculated]
|
20
|
+
f = f.calculated { |r| definition[:calculated] % Morfo::Tools::FlattenHashKeys.new(r).flatten }
|
21
|
+
end
|
22
|
+
|
23
|
+
if definition[:transformed]
|
24
|
+
f = f.transformed { |v| definition[:transformed] % {value: v} }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :definitions
|
33
|
+
end
|
34
|
+
end
|