htransform 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +149 -0
- data/Rakefile +13 -0
- data/examples/example.rb +35 -0
- data/examples/example_htransform.rb +18 -0
- data/htransform.gemspec +21 -0
- data/lib/htransform/version.rb +3 -0
- data/lib/htransform.rb +198 -0
- data/spec/htransform_nested_spec.rb +108 -0
- data/spec/htransform_spec.rb +392 -0
- data/spec/spec_helper.rb +1 -0
- metadata +94 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
HTransform
|
2
|
+
======
|
3
|
+
|
4
|
+
HTransform provides a simple DSL to transform a supplied hash with an arbitrary structure to another hash with an even more arbitrary structure.
|
5
|
+
|
6
|
+
***Note:*** *HTransform does not simply modify the hash you have passed in. It actually creates a new hash from scratch. It only copies or does transformations on the fields you specify in the transform block.*
|
7
|
+
|
8
|
+
Support
|
9
|
+
=======
|
10
|
+
|
11
|
+
Should work for both 1.8 and 1.9 rubies.
|
12
|
+
|
13
|
+
Install
|
14
|
+
=======
|
15
|
+
|
16
|
+
Standard gem install.
|
17
|
+
|
18
|
+
gem install htransform
|
19
|
+
|
20
|
+
|
21
|
+
Put this in your Gemfile.
|
22
|
+
|
23
|
+
gem "htransform"
|
24
|
+
|
25
|
+
or
|
26
|
+
|
27
|
+
gem "htransform", :git =>
|
28
|
+
"git://github.com/joshkrueger/htransform.git"
|
29
|
+
|
30
|
+
Basic Usage
|
31
|
+
===========
|
32
|
+
|
33
|
+
All you have to do is create a new class and inherit from HTransform.
|
34
|
+
|
35
|
+
class ExampleHTransform < HTransform
|
36
|
+
end
|
37
|
+
|
38
|
+
Inside your new class you just use the hopefully not-convoluted DSL.
|
39
|
+
|
40
|
+
class ExampleHTransform < HTransform
|
41
|
+
transform do
|
42
|
+
input :from => "foo", :to => :bar
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
Theres also shorthand for you lazy folks.
|
47
|
+
|
48
|
+
input "foo" => :bar
|
49
|
+
|
50
|
+
But how do I do more than just re-name my keys?
|
51
|
+
|
52
|
+
Let's start simple. Lets take the following hash
|
53
|
+
|
54
|
+
{ "foo" => "party" }
|
55
|
+
|
56
|
+
and have HTransform change it to
|
57
|
+
|
58
|
+
{ :foo => "Party" }
|
59
|
+
|
60
|
+
Its pretty easy!
|
61
|
+
|
62
|
+
input "foo" => :foo, :via => :capitalize
|
63
|
+
|
64
|
+
See! Nice and simple!
|
65
|
+
|
66
|
+
HTransform with a block!
|
67
|
+
|
68
|
+
input "foo" => :foo, :via => lambda { |x| x.capitalize }
|
69
|
+
|
70
|
+
Actually Using HTransform
|
71
|
+
================
|
72
|
+
|
73
|
+
So we have our ExampleHTransform we created above (with our capitalize transform), saved somewhere. Lets say its in your "lib" folder. Anywhere it's loadable via require should be fine.
|
74
|
+
|
75
|
+
require 'htransform'
|
76
|
+
require 'example_htransform'
|
77
|
+
|
78
|
+
contrived_input_hash = { "foo" => "bar" }
|
79
|
+
|
80
|
+
output_hash = ExampleHTransform.convert(contrived_input_hash)
|
81
|
+
|
82
|
+
and now output_hash should look like
|
83
|
+
|
84
|
+
{ :foo => "Bar" }
|
85
|
+
|
86
|
+
Slightly Less Basic Usage
|
87
|
+
=================
|
88
|
+
|
89
|
+
Want to combine multiple input keys? **HTransform CAN DO THAT**
|
90
|
+
|
91
|
+
input_multiple ["foo", "bar"] => :foo_bar
|
92
|
+
|
93
|
+
By default HTransform will just join the multiple elements with a space. i.e. "foo" and "bar" become "foo bar". Need something more complicated?
|
94
|
+
|
95
|
+
input_multiple ["foo", "bar"] => :foo_bar, :via => lambda { |x| x.map{ |v| v.capitalize }.join(" ") }
|
96
|
+
|
97
|
+
Here, "foo" and "bar" become "Foo Bar". Easy right?
|
98
|
+
|
99
|
+
Hrmm. What about nested hashes? No problem. Just define the nesting order in an array.
|
100
|
+
|
101
|
+
Lets start with nested inputs. Given the hash
|
102
|
+
|
103
|
+
{ "foo" => { "bar" => "sample text" } }
|
104
|
+
|
105
|
+
and a desired hash of
|
106
|
+
|
107
|
+
{ "example" => "sample text" }
|
108
|
+
|
109
|
+
we would give HTransform the following operation
|
110
|
+
|
111
|
+
input ["foo", "bar"] => "example"
|
112
|
+
|
113
|
+
If we want the opposite, all we do is reverse it.
|
114
|
+
|
115
|
+
input "example" => ["foo", "bar"]
|
116
|
+
|
117
|
+
Nested to nested?
|
118
|
+
|
119
|
+
input ["foo", "bar"] => ["baz", "qux"]
|
120
|
+
|
121
|
+
this turns
|
122
|
+
|
123
|
+
{ "foo" => { "bar" => "hello world" } }
|
124
|
+
|
125
|
+
into
|
126
|
+
|
127
|
+
{ "baz" => { "qux" => "hello world" } }
|
128
|
+
|
129
|
+
Want to combine multiple inputs when one or more are an array?
|
130
|
+
|
131
|
+
input [ [ "foo", "bar" ], "baz" ] => "combined nested hash"
|
132
|
+
|
133
|
+
Shorthand Limitations
|
134
|
+
==============
|
135
|
+
|
136
|
+
Hey, the shorthand isn't perfect and theres really only a couple scenarios I can think of. If you have a key on your input hash of :via, you can't use the shorthand.
|
137
|
+
|
138
|
+
input :via => :not_via
|
139
|
+
|
140
|
+
That won't work. Why? **I'm lazy** So just use the long format.
|
141
|
+
|
142
|
+
input :from => :via, :to => :not_via, :via => :capitalize
|
143
|
+
|
144
|
+
However, :from or :to will work just fine in the shorthand. The longhand syntax only works if both the :from and :to symbols are passed in.
|
145
|
+
|
146
|
+
input :from => "from"
|
147
|
+
|
148
|
+
That will work.
|
149
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
|
3
|
+
Bundler::GemHelper.install_tasks
|
4
|
+
|
5
|
+
require "rspec"
|
6
|
+
require "rspec/core/rake_task"
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new do |t|
|
9
|
+
t.rspec_opts = %w(--format documentation --color)
|
10
|
+
t.pattern = 'spec/**/*_spec.rb'
|
11
|
+
end
|
12
|
+
|
13
|
+
task :default => ["spec"]
|
data/examples/example.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'htransform'
|
4
|
+
require 'example_transform'
|
5
|
+
|
6
|
+
in_hash = {
|
7
|
+
"fname" => "JOHN",
|
8
|
+
"lname" => "doe",
|
9
|
+
"phonenum" => "555-123-1122",
|
10
|
+
"email" => "j.doe@example.com",
|
11
|
+
"state_id" => {
|
12
|
+
"number" => "d123456",
|
13
|
+
"state" => "il",
|
14
|
+
"type" => "drivers"
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
desired_hash = {
|
19
|
+
:first_name => "John",
|
20
|
+
:last_name => "Doe",
|
21
|
+
:full_name => "John Doe",
|
22
|
+
:contact_info => {
|
23
|
+
:phone_number => "5551231122",
|
24
|
+
:email_address => "j.doe@example.com"
|
25
|
+
},
|
26
|
+
:state_identification => {
|
27
|
+
:issuing_state => "IL",
|
28
|
+
:type => "drivers",
|
29
|
+
:number => "D123456"
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
result_hash = ExampleTransform.convert(in_hash)
|
34
|
+
|
35
|
+
puts "The Hash Transformation worked correctly!" if result_hash == desired_hash
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'htransform'
|
2
|
+
|
3
|
+
class ExampleTransform < HTransform
|
4
|
+
transform do
|
5
|
+
|
6
|
+
input "fname" => :first_name, :via => :capitalize
|
7
|
+
input :from => "lname", :to => :last_name, :via => lambda { |x| x.capitalize }
|
8
|
+
input "phonenum" => [:contact_info, :phone_number], :via => lambda { |p| p.gsub("-", "") }
|
9
|
+
input "email" => [:contact_info, :email_address]
|
10
|
+
|
11
|
+
input_multiple :from => ["fname", "lname"], :to => :full_name, :via => lambda { |x| x.map{ |v| v.capitalize }.join(" ") }
|
12
|
+
|
13
|
+
input ["state_id", "state"] => [:state_identification, :issuing_state], :via => :upcase
|
14
|
+
input ["state_id", "number"] => [:state_identification, :number], :via => :upcase
|
15
|
+
input ["state_id", "type"] => [:state_identification, :type]
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
data/htransform.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "htransform/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "htransform"
|
7
|
+
s.version = HTransform::VERSION
|
8
|
+
s.authors = ["Josh Krueger"]
|
9
|
+
s.email = ["joshsinbox@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{HTransform transforms your hashes}
|
12
|
+
s.description = %q{HTransform provides a simple DSL to transform arbitrary hashes into another, more arbitrary hash. Yay.}
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_development_dependency("rspec", "~> 2.5.0")
|
20
|
+
s.add_development_dependency("rake")
|
21
|
+
end
|
data/lib/htransform.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
require "htransform/version"
|
2
|
+
|
3
|
+
class HTransform
|
4
|
+
|
5
|
+
ValueNotPresentError = Class.new(StandardError)
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_reader :transform_block
|
9
|
+
|
10
|
+
attr_reader :input_nest
|
11
|
+
attr_reader :output_nest
|
12
|
+
|
13
|
+
def convert(input_hash)
|
14
|
+
new.convert(input_hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
def transform(&trans_block)
|
18
|
+
@transform_block = trans_block
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@output_hash = Hash.new { |h,k| h[k] = Hash.new(&h.default_proc) }
|
25
|
+
@input_nest = []
|
26
|
+
@output_nest = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def convert(input)
|
30
|
+
|
31
|
+
unless input.is_a? Hash
|
32
|
+
if input.respond_to? :to_hash
|
33
|
+
input = input.to_hash
|
34
|
+
else
|
35
|
+
raise ArgumentError "object is not a Hash or does not respond to to_hash"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
@input_hash = input
|
40
|
+
instance_exec(&self.class.transform_block)
|
41
|
+
Hash[@output_hash]
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def set_val(dst, val)
|
47
|
+
|
48
|
+
dst = (@output_nest + [dst]).flatten unless @output_nest.empty?
|
49
|
+
|
50
|
+
if dst.is_a? Array
|
51
|
+
last_key = dst.pop
|
52
|
+
dst.reduce(@output_hash) { |h,k| h[k] }[last_key] = val
|
53
|
+
else
|
54
|
+
@output_hash[dst] = val
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_val(src)
|
59
|
+
return get_val!(src)
|
60
|
+
rescue ValueNotPresentError
|
61
|
+
return nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_val!(src)
|
65
|
+
|
66
|
+
src = (@input_nest + [src]).flatten unless @input_nest.empty?
|
67
|
+
|
68
|
+
if src.is_a? Array
|
69
|
+
src.reduce(@input_hash) { |h,k| h[k] }
|
70
|
+
else
|
71
|
+
if @input_hash.key?(src)
|
72
|
+
@input_hash[src]
|
73
|
+
else
|
74
|
+
raise ValueNotPresentError
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def key_present?(src)
|
80
|
+
src = (@input_nest + [src]).flatten unless @input_nest.empty?
|
81
|
+
if src.is_a? Array
|
82
|
+
src.reduce(@input_hash) do |h, k|
|
83
|
+
h[k].is_a?(Hash) ? h[k] : h.has_key?(k)
|
84
|
+
end
|
85
|
+
else
|
86
|
+
@input_hash.key?(src)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_options(options)
|
91
|
+
from, to, via, default = nil
|
92
|
+
|
93
|
+
via = options.delete :via
|
94
|
+
default = options.delete :default
|
95
|
+
|
96
|
+
from, to = if options.key? :from and options.key? :to
|
97
|
+
[options[:from], options[:to]]
|
98
|
+
else
|
99
|
+
options.delete :from
|
100
|
+
options.delete :to
|
101
|
+
|
102
|
+
lazy_args = options.shift
|
103
|
+
if lazy_args.is_a? Array and lazy_args.length == 2
|
104
|
+
lazy_args
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
[from, to, via, default]
|
109
|
+
end
|
110
|
+
|
111
|
+
def passthrough(*options)
|
112
|
+
options.each do |k|
|
113
|
+
begin
|
114
|
+
input_value = get_val!(k)
|
115
|
+
set_val(k, input_value)
|
116
|
+
rescue ValueNotPresentError
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def input_multiple(options)
|
122
|
+
from, to, via, default = parse_options(options)
|
123
|
+
via = lambda { |x| x.join(" ") } if via.nil?
|
124
|
+
|
125
|
+
|
126
|
+
old_values = from.map{ |k| get_val(k) }
|
127
|
+
new_value = if via.is_a? Proc
|
128
|
+
via.call old_values
|
129
|
+
elsif via.is_a? Symbol
|
130
|
+
if methods.include? via.to_s or methods.include? via
|
131
|
+
via_method = method(via)
|
132
|
+
if via_method.arity != 1
|
133
|
+
via_method.call(*old_values)
|
134
|
+
else
|
135
|
+
via_method.call(old_values)
|
136
|
+
end
|
137
|
+
else
|
138
|
+
old_values.send(via)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
set_val(to, new_value)
|
144
|
+
end
|
145
|
+
|
146
|
+
def input(options)
|
147
|
+
from, to, via, default = parse_options(options)
|
148
|
+
return if !key_present?(from) && default.nil?
|
149
|
+
|
150
|
+
new_value = if get_val(from).nil? && !default.nil?
|
151
|
+
default.respond_to?(:call) ? default.call : default
|
152
|
+
elsif via.is_a? Proc
|
153
|
+
via.call get_val(from)
|
154
|
+
elsif via.is_a? Symbol
|
155
|
+
if methods.include? via.to_s or methods.include? via
|
156
|
+
via_method = method(via)
|
157
|
+
if via_method.arity != 1
|
158
|
+
method(via).call(*get_val(from))
|
159
|
+
else
|
160
|
+
method(via).call(get_val(from))
|
161
|
+
end
|
162
|
+
else
|
163
|
+
get_val(from).send(via)
|
164
|
+
end
|
165
|
+
else
|
166
|
+
get_val(from)
|
167
|
+
end
|
168
|
+
|
169
|
+
set_val(to, new_value)
|
170
|
+
end
|
171
|
+
|
172
|
+
def insert(options)
|
173
|
+
options.each { |key, value| set_val(key, value) }
|
174
|
+
end
|
175
|
+
|
176
|
+
def nested(options, &trans_block)
|
177
|
+
|
178
|
+
if options.is_a? Symbol
|
179
|
+
options = {:key => options, :type => "output"}
|
180
|
+
else
|
181
|
+
options[:type] = "output" unless options.key? :type
|
182
|
+
end
|
183
|
+
|
184
|
+
if options[:type] == "output" or options[:type] == "input"
|
185
|
+
type = options[:type]
|
186
|
+
if options[:key]
|
187
|
+
key = options[:key]
|
188
|
+
nest_var = "@#{type}_nest"
|
189
|
+
self.instance_variable_set(:"#{nest_var}", self.instance_variable_get(:"#{nest_var}") + [key].flatten)
|
190
|
+
|
191
|
+
instance_exec(&trans_block)
|
192
|
+
|
193
|
+
self.instance_variable_set(:"#{nest_var}", self.instance_variable_get(:"#{nest_var}") - [key].flatten)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
describe HTransform do
|
5
|
+
describe "convert" do
|
6
|
+
context "using the nested block" do
|
7
|
+
|
8
|
+
it "defaults to output nesting" do
|
9
|
+
class TestHTransform < HTransform
|
10
|
+
transform do
|
11
|
+
nested :key => :foo_nest do
|
12
|
+
input "foo" => :foo
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
input_hash = { "foo" => "bar" }
|
18
|
+
desired_hash = { :foo_nest => { :foo => "bar" } }
|
19
|
+
|
20
|
+
result = TestHTransform.convert(input_hash)
|
21
|
+
|
22
|
+
result.should == desired_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
it "uses output nested when specified" do
|
26
|
+
class TestHTransform < HTransform
|
27
|
+
transform do
|
28
|
+
nested :type => "output", :key => :foo_nest do
|
29
|
+
input "foo" => :foo
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
input_hash = { "foo" => "bar" }
|
35
|
+
desired_hash = { :foo_nest => { :foo => "bar" } }
|
36
|
+
|
37
|
+
result = TestHTransform.convert(input_hash)
|
38
|
+
|
39
|
+
result.should == desired_hash
|
40
|
+
end
|
41
|
+
|
42
|
+
it "uses input nesting when specified" do
|
43
|
+
class TestHTransform < HTransform
|
44
|
+
transform do
|
45
|
+
nested :type => "input", :key => :nested_data do
|
46
|
+
input "foo" => :foo
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
input_hash = { :nested_data => { "foo" => "bar" } }
|
52
|
+
desired_hash = { :foo => "bar" }
|
53
|
+
|
54
|
+
result = TestHTransform.convert(input_hash)
|
55
|
+
|
56
|
+
result.should == desired_hash
|
57
|
+
end
|
58
|
+
|
59
|
+
it "can nest multiple nested blocks of different types" do
|
60
|
+
class TestHTransform < HTransform
|
61
|
+
transform do
|
62
|
+
nested :type => "input", :key => :nested_input do
|
63
|
+
input :foo_nest => :foo
|
64
|
+
input :bar_nest => :bar
|
65
|
+
|
66
|
+
nested :type => "input", :key => :more_nesting do
|
67
|
+
nested :type => "output", :key => :nested do
|
68
|
+
input :foobar => :foo_bar
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
nested :key => :animal_sounds do
|
74
|
+
passthrough :cat, :dog
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
input_hash = { :nested_input => { :foo_nest => "foo!", :bar_nest => "bar!", :more_nesting => { :foobar => "meh"} },
|
80
|
+
:cat => "meow!", :dog => "woof!" }
|
81
|
+
|
82
|
+
desired_hash = { :foo => "foo!", :bar => "bar!", :nested => { :foo_bar => "meh"} , :animal_sounds => { :cat => "meow!", :dog => "woof!" } }
|
83
|
+
|
84
|
+
result = TestHTransform.convert(input_hash)
|
85
|
+
|
86
|
+
result.should == desired_hash
|
87
|
+
end
|
88
|
+
|
89
|
+
it "can use the shorthand method and default to output nesting" do
|
90
|
+
class TestHTransform < HTransform
|
91
|
+
transform do
|
92
|
+
nested :out_nest do
|
93
|
+
input "foo" => :foo
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
input_hash = { "foo" => "bar" }
|
99
|
+
desired_hash = { :out_nest => { :foo => "bar" } }
|
100
|
+
|
101
|
+
result = TestHTransform.convert(input_hash)
|
102
|
+
|
103
|
+
result.should == desired_hash
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,392 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
describe HTransform do
|
5
|
+
describe "convert" do
|
6
|
+
it "returns real hashes" do
|
7
|
+
class TestHTransform < HTransform
|
8
|
+
transform do
|
9
|
+
input "foo" => :baz
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
input_hash = { "foo" => "bar" }
|
14
|
+
result = TestHTransform.convert(input_hash)
|
15
|
+
result[:missing].should be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it "can call instance methods from :via" do
|
19
|
+
class TestHTransform < HTransform
|
20
|
+
transform do
|
21
|
+
input "foo" => :foo, :via => lambda { |value| my_method(value) }
|
22
|
+
end
|
23
|
+
def my_method(value); value.reverse; end
|
24
|
+
end
|
25
|
+
|
26
|
+
input_hash = { "foo" => "bar" }
|
27
|
+
TestHTransform.convert(input_hash).should == { :foo => "rab" }
|
28
|
+
end
|
29
|
+
|
30
|
+
context "via is a symbol" do
|
31
|
+
it "can call instance methods via a symbol" do
|
32
|
+
class TestHTransform < HTransform
|
33
|
+
transform do
|
34
|
+
input "foo" => :foo, :via => :single_arg_method
|
35
|
+
input_multiple ["num1", "num2"] => :diff, :via => :multi_arg_method
|
36
|
+
input :nums => :diff2, :via => :multi_arg_method
|
37
|
+
input :nums => :splat_1, :via => :splat_method
|
38
|
+
input "foo" => :foo_splat, :via => :splat_method
|
39
|
+
end
|
40
|
+
|
41
|
+
def single_arg_method(value); value.reverse; end
|
42
|
+
def multi_arg_method(value1, value2); value1 - value2; end
|
43
|
+
def splat_method(*arg); arg.join(", "); end
|
44
|
+
end
|
45
|
+
|
46
|
+
input_hash = { "foo" => "bar", "num1" => 100, "num2" => 50, :nums => [100, 50] }
|
47
|
+
|
48
|
+
desired_hash = {:foo => "rab", :diff => 50, :diff2 => 50, :splat_1 => "100, 50", :foo_splat => "bar" }
|
49
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "the input key is missing" do
|
54
|
+
it "does not set it in the output" do
|
55
|
+
class TestHTransform < HTransform
|
56
|
+
transform do
|
57
|
+
input "birth_date" => :birthday, :via => lambda { |d| d.strftime('%F') }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
input_hash = { "foo" => "bar" }
|
62
|
+
TestHTransform.convert(input_hash).should == {}
|
63
|
+
end
|
64
|
+
|
65
|
+
context "the input key is present but the value is nil" do
|
66
|
+
it "does set it in the input" do
|
67
|
+
class TestHTransform < HTransform
|
68
|
+
transform do
|
69
|
+
input "foo" => :bar
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
input_hash = { "foo" => nil }
|
74
|
+
desired_hash = { :bar => nil }
|
75
|
+
|
76
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it "can set a default value" do
|
81
|
+
class TestHTransform < HTransform
|
82
|
+
transform do
|
83
|
+
input "birth_date" => :birthday, :default => Date.today
|
84
|
+
input "death_date" => :deathday, :default => lambda { Date.today + 5 }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
input_hash = { "foo" => "bar" }
|
89
|
+
desired_hash = { :birthday => Date.today, :deathday => Date.today + 5 }
|
90
|
+
|
91
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "inserting keys" do
|
96
|
+
it "should insert the key regardless of the input hash" do
|
97
|
+
class TestHTransform < HTransform
|
98
|
+
transform do
|
99
|
+
insert :bar => 'bar', :quux => 'quux'
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
input_hash = { "foo" => "foo" }
|
104
|
+
desired_hash = { :bar => 'bar', :quux => 'quux' }
|
105
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "the value has no transform" do
|
110
|
+
it "should produce the same key (string)" do
|
111
|
+
class TestHTransform < HTransform
|
112
|
+
transform do
|
113
|
+
input :from => "foo", :to => "foo"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
input_hash = { "foo" => "bar" }
|
118
|
+
|
119
|
+
TestHTransform.convert(input_hash).should == input_hash
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should produce the same key via shorthand (symbol)" do
|
123
|
+
class TestHTransform < HTransform
|
124
|
+
transform do
|
125
|
+
input :foo => :foo
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
input_hash = { :foo => :bar }
|
130
|
+
|
131
|
+
TestHTransform.convert(input_hash).should == input_hash
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should change the key (string to symbol)" do
|
135
|
+
class TestHTransform < HTransform
|
136
|
+
transform do
|
137
|
+
input "foo" => :foo
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
input_hash = { "foo" => "bar" }
|
142
|
+
desired_hash = { :foo => "bar" }
|
143
|
+
|
144
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should change the key (symbox to key)" do
|
148
|
+
class TestHTransform < HTransform
|
149
|
+
transform do
|
150
|
+
input :foo => "foo"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
desired_hash = { "foo" => "bar" }
|
155
|
+
input_hash = { :foo => "bar" }
|
156
|
+
|
157
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
context "the value should be capitalized" do
|
163
|
+
it "should produce the same key" do
|
164
|
+
class TestHTransform < HTransform
|
165
|
+
transform do
|
166
|
+
input "foo" => "foo", :via => :capitalize
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
input_hash = { "foo" => "bar" }
|
171
|
+
desired_hash = { "foo" => "Bar" }
|
172
|
+
|
173
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should produce a different key" do
|
177
|
+
class TestHTransform < HTransform
|
178
|
+
transform do
|
179
|
+
input "foo" => :foo, :via => lambda { |x| x.capitalize }
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
input_hash = { "foo" => "bar" }
|
184
|
+
desired_hash = { :foo => "Bar" }
|
185
|
+
|
186
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
context "use nested input" do
|
192
|
+
it "should produce the same key without nesting" do
|
193
|
+
class TestHTransform < HTransform
|
194
|
+
transform do
|
195
|
+
input ["foo", "bar"] => "bar"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
input_hash = { "foo" => { "bar" => "FOOBAR!" } }
|
200
|
+
desired_hash = { "bar" => "FOOBAR!" }
|
201
|
+
|
202
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should produce a different key without nesting" do
|
206
|
+
class TestHTransform < HTransform
|
207
|
+
transform do
|
208
|
+
input ["foo", "bar"] => :foo_bar
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
input_hash = { "foo" => { "bar" => "FOOBAR!" } }
|
213
|
+
desired_hash = { :foo_bar => "FOOBAR!" }
|
214
|
+
|
215
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context "use nested output" do
|
220
|
+
it "should produce the same key, but nested" do
|
221
|
+
class TestHTransform < HTransform
|
222
|
+
transform do
|
223
|
+
input :foo => [:bar, :foo]
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
input_hash = { :foo => "FOO!" }
|
228
|
+
desired_hash = { :bar => { :foo => "FOO!" } }
|
229
|
+
|
230
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should produce a different key, but nested" do
|
234
|
+
class TestHTransform < HTransform
|
235
|
+
transform do
|
236
|
+
input "foo" => [:bar, :foo]
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
input_hash = { "foo" => "FOO!" }
|
241
|
+
desired_hash = { :bar => { :foo => "FOO!" } }
|
242
|
+
|
243
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
244
|
+
end
|
245
|
+
|
246
|
+
it "should produce a different key, but nested (with string keys)" do
|
247
|
+
class TestHTransform < HTransform
|
248
|
+
transform do
|
249
|
+
input "foo" => ["bar", "foo"]
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
input_hash = { "foo" => "FOO!" }
|
254
|
+
desired_hash = { "bar" => { "foo" => "FOO!" } }
|
255
|
+
|
256
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should use nested input and produce the same key, but nested differently" do
|
260
|
+
class TestHTransform < HTransform
|
261
|
+
transform do
|
262
|
+
input ["foo", "bar"] => ["baz", "bar"]
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
input_hash = { "foo" => { "bar" => "BAR!" } }
|
267
|
+
desired_hash = { "baz" => { "bar" => "BAR!" } }
|
268
|
+
|
269
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
270
|
+
end
|
271
|
+
|
272
|
+
it "should use nested input and produce a different key, but nested differently" do
|
273
|
+
class TestHTransform < HTransform
|
274
|
+
transform do
|
275
|
+
input ["foo", "bar"] => [:baz, :bar]
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
input_hash = { "foo" => { "bar" => "BAR!" } }
|
280
|
+
desired_hash = { :baz => { :bar => "BAR!" } }
|
281
|
+
|
282
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
context "use multiple inputs" do
|
287
|
+
it "should join two elements with a space and produce a single key" do
|
288
|
+
class TestHTransform < HTransform
|
289
|
+
transform do
|
290
|
+
input_multiple [:foo, :bar] => :foo_bar
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
input_hash = { :foo => "Foo", :bar => "Bar" }
|
295
|
+
desired_hash = { :foo_bar => "Foo Bar" }
|
296
|
+
|
297
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
298
|
+
end
|
299
|
+
|
300
|
+
it "should join two nested elements with a dash, via a lambda and produce a single key" do
|
301
|
+
class TestHTransform < HTransform
|
302
|
+
transform do
|
303
|
+
input_multiple [[:foo, :bar], [:baz, :qux]] => :bar_qux, :via => lambda { |x| x.join("-") }
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
input_hash = { :foo => { :bar => "fbar" }, :baz => { :qux => "bqux" } }
|
308
|
+
desired_hash = { :bar_qux => "fbar-bqux" }
|
309
|
+
|
310
|
+
TestHTransform.convert(input_hash).should == desired_hash
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
|
316
|
+
context "multiple htransforms" do
|
317
|
+
it "does not share transformations between unrelated subclasses" do
|
318
|
+
class HTransformA < HTransform
|
319
|
+
transform do
|
320
|
+
input "foo" => :foo
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
class HTransformB < HTransform
|
325
|
+
transform do
|
326
|
+
input "bar" => :bar
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
input_hash = { "foo" => "fooval", "bar" => "barval" }
|
331
|
+
HTransformA.convert(input_hash).should == { :foo => "fooval" }
|
332
|
+
HTransformB.convert(input_hash).should == { :bar => "barval" }
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
context "passthrough" do
|
337
|
+
|
338
|
+
it "ignores a passthrough'd key if it is absent" do
|
339
|
+
class TestHTransform < HTransform
|
340
|
+
transform do
|
341
|
+
passthrough :foo
|
342
|
+
input :bar => :bar_key
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
input_hash = { :bar => 'one' }
|
347
|
+
desired_hash = { :bar_key => 'one' }
|
348
|
+
|
349
|
+
result = TestHTransform.convert(input_hash)
|
350
|
+
result.should == desired_hash
|
351
|
+
end
|
352
|
+
|
353
|
+
it "does not ignore the passthrough key if it is present but nil" do
|
354
|
+
class TestHTransform < HTransform
|
355
|
+
transform do
|
356
|
+
passthrough :foo
|
357
|
+
input :bar => :bar_key
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
input_hash = { :bar => 'one', :foo => nil }
|
362
|
+
desired_hash = { :bar_key => 'one', :foo => nil }
|
363
|
+
|
364
|
+
result = TestHTransform.convert(input_hash)
|
365
|
+
result.should == desired_hash
|
366
|
+
end
|
367
|
+
|
368
|
+
end
|
369
|
+
|
370
|
+
context "non-hashes" do
|
371
|
+
it "works with objects that respond to to_hash" do
|
372
|
+
class TestHTransform < HTransform
|
373
|
+
transform do
|
374
|
+
passthrough :foo
|
375
|
+
input :bar => :bar_key
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
class NonHash
|
380
|
+
def to_hash
|
381
|
+
{ :foo => "FOO", :bar => 'BAR' }
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
input_object = NonHash.new
|
386
|
+
desired_hash = { :bar_key => 'BAR', :foo => "FOO" }
|
387
|
+
|
388
|
+
result = TestHTransform.convert(input_object)
|
389
|
+
result.should == desired_hash
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'htransform'
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: htransform
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.8.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josh Krueger
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-08-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.5.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.5.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: HTransform provides a simple DSL to transform arbitrary hashes into another,
|
47
|
+
more arbitrary hash. Yay.
|
48
|
+
email:
|
49
|
+
- joshsinbox@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- Gemfile
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- examples/example.rb
|
59
|
+
- examples/example_htransform.rb
|
60
|
+
- htransform.gemspec
|
61
|
+
- lib/htransform.rb
|
62
|
+
- lib/htransform/version.rb
|
63
|
+
- spec/htransform_nested_spec.rb
|
64
|
+
- spec/htransform_spec.rb
|
65
|
+
- spec/spec_helper.rb
|
66
|
+
homepage: ''
|
67
|
+
licenses: []
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.8.23
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: HTransform transforms your hashes
|
90
|
+
test_files:
|
91
|
+
- spec/htransform_nested_spec.rb
|
92
|
+
- spec/htransform_spec.rb
|
93
|
+
- spec/spec_helper.rb
|
94
|
+
has_rdoc:
|