ixtlan-babel 0.5.0 → 0.7.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/Gemfile +4 -1
- data/MIT-LICENSE +1 -1
- data/README.md +3 -14
- data/Rakefile +27 -0
- data/lib/ixtlan-babel.rb +0 -21
- data/lib/ixtlan-babel.rb~ +1 -0
- data/lib/ixtlan/babel/common_filters.rb +21 -0
- data/lib/ixtlan/babel/common_filters.rb~ +14 -0
- data/lib/ixtlan/babel/factory.rb +36 -56
- data/lib/ixtlan/babel/factory.rb~ +39 -59
- data/lib/ixtlan/babel/hash_filter.rb +125 -44
- data/lib/ixtlan/babel/hash_filter.rb~ +149 -40
- data/lib/ixtlan/babel/model_filter.rb~ +114 -52
- data/lib/ixtlan/babel/model_serializer.rb +103 -0
- data/lib/ixtlan/babel/model_serializer.rb~ +104 -0
- data/spec/hash_filter_spec.rb +71 -102
- data/spec/model_serializer_spec.rb +159 -0
- data/spec/model_serializer_spec.rb~ +158 -0
- data/spec/spec_helper.rb +5 -3
- data/spec/spec_helper.rb~ +8 -0
- metadata +53 -72
- data/lib/ixtlan/babel/abstract_filter.rb +0 -46
- data/lib/ixtlan/babel/context.rb +0 -67
- data/lib/ixtlan/babel/dm_validation_errors_serializer.rb +0 -8
- data/lib/ixtlan/babel/filter_config.rb +0 -74
- data/lib/ixtlan/babel/model_filter.rb +0 -79
- data/lib/ixtlan/babel/params_filter.rb +0 -149
- data/lib/ixtlan/babel/serializer.rb +0 -186
- data/spec/model_filter_spec.rb +0 -131
- data/spec/model_filter_with_dsl_spec.rb +0 -173
- data/spec/model_filter_with_methods_spec.rb +0 -151
- data/spec/params_filter_dsl_spec.rb +0 -134
- data/spec/params_filter_spec.rb +0 -113
@@ -0,0 +1,104 @@
|
|
1
|
+
module Ixtlan
|
2
|
+
module Babel
|
3
|
+
|
4
|
+
module ModelSerializer
|
5
|
+
|
6
|
+
attr_reader :model
|
7
|
+
|
8
|
+
def self.included( model )
|
9
|
+
model.extend( ClassMethods )
|
10
|
+
end
|
11
|
+
|
12
|
+
def respond_to? name
|
13
|
+
@model.respond_to? name
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing( name, *args, &block )
|
17
|
+
@model.send( name, *args, &block )
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_json
|
21
|
+
to_data.to_json
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_yaml
|
25
|
+
to_data.to_yaml
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_data
|
29
|
+
if @model.respond_to?( :collect ) and not @model.is_a?( Hash )
|
30
|
+
@model.collect do |m|
|
31
|
+
replace( m ).to_hash
|
32
|
+
end
|
33
|
+
else
|
34
|
+
to_hash
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def serializers=( map )
|
39
|
+
@map = map
|
40
|
+
end
|
41
|
+
|
42
|
+
def serialize( data )
|
43
|
+
if @map && ser = @map[ data.class.to_s ]
|
44
|
+
p ser
|
45
|
+
ser.call(data)
|
46
|
+
else
|
47
|
+
data
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_hash
|
52
|
+
result = {}
|
53
|
+
self.class.attributes.each do |k,v|
|
54
|
+
if v
|
55
|
+
filter = v.is_a?( Array ) ? v[ 0 ] : v
|
56
|
+
filter.serializers = @map
|
57
|
+
model = @model.send( k )
|
58
|
+
result[ k ] = filter.replace( model ).to_data if model
|
59
|
+
else
|
60
|
+
result[ k ] = serialize( @model.send( k ) )
|
61
|
+
end
|
62
|
+
end
|
63
|
+
result
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize( model = nil, map = nil )
|
67
|
+
super()
|
68
|
+
@map = map
|
69
|
+
replace( model )
|
70
|
+
end
|
71
|
+
|
72
|
+
def replace( model )
|
73
|
+
@model = model
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
module ClassMethods
|
78
|
+
|
79
|
+
def attribute( name, type = nil )
|
80
|
+
attributes[ name.to_sym ] = new_instance( type )
|
81
|
+
end
|
82
|
+
|
83
|
+
def attributes( *args )
|
84
|
+
if args.size == 0
|
85
|
+
@attributes ||= (superclass.attributes.dup rescue nil) || {}
|
86
|
+
else
|
87
|
+
args.each { |a| attribute( a ) }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def new_instance( type )
|
92
|
+
case type
|
93
|
+
when Array
|
94
|
+
[ type[ 0 ].new ]
|
95
|
+
when NilClass
|
96
|
+
nil
|
97
|
+
else
|
98
|
+
type.new
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/spec/hash_filter_spec.rb
CHANGED
@@ -1,133 +1,102 @@
|
|
1
|
-
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require 'ixtlan/babel/hash_filter'
|
3
|
+
require 'yaml'
|
4
|
+
class A; end
|
5
|
+
class AFilter
|
6
|
+
include Ixtlan::Babel::HashFilter
|
7
|
+
|
8
|
+
attributes :id, :name
|
9
|
+
end
|
10
|
+
class HiddenFilter < AFilter
|
11
|
+
attributes.delete( :id )
|
12
|
+
hidden :id
|
13
|
+
end
|
14
|
+
class AddressFilter
|
15
|
+
include Ixtlan::Babel::HashFilter
|
16
|
+
attributes :street
|
17
|
+
end
|
18
|
+
class PhoneFilter
|
19
|
+
include Ixtlan::Babel::HashFilter
|
20
|
+
attributes :prefix, :number
|
21
|
+
end
|
22
|
+
class AreaFilter
|
23
|
+
include Ixtlan::Babel::HashFilter
|
24
|
+
attributes :code, :iso
|
25
|
+
end
|
26
|
+
class Phone2Filter < PhoneFilter
|
27
|
+
attribute :area, AreaFilter
|
28
|
+
end
|
29
|
+
class NestedFilter < AFilter
|
30
|
+
attribute :address, AddressFilter
|
31
|
+
end
|
32
|
+
class NestedArrayFilter < AFilter
|
33
|
+
attribute :phone_numbers, Array[PhoneFilter]
|
34
|
+
end
|
35
|
+
class DeepNestedFilter < NestedArrayFilter
|
36
|
+
attribute :phone_numbers, Array[Phone2Filter]
|
37
|
+
end
|
2
38
|
|
3
39
|
class Hash
|
4
40
|
def attributes
|
5
41
|
self
|
6
42
|
end
|
7
|
-
def method_missing(method
|
43
|
+
def method_missing(method)
|
8
44
|
self[method.to_s]
|
9
45
|
end
|
10
46
|
end
|
11
47
|
|
12
48
|
describe Ixtlan::Babel::HashFilter do
|
13
49
|
let(:data) do
|
14
|
-
|
50
|
+
{
|
15
51
|
'id' => 987,
|
16
52
|
'name' => 'me and the corner',
|
17
53
|
'address' => { 'street' => 'Foo 12', 'zipcode' => '12345' },
|
18
|
-
'phone_numbers' => {
|
54
|
+
'phone_numbers' => [ {
|
19
55
|
'prefix' => 12,
|
20
56
|
'number' => '123',
|
21
57
|
'area' => { 'code' => '001', 'iso' => 'us'}
|
22
|
-
}
|
58
|
+
} ]
|
23
59
|
}
|
24
|
-
class Hash
|
25
|
-
def self.new(hash = nil, &block)
|
26
|
-
if hash
|
27
|
-
self[hash]
|
28
|
-
else
|
29
|
-
super &block
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
data
|
34
|
-
end
|
35
|
-
|
36
|
-
let(:serializer) { Ixtlan::Babel::Serializer.new(data) }
|
37
|
-
let(:deserializer) do
|
38
|
-
f = Ixtlan::Babel::HashFilter.new
|
39
|
-
def f.from_json( json, options = nil )
|
40
|
-
data = MultiJson.load(json)
|
41
|
-
self.options = options || {}
|
42
|
-
if data.is_a? Array
|
43
|
-
if filter.options[:root]
|
44
|
-
data.collect do |d|
|
45
|
-
Hash.new( self.filter( d[ self.options[:root] ] ) )
|
46
|
-
end
|
47
|
-
else
|
48
|
-
data.collect{ |d| Hash.new( self.filter( d ) ) }
|
49
|
-
end
|
50
|
-
else
|
51
|
-
data = data[ self.options[:root] ] if self.options[:root]
|
52
|
-
Hash.new( self.filter( data ) )
|
53
|
-
end
|
54
|
-
end
|
55
|
-
f
|
56
60
|
end
|
57
61
|
|
58
|
-
it 'should
|
59
|
-
|
60
|
-
result
|
61
|
-
result.must_equal
|
62
|
+
it 'should filter a hash' do
|
63
|
+
result = AFilter.new.replace( data )
|
64
|
+
result.params.must_equal Hash[ 'id' => data['id'], 'name' => data['name'] ]
|
65
|
+
result.id.must_equal data['id']
|
66
|
+
result.name.must_equal data['name']
|
62
67
|
end
|
63
68
|
|
64
|
-
it 'should
|
65
|
-
|
66
|
-
result
|
67
|
-
result.must_equal
|
69
|
+
it 'should filter a hash with hidden' do
|
70
|
+
result = HiddenFilter.new.replace( data )
|
71
|
+
result.params.must_equal Hash[ 'name' => data['name'] ]
|
72
|
+
result.id.must_equal data['id']
|
73
|
+
result.name.must_equal data['name']
|
68
74
|
end
|
69
75
|
|
70
|
-
it 'should
|
71
|
-
|
72
|
-
|
73
|
-
['address', 'phone_numbers'])
|
74
|
-
data['phone_numbers'].delete('area')
|
75
|
-
result.must_equal Hash[data]
|
76
|
-
end
|
77
|
-
|
78
|
-
it 'should serialize and deserialize a hash with except' do
|
79
|
-
json = serializer.to_json(:except => ['id'])
|
80
|
-
result = deserializer.from_json(json, :except => ['id'])
|
81
|
-
result.must_equal Hash['name' => data['name']]
|
82
|
-
result = deserializer.from_json(json)
|
83
|
-
result.must_equal Hash['name' => data['name']]
|
84
|
-
end
|
85
|
-
|
86
|
-
it 'should serialize and deserialize a hash with only' do
|
87
|
-
json = serializer.to_json(:only => ['name'])
|
88
|
-
result = deserializer.from_json(json, :only => ['name'])
|
89
|
-
result.must_equal Hash['name' => data['name']]
|
90
|
-
result = deserializer.from_json(json)
|
91
|
-
result.must_equal Hash['name' => data['name']]
|
92
|
-
end
|
93
|
-
|
94
|
-
it 'should serialize and deserialize a hash with nested only' do
|
95
|
-
json = serializer.to_json(:include => { 'address' => {:only => ['street']}})
|
96
|
-
data.delete('phone_numbers')
|
76
|
+
it 'should filter a hash with nested' do
|
77
|
+
result = NestedFilter.new.replace( data )
|
78
|
+
data.delete( 'phone_numbers' )
|
97
79
|
data['address'].delete('zipcode')
|
98
|
-
result
|
99
|
-
|
100
|
-
result.must_equal data
|
101
|
-
result = deserializer.from_json(json, :include => ['address'])
|
102
|
-
result.must_equal data
|
80
|
+
result.params.to_yaml.must_equal data.to_yaml
|
81
|
+
result.address.street.must_equal data['address']['street']
|
103
82
|
end
|
104
83
|
|
105
|
-
it 'should
|
106
|
-
|
107
|
-
|
108
|
-
data.delete('
|
109
|
-
data
|
110
|
-
result
|
111
|
-
|
112
|
-
result.must_equal data
|
113
|
-
result = deserializer.from_json(json, :include => ['address'])
|
114
|
-
result.must_equal data
|
115
|
-
end
|
116
|
-
|
117
|
-
it 'should serialize and deserialize a hash with nested include' do
|
118
|
-
json = serializer.to_json(:include => { 'address' => {},
|
119
|
-
'phone_numbers' => { :include => ['area']}})
|
120
|
-
result = deserializer.from_json(json, :include => { 'address' => {},
|
121
|
-
'phone_numbers' => {
|
122
|
-
:include => ['area']}})
|
123
|
-
result.must_equal data
|
84
|
+
it 'should filter a hash with nested array' do
|
85
|
+
result = NestedArrayFilter.new.replace( data )
|
86
|
+
data.delete( 'address' )
|
87
|
+
data['phone_numbers'].delete('area')
|
88
|
+
result.params.to_yaml.must_equal data.to_yaml
|
89
|
+
result.phone_numbers[0].prefix.must_equal data['phone_numbers'][0]['prefix']
|
90
|
+
result.phone_numbers[0].number.must_equal data['phone_numbers'][0]['number']
|
124
91
|
end
|
125
92
|
|
126
|
-
it 'should
|
127
|
-
|
128
|
-
|
129
|
-
data
|
130
|
-
|
131
|
-
|
93
|
+
it 'should filter a hash with deep nested' do
|
94
|
+
result = DeepNestedFilter.new.replace( data )
|
95
|
+
data.delete( 'address' )
|
96
|
+
result.params.to_yaml.must_equal data.to_yaml
|
97
|
+
result.phone_numbers[0].prefix.must_equal data['phone_numbers'][0]['prefix']
|
98
|
+
result.phone_numbers[0].number.must_equal data['phone_numbers'][0]['number']
|
99
|
+
result.phone_numbers[0].area.code.must_equal data['phone_numbers'][0]['area']['code']
|
100
|
+
result.phone_numbers[0].area.iso.must_equal data['phone_numbers'][0]['area']['iso']
|
132
101
|
end
|
133
102
|
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require 'ixtlan/babel/model_serializer'
|
3
|
+
require 'virtus'
|
4
|
+
require 'multi_json'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
Model = Virtus.model
|
8
|
+
class Address
|
9
|
+
include Model
|
10
|
+
|
11
|
+
attribute :street, String
|
12
|
+
attribute :zipcode, String
|
13
|
+
end
|
14
|
+
class Area
|
15
|
+
include Model
|
16
|
+
|
17
|
+
attribute :code, String
|
18
|
+
attribute :iso, String
|
19
|
+
end
|
20
|
+
class PhoneNumber
|
21
|
+
include Model
|
22
|
+
|
23
|
+
attribute :prefix, Integer
|
24
|
+
attribute :number, String
|
25
|
+
attribute :area, Area
|
26
|
+
end
|
27
|
+
class Person
|
28
|
+
include Model
|
29
|
+
|
30
|
+
attribute :id, String
|
31
|
+
attribute :name, String
|
32
|
+
attribute :address, Address
|
33
|
+
|
34
|
+
attr_accessor :phone_numbers, :age, :children_names
|
35
|
+
|
36
|
+
def phone_numbers
|
37
|
+
@phone_numbers ||= [PhoneNumber.new(
|
38
|
+
:prefix => 12,
|
39
|
+
:number => '123',
|
40
|
+
:area => Area.new( :code => '001', :iso => 'us' ) )]
|
41
|
+
end
|
42
|
+
|
43
|
+
def age
|
44
|
+
@age ||= 123
|
45
|
+
end
|
46
|
+
|
47
|
+
def children_names
|
48
|
+
@children_names ||= ['anna', 'jack', 'rama', 'mia']
|
49
|
+
end
|
50
|
+
|
51
|
+
def children_ages
|
52
|
+
@children_ages ||= [12, 3, 6, 9]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class ASerializer
|
57
|
+
include Ixtlan::Babel::ModelSerializer
|
58
|
+
attributes :id, :name
|
59
|
+
end
|
60
|
+
|
61
|
+
class MethodsSerializer < ASerializer
|
62
|
+
attributes :age, :children_names, :children_ages
|
63
|
+
end
|
64
|
+
|
65
|
+
class PhoneSerializer
|
66
|
+
include Ixtlan::Babel::ModelSerializer
|
67
|
+
attributes :prefix, :number
|
68
|
+
end
|
69
|
+
|
70
|
+
class AddressSerializer
|
71
|
+
include Ixtlan::Babel::ModelSerializer
|
72
|
+
attributes :street, :zipcode
|
73
|
+
end
|
74
|
+
|
75
|
+
class NestedListSerializer < ASerializer
|
76
|
+
attribute :address, AddressSerializer
|
77
|
+
attribute :phone_numbers, Array[PhoneSerializer]
|
78
|
+
end
|
79
|
+
class AreaSerializer
|
80
|
+
include Ixtlan::Babel::ModelSerializer
|
81
|
+
attributes :code, :iso
|
82
|
+
end
|
83
|
+
|
84
|
+
class Phone2Serializer < PhoneSerializer
|
85
|
+
attribute :area, AreaSerializer
|
86
|
+
end
|
87
|
+
class DeepNestedSerializer < NestedListSerializer
|
88
|
+
attribute :phone_numbers, Array[Phone2Serializer]
|
89
|
+
end
|
90
|
+
|
91
|
+
describe Ixtlan::Babel::ModelSerializer do
|
92
|
+
let( :person ) do
|
93
|
+
Person.new( :id => 987,
|
94
|
+
:name => 'me and the corner',
|
95
|
+
:address => Address.new( :street => 'Foo 12',
|
96
|
+
:zipcode => '12345' ) )
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should serialize with methods' do
|
100
|
+
data = MethodsSerializer.new( person )
|
101
|
+
result = MultiJson.load(data.to_json)
|
102
|
+
result.must_equal Hash[ "id"=>"987",
|
103
|
+
"name"=>"me and the corner",
|
104
|
+
"age"=>123,
|
105
|
+
"children_names"=> [ "anna",
|
106
|
+
"jack",
|
107
|
+
"rama",
|
108
|
+
"mia" ],
|
109
|
+
"children_ages"=>[ 12, 3, 6, 9 ] ]
|
110
|
+
data.id.must_equal person.id
|
111
|
+
data.name.must_equal person.name
|
112
|
+
data.age.must_equal person.age
|
113
|
+
data.children_ages.must_equal person.children_ages
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should serialize' do
|
117
|
+
data = ASerializer.new( person )
|
118
|
+
result = MultiJson.load(data.to_json)
|
119
|
+
result.must_equal Hash[ "id"=>"987", "name"=>"me and the corner" ]
|
120
|
+
data.id.must_equal person.id
|
121
|
+
data.name.must_equal person.name
|
122
|
+
data.age.must_equal person.age
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should serialize with nested list' do
|
126
|
+
data = NestedListSerializer.new( person )
|
127
|
+
result = MultiJson.load( data.to_json )
|
128
|
+
result.must_equal Hash[ "id"=>"987", "name"=>"me and the corner" ,
|
129
|
+
"address"=> {
|
130
|
+
"street"=>"Foo 12",
|
131
|
+
"zipcode"=>"12345"
|
132
|
+
},
|
133
|
+
"phone_numbers"=> [ { "prefix"=>12,
|
134
|
+
"number"=>"123" } ] ]
|
135
|
+
data.phone_numbers[0].prefix.must_equal result['phone_numbers'][0]['prefix']
|
136
|
+
data.phone_numbers[0].number.must_equal result['phone_numbers'][0]['number']
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should serialize and deserialize with nested include' do
|
140
|
+
data = DeepNestedSerializer.new( person )
|
141
|
+
result = MultiJson.load( data.to_json )
|
142
|
+
result.must_equal Hash[ "id"=>"987", "name"=>"me and the corner" ,
|
143
|
+
"address"=> {
|
144
|
+
"street"=>"Foo 12",
|
145
|
+
"zipcode"=>"12345"
|
146
|
+
},
|
147
|
+
"phone_numbers"=> [ { "prefix"=>12,
|
148
|
+
"number"=>"123",
|
149
|
+
"area"=> {
|
150
|
+
"code"=>"001",
|
151
|
+
"iso"=>"us"
|
152
|
+
}
|
153
|
+
} ] ]
|
154
|
+
data.phone_numbers[0].prefix.must_equal result['phone_numbers'][0]['prefix']
|
155
|
+
data.phone_numbers[0].number.must_equal result['phone_numbers'][0]['number']
|
156
|
+
data.phone_numbers[0].area.code.must_equal result['phone_numbers'][0]['area']['code']
|
157
|
+
data.phone_numbers[0].area.iso.must_equal result['phone_numbers'][0]['area']['iso']
|
158
|
+
end
|
159
|
+
end
|