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.
@@ -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
@@ -1,133 +1,102 @@
1
- require 'spec_helper'
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, *args)
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
- data = {
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 serialize and deserialize a hash' do
59
- json = serializer.to_json
60
- result = deserializer.from_json(json)
61
- result.must_equal Hash['id' => data['id'], 'name' => data['name']]
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 serialize and deserialize a hash with root' do
65
- json = serializer.to_json :root => 'my'
66
- result = deserializer.from_json(json, :root => 'my')
67
- result.must_equal Hash['id' => data['id'], 'name' => data['name']]
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 serialize and deserialize a hash with include list' do
71
- json = serializer.to_json(:include => ['address', 'phone_numbers'])
72
- result = deserializer.from_json(json, :include =>
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 = deserializer.from_json(json, :include =>
99
- { 'address' => {:only => ['street']}})
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 serialize and deserialize a hash with nested except' do
106
- json = serializer.to_json(:include => { 'address' =>
107
- {:except => ['zipcode']}})
108
- data.delete('phone_numbers')
109
- data['address'].delete('zipcode')
110
- result = deserializer.from_json(json, :include => { 'address' =>
111
- {:except => ['zipcode']}})
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 convert elements from arrays wth custom serializer' do
127
- serializer.add_custom_serializers( "Symbol" =>
128
- Proc.new {|v| v.to_s.capitalize } )
129
- data['children_names'] = [:adi, :aromal, :shreedev]
130
- d = serializer.to_hash(:include => [ :children_names ])
131
- d[ "children_names"].must_equal( ["Adi", "Aromal", "Shreedev"] )
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