ixtlan-babel 0.5.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|