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.
@@ -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