candywrapper 0.0.1
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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +173 -0
- data/Rakefile +22 -0
- data/candywrapper.gemspec +19 -0
- data/lib/candywrapper.rb +136 -0
- data/lib/candywrapper/version.rb +3 -0
- data/test/unit/basic_test.rb +165 -0
- metadata +66 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2012 Steve Jang
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Candywrapper
|
|
2
|
+
|
|
3
|
+
This gem provides simple wrapper around a regular ruby Hash object.
|
|
4
|
+
This wrapper provides the following functionality:
|
|
5
|
+
- DSL for defining arbitrarily deep serializable structure
|
|
6
|
+
- JSON serialization
|
|
7
|
+
|
|
8
|
+
This gem is useful when you want to serialize/deserialize Ruby
|
|
9
|
+
objects to store or to transmit them over network. This gem was designed
|
|
10
|
+
with the following advantages in mind:
|
|
11
|
+
|
|
12
|
+
1. You can mix-in the functionality to existing Ruby objects.
|
|
13
|
+
|
|
14
|
+
2. DSL explicitly defines the protocol payload
|
|
15
|
+
- Serialization only includes whatever is defined via DSL.
|
|
16
|
+
All other object states are ignored. This clearly delineates
|
|
17
|
+
object states that are transient from those that are to be
|
|
18
|
+
preserved across serialization.
|
|
19
|
+
- Attributes defined via Candywrapper DSL are called
|
|
20
|
+
"serializable attributes".
|
|
21
|
+
|
|
22
|
+
3. Light-weight and fast
|
|
23
|
+
- Serialization payload is represented by a single hash object
|
|
24
|
+
(containing nested hashes). There is no other bookkeeping
|
|
25
|
+
going on other than that.
|
|
26
|
+
- (De-)Serialization is fast since we just use C-compiled JSON
|
|
27
|
+
gem of the 1 hash object. We never recursively traverse the
|
|
28
|
+
nested hash structure, since doing that in Ruby would be
|
|
29
|
+
50-70 times slower than in C.
|
|
30
|
+
|
|
31
|
+
4. You can nest candywrappers inside candywrappers.
|
|
32
|
+
- If you nest candywrapper object 2 and object 3 inside
|
|
33
|
+
candywrapper object 1, there is still only 1 nested hash
|
|
34
|
+
object wrapped by object 1. In this case, the hash object
|
|
35
|
+
used by objects 2 and 3 are nested inside the hash object
|
|
36
|
+
used by object 1. This means that we only ever pass 1 hash
|
|
37
|
+
object to json generator during serialization.
|
|
38
|
+
- What happens to object 2 and object 3 in the above story?
|
|
39
|
+
Those objects are just wrappers around the nested (sub-)
|
|
40
|
+
hashes of object 1's hash. All these relationships / references
|
|
41
|
+
are correctly maintained within the accessor methods.
|
|
42
|
+
|
|
43
|
+
Given this design, please be aware that:
|
|
44
|
+
|
|
45
|
+
1. Any object state that you want to save or send should be
|
|
46
|
+
stored in the wrapped hash object. (see usage)
|
|
47
|
+
|
|
48
|
+
2. The only types that can be stored as serializable attribute
|
|
49
|
+
are basic JSON primitives, or other candywrapper objects.
|
|
50
|
+
(see usage) For example, you cannot store a File object in
|
|
51
|
+
a candywrapper attribute.
|
|
52
|
+
|
|
53
|
+
3. We do not support multiple references to the same object, circular or not.
|
|
54
|
+
If you assign the same complex object across multiple serializable
|
|
55
|
+
attributes, the behavior is undefined. (depending on how JSON generator
|
|
56
|
+
implementation behaves)
|
|
57
|
+
|
|
58
|
+
4. Cloning a candywrapper object (.clone) will perform deep copy.
|
|
59
|
+
If we don't do this, you could end up with two separate objects
|
|
60
|
+
sharing the same internal state.
|
|
61
|
+
|
|
62
|
+
5. All serializable attributes are optional in nature. If you don't set
|
|
63
|
+
them, they won't be part of the serialized payload.
|
|
64
|
+
|
|
65
|
+
## Known Issues / Todo
|
|
66
|
+
|
|
67
|
+
1. We do not provide validation of values being assigned to serializable
|
|
68
|
+
attributes. The reason for this is to keep things as minimal as possible.
|
|
69
|
+
If we were to provide validation, we would also have to go through
|
|
70
|
+
any nested raw hash object to see if they satisfy the structure specified
|
|
71
|
+
in DSL, which means recursively traversing all the elements of the raw
|
|
72
|
+
hash during deserialization.
|
|
73
|
+
|
|
74
|
+
The only thing that we check for you is whether something is allowed to
|
|
75
|
+
be nil or not, as such feature tends to catch a lot of real bugs.
|
|
76
|
+
|
|
77
|
+
2. Currently, we do not support nested array of candywrappers. For now,
|
|
78
|
+
arrays must only contain other JSON primitives.
|
|
79
|
+
When we support this in the future, we will probably require each
|
|
80
|
+
array explicitly define the element type, and we will not allow mixing
|
|
81
|
+
of multiple candywrapper types within the same array.
|
|
82
|
+
|
|
83
|
+
3. Currently, we do not support arbitrary hash containing candywrappers
|
|
84
|
+
as values. The reason for this is that to support deserializing a
|
|
85
|
+
candywrapper object from value within an arbitrarily nested hash
|
|
86
|
+
object requires decorating the raw payload. (e.g. mark a hash object
|
|
87
|
+
as being payload of a candywrapper class using '_class_name' key)
|
|
88
|
+
|
|
89
|
+
Related advice here is that you stick to explicitly defining your object
|
|
90
|
+
structure, rather than leaving it up to some run-time interpretation
|
|
91
|
+
(based on existence of a specific key-val). I've tried to support this
|
|
92
|
+
kind of functionality before in a previous job, and it became pretty ugly.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## Installation
|
|
96
|
+
|
|
97
|
+
Add this line to your application's Gemfile:
|
|
98
|
+
|
|
99
|
+
gem 'candywrapper'
|
|
100
|
+
|
|
101
|
+
And then execute:
|
|
102
|
+
|
|
103
|
+
$ bundle
|
|
104
|
+
|
|
105
|
+
Or install it yourself as:
|
|
106
|
+
|
|
107
|
+
$ gem install candywrapper
|
|
108
|
+
|
|
109
|
+
## Usage
|
|
110
|
+
|
|
111
|
+
Here is a simple example to illustrate how to use Candywrapper.
|
|
112
|
+
|
|
113
|
+
require 'candywrapper'
|
|
114
|
+
|
|
115
|
+
class Address
|
|
116
|
+
include Candywrapper
|
|
117
|
+
serializable_attr :street
|
|
118
|
+
serializable_attr :city
|
|
119
|
+
serializable_attr :state
|
|
120
|
+
serializable_attr :zip
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
class Person
|
|
124
|
+
include Candywrapper
|
|
125
|
+
serializable_attr :first_name
|
|
126
|
+
serializable_attr :middle_name
|
|
127
|
+
serializable_attr :last_name
|
|
128
|
+
serializable_attr :home_address, Address
|
|
129
|
+
serializable_attr :work_address, Address
|
|
130
|
+
def full_name
|
|
131
|
+
@full_name = [first_name, middle_name, last_name].compact.join(" ")
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
a = Address.new
|
|
136
|
+
a.street = "120 Cherry ST N"
|
|
137
|
+
a.city = "Seattle"
|
|
138
|
+
a.state = "WA"
|
|
139
|
+
a.zip = "98101"
|
|
140
|
+
|
|
141
|
+
p = Person.new
|
|
142
|
+
p.home_address = a
|
|
143
|
+
p.first_name = "James"
|
|
144
|
+
p.last_name = "Bond"
|
|
145
|
+
p.full_name # => "James Bond"
|
|
146
|
+
|
|
147
|
+
json = p.serialize_to_json
|
|
148
|
+
# =>
|
|
149
|
+
# {
|
|
150
|
+
# "first_name": "James",
|
|
151
|
+
# "last_name": "Bond",
|
|
152
|
+
# "home_address": {
|
|
153
|
+
# "street": "120 Cherry ST N",
|
|
154
|
+
# "city": "Seattle",
|
|
155
|
+
# "state": "WA",
|
|
156
|
+
# "zip": "98101"
|
|
157
|
+
# }
|
|
158
|
+
# }
|
|
159
|
+
|
|
160
|
+
p2 = Person.deserialize_from_json(json)
|
|
161
|
+
p2.class # => Person
|
|
162
|
+
p2.home_address.class # => Address
|
|
163
|
+
p2.work_address # => nil
|
|
164
|
+
p2.full_name # => "James Bond"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
## Contributing
|
|
168
|
+
|
|
169
|
+
1. Fork it
|
|
170
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
171
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
|
172
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
173
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env rake
|
|
2
|
+
require "bundler/gem_tasks"
|
|
3
|
+
require "rake"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
|
|
6
|
+
namespace :test do
|
|
7
|
+
|
|
8
|
+
Rake::TestTask.new(:unit) do |test|
|
|
9
|
+
test.libs << 'test'
|
|
10
|
+
test.pattern = '{test/unit/**/*_test.rb}'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
Rake::TestTask.new(:all) do |test|
|
|
14
|
+
test.libs << 'test'
|
|
15
|
+
test.pattern = '{test/**/*_test.rb}'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
task :test => "test:unit"
|
|
21
|
+
task :default => "test"
|
|
22
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
require File.expand_path('../lib/candywrapper/version', __FILE__)
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |gem|
|
|
5
|
+
gem.authors = ["Steve Jang"]
|
|
6
|
+
gem.email = ["estebanjang@gmail.com"]
|
|
7
|
+
gem.description = %q{This gem provides DSL for specifying attributes to be included in serailization.}
|
|
8
|
+
gem.summary = %q{Please see README.md file for full description of what this gem does.}
|
|
9
|
+
gem.homepage = ""
|
|
10
|
+
|
|
11
|
+
gem.files = `git ls-files`.split($\)
|
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
14
|
+
gem.name = "candywrapper"
|
|
15
|
+
gem.require_paths = ["lib"]
|
|
16
|
+
gem.version = Candywrapper::VERSION
|
|
17
|
+
|
|
18
|
+
gem.add_dependency("json")
|
|
19
|
+
end
|
data/lib/candywrapper.rb
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require "time"
|
|
2
|
+
require "json"
|
|
3
|
+
require "candywrapper/version"
|
|
4
|
+
|
|
5
|
+
# === Description
|
|
6
|
+
# Mix-in to add serializable attribute support to a class.
|
|
7
|
+
# Please refer to candywrapper gem README.md documentation.
|
|
8
|
+
#
|
|
9
|
+
module Candywrapper
|
|
10
|
+
|
|
11
|
+
CANDYWRAPPER_OBJ = '@candywrapper'.freeze
|
|
12
|
+
|
|
13
|
+
def payload_hash
|
|
14
|
+
@payload_hash ||= {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def payload_hash_set(h)
|
|
18
|
+
if h.is_a?(Hash)
|
|
19
|
+
@payload_hash = h
|
|
20
|
+
h.instance_variable_set(CANDYWRAPPER_OBJ, self)
|
|
21
|
+
else
|
|
22
|
+
raise "payload_hash cannot be a non-hash object: #{h.inspect}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def serialize_to_json
|
|
27
|
+
JSON.generate(@payload_hash)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def candywrapper_object_get(name_s, type, payload_val)
|
|
33
|
+
obj = payload_val.instance_variable_get(CANDYWRAPPER_OBJ)
|
|
34
|
+
unless obj
|
|
35
|
+
obj = type.candywrap_hash(payload_val)
|
|
36
|
+
payload_val.instance_variable_set(CANDYWRAPPER_OBJ, obj)
|
|
37
|
+
end
|
|
38
|
+
obj
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def candywrapper_object_set(name_s, type, val)
|
|
42
|
+
if val.is_a?(type)
|
|
43
|
+
payload_hash[name_s] = val.payload_hash
|
|
44
|
+
else
|
|
45
|
+
raise "Expected #{type.inspect} but got #{val.inspect} for #{name_s}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def candywrapper_attr_get(name_s, type, conversion, opt = {})
|
|
50
|
+
|
|
51
|
+
payload_val = payload_hash[name_s]
|
|
52
|
+
if payload_val.nil?
|
|
53
|
+
conversion = :none # forgive non-existent attribute in payload
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
case conversion
|
|
57
|
+
when :none
|
|
58
|
+
return payload_val
|
|
59
|
+
when :candywrapper
|
|
60
|
+
return candywrapper_object_get(name_s, type, payload_val)
|
|
61
|
+
when :iso8601
|
|
62
|
+
return Time.parse(payload_val)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def candywrapper_attr_set(name_s, type, conversion, val, opt = {})
|
|
67
|
+
|
|
68
|
+
if val.nil?
|
|
69
|
+
raise "Nil is not allowed for #{name_s} attribute" if opt[:no_nil]
|
|
70
|
+
conversion = :none
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
case conversion
|
|
74
|
+
when :none
|
|
75
|
+
payload_hash[name_s] = val
|
|
76
|
+
when :candywrapper
|
|
77
|
+
candywrapper_object_set(name_s, type, val)
|
|
78
|
+
when :iso8601
|
|
79
|
+
if val.is_a?(Time)
|
|
80
|
+
payload_hash[name_s] = val.utc.strftime("%FT%T%z")
|
|
81
|
+
else
|
|
82
|
+
raise "Expected #{type.inspect} but got #{val.inspect} for #{name_s}"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
module ClassMethods
|
|
88
|
+
|
|
89
|
+
def candywrap_hash(h)
|
|
90
|
+
obj = self.new
|
|
91
|
+
obj.payload_hash_set(h)
|
|
92
|
+
obj
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def deserialize_from_json(json)
|
|
96
|
+
h = JSON.parse(json)
|
|
97
|
+
candywrap_hash(h)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# === Description
|
|
101
|
+
# Add a serializable attribute to the current class.
|
|
102
|
+
#
|
|
103
|
+
# === Parameters
|
|
104
|
+
# name:: (Symbol) Attribute name
|
|
105
|
+
# type:: (Class) Class of the attribute value; nil to indicate "don't care"
|
|
106
|
+
# opt:: (Hash) Additional options
|
|
107
|
+
# * opt[:no_nil]:: do not allow nil assignment
|
|
108
|
+
#
|
|
109
|
+
def serializable_attr(name, type = nil, opt = {})
|
|
110
|
+
|
|
111
|
+
name_s = name.to_s
|
|
112
|
+
if type.respond_to?(:ancestors) and type.ancestors.include?(Candywrapper)
|
|
113
|
+
conversion = :candywrapper
|
|
114
|
+
elsif type == Time
|
|
115
|
+
conversion = :iso8601
|
|
116
|
+
else
|
|
117
|
+
conversion = :none
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
define_method name_s do
|
|
121
|
+
candywrapper_attr_get(name_s, type, conversion, opt)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
define_method "#{name_s}=" do |val|
|
|
125
|
+
candywrapper_attr_set(name_s, type, conversion, val, opt)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.included(c)
|
|
132
|
+
class << c
|
|
133
|
+
include ClassMethods
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
ENV['SERVICE_ROOT'] = File.expand_path(File.dirname(__FILE__))
|
|
2
|
+
require 'minitest/autorun'
|
|
3
|
+
require 'candywrapper'
|
|
4
|
+
|
|
5
|
+
class Address
|
|
6
|
+
include Candywrapper
|
|
7
|
+
serializable_attr :street
|
|
8
|
+
serializable_attr :city
|
|
9
|
+
serializable_attr :state
|
|
10
|
+
serializable_attr :zip
|
|
11
|
+
serializable_attr :created_at, Time
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Person
|
|
15
|
+
include Candywrapper
|
|
16
|
+
serializable_attr :first_name
|
|
17
|
+
serializable_attr :middle_name
|
|
18
|
+
serializable_attr :last_name
|
|
19
|
+
serializable_attr :home_address, Address
|
|
20
|
+
serializable_attr :work_address, Address
|
|
21
|
+
serializable_attr :created_at, Time
|
|
22
|
+
serializable_attr :database_id, Integer, :no_nil => true
|
|
23
|
+
def full_name
|
|
24
|
+
@full_name = [first_name, middle_name, last_name].compact.join(" ")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class CandywrapperTest < MiniTest::Unit::TestCase
|
|
29
|
+
|
|
30
|
+
def setup
|
|
31
|
+
@a = Address.new
|
|
32
|
+
@a.street = "120 Cherry ST N"
|
|
33
|
+
@a.city = "Seattle"
|
|
34
|
+
@a.state = "WA"
|
|
35
|
+
@a.zip = "98101"
|
|
36
|
+
|
|
37
|
+
@p = Person.new
|
|
38
|
+
@p.home_address = @a
|
|
39
|
+
@p.first_name = "James"
|
|
40
|
+
@p.last_name = "Bond"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def test_accessors
|
|
44
|
+
|
|
45
|
+
assert_equal(@p.home_address.class, Address)
|
|
46
|
+
assert_equal("120 Cherry ST N", @p.home_address.street)
|
|
47
|
+
assert_equal("Seattle", @p.home_address.city)
|
|
48
|
+
assert_equal("WA", @p.home_address.state)
|
|
49
|
+
assert_equal("98101", @p.home_address.zip)
|
|
50
|
+
assert_equal(nil, @p.work_address)
|
|
51
|
+
|
|
52
|
+
ah = @p.home_address.payload_hash
|
|
53
|
+
assert_equal("120 Cherry ST N", ah['street'])
|
|
54
|
+
assert_equal("Seattle", ah['city'])
|
|
55
|
+
assert_equal("WA", ah['state'])
|
|
56
|
+
assert_equal("98101", ah['zip'])
|
|
57
|
+
|
|
58
|
+
assert_equal("James", @p.first_name)
|
|
59
|
+
assert_equal("Bond", @p.last_name)
|
|
60
|
+
assert_equal(nil, @p.middle_name)
|
|
61
|
+
assert_equal("James Bond", @p.full_name)
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_time_conversion
|
|
66
|
+
|
|
67
|
+
t1 = Time.parse('2012-08-18T06:10:00 MDT')
|
|
68
|
+
t2 = Time.parse('2012-08-18T06:20:00 MDT')
|
|
69
|
+
t3 = Time.parse('2012-08-18T05:20:00 PDT')
|
|
70
|
+
assert_equal(t2, t3)
|
|
71
|
+
|
|
72
|
+
@p.created_at = t1
|
|
73
|
+
@p.home_address.created_at = t2
|
|
74
|
+
|
|
75
|
+
assert_equal("2012-08-18T12:10:00+0000", @p.payload_hash['created_at'])
|
|
76
|
+
assert_equal("2012-08-18T12:20:00+0000", @p.payload_hash['home_address']['created_at'])
|
|
77
|
+
|
|
78
|
+
json = @p.serialize_to_json
|
|
79
|
+
|
|
80
|
+
p2 = Person.deserialize_from_json(json)
|
|
81
|
+
assert_equal("2012-08-18T12:10:00+0000", p2.payload_hash['created_at'])
|
|
82
|
+
assert_equal("2012-08-18T12:20:00+0000", p2.payload_hash['home_address']['created_at'])
|
|
83
|
+
|
|
84
|
+
assert_equal(t1, p2.created_at)
|
|
85
|
+
assert_equal(t2, p2.home_address.created_at)
|
|
86
|
+
assert_equal(t3, p2.home_address.created_at)
|
|
87
|
+
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_serialization
|
|
91
|
+
|
|
92
|
+
json = @p.serialize_to_json
|
|
93
|
+
|
|
94
|
+
h = JSON.parse(json)
|
|
95
|
+
assert_equal('James', h['first_name'])
|
|
96
|
+
assert_equal('Bond', h['last_name'])
|
|
97
|
+
assert_equal(nil, h['middle_name'])
|
|
98
|
+
assert_equal('120 Cherry ST N', h['home_address']['street'])
|
|
99
|
+
assert_equal('Seattle', h['home_address']['city'])
|
|
100
|
+
assert_equal('WA', h['home_address']['state'])
|
|
101
|
+
assert_equal('98101', h['home_address']['zip'])
|
|
102
|
+
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_deserialization
|
|
106
|
+
|
|
107
|
+
json = @p.serialize_to_json
|
|
108
|
+
|
|
109
|
+
p2 = Person.deserialize_from_json(json)
|
|
110
|
+
assert_equal("James", p2.first_name)
|
|
111
|
+
assert_equal(nil, p2.middle_name)
|
|
112
|
+
assert_equal("Bond", p2.last_name)
|
|
113
|
+
assert_equal(nil, p2.work_address)
|
|
114
|
+
assert_equal(nil, p2.created_at)
|
|
115
|
+
assert_equal("James Bond", p2.full_name)
|
|
116
|
+
|
|
117
|
+
assert_equal("120 Cherry ST N", p2.home_address.street)
|
|
118
|
+
assert_equal("Seattle", p2.home_address.city)
|
|
119
|
+
assert_equal("WA", p2.home_address.state)
|
|
120
|
+
assert_equal("98101", p2.home_address.zip)
|
|
121
|
+
assert_equal(nil, p2.home_address.created_at)
|
|
122
|
+
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def test_errors
|
|
126
|
+
|
|
127
|
+
begin
|
|
128
|
+
@p.database_id = nil
|
|
129
|
+
rescue => e
|
|
130
|
+
assert(e.to_s =~ /Nil is not allowed/)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
begin
|
|
134
|
+
@p.work_address = "100 5th Ave"
|
|
135
|
+
rescue => e
|
|
136
|
+
assert(e.to_s =~ /Expected Address but got/)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
begin
|
|
140
|
+
@p.created_at = "2012-01-01"
|
|
141
|
+
rescue => e
|
|
142
|
+
assert(e.to_s =~ /Expected Time but got/)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Bad type in payload is forgiven, until you try to access it
|
|
146
|
+
#
|
|
147
|
+
h = JSON.parse(@p.serialize_to_json)
|
|
148
|
+
h['work_address'] = "100 5th Ave"
|
|
149
|
+
json = JSON.generate(h)
|
|
150
|
+
p2 = Person.deserialize_from_json(json)
|
|
151
|
+
begin
|
|
152
|
+
p2.work_address
|
|
153
|
+
rescue => e
|
|
154
|
+
assert(e.to_s =~ /payload_hash cannot be a non-hash object/)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# No exceptions
|
|
158
|
+
#
|
|
159
|
+
p2.work_address = Address.new # we can fix it later
|
|
160
|
+
p2.work_address.street = "100 5th Ave"
|
|
161
|
+
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
end
|
|
165
|
+
|
metadata
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: candywrapper
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Steve Jang
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2012-08-20 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: json
|
|
16
|
+
requirement: &11759500 !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ! '>='
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: *11759500
|
|
25
|
+
description: This gem provides DSL for specifying attributes to be included in serailization.
|
|
26
|
+
email:
|
|
27
|
+
- estebanjang@gmail.com
|
|
28
|
+
executables: []
|
|
29
|
+
extensions: []
|
|
30
|
+
extra_rdoc_files: []
|
|
31
|
+
files:
|
|
32
|
+
- .gitignore
|
|
33
|
+
- Gemfile
|
|
34
|
+
- LICENSE
|
|
35
|
+
- README.md
|
|
36
|
+
- Rakefile
|
|
37
|
+
- candywrapper.gemspec
|
|
38
|
+
- lib/candywrapper.rb
|
|
39
|
+
- lib/candywrapper/version.rb
|
|
40
|
+
- test/unit/basic_test.rb
|
|
41
|
+
homepage: ''
|
|
42
|
+
licenses: []
|
|
43
|
+
post_install_message:
|
|
44
|
+
rdoc_options: []
|
|
45
|
+
require_paths:
|
|
46
|
+
- lib
|
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
|
+
none: false
|
|
49
|
+
requirements:
|
|
50
|
+
- - ! '>='
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '0'
|
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
54
|
+
none: false
|
|
55
|
+
requirements:
|
|
56
|
+
- - ! '>='
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '0'
|
|
59
|
+
requirements: []
|
|
60
|
+
rubyforge_project:
|
|
61
|
+
rubygems_version: 1.8.11
|
|
62
|
+
signing_key:
|
|
63
|
+
specification_version: 3
|
|
64
|
+
summary: Please see README.md file for full description of what this gem does.
|
|
65
|
+
test_files:
|
|
66
|
+
- test/unit/basic_test.rb
|