horcrux 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +9 -0
- data/horcrux.gemspec +9 -2
- data/lib/horcrux/entity.rb +179 -0
- data/lib/horcrux/serializers/gzip_serializer.rb +29 -0
- data/lib/horcrux/serializers/message_pack_serializer.rb +27 -0
- data/lib/horcrux.rb +1 -1
- data/test/entity_test.rb +138 -0
- data/test/helper.rb +5 -0
- data/test/memory_test.rb +2 -2
- data/test/serializer_test.rb +40 -0
- metadata +16 -6
data/Gemfile
ADDED
data/horcrux.gemspec
CHANGED
@@ -12,8 +12,8 @@ Gem::Specification.new do |s|
|
|
12
12
|
## If your rubyforge_project name is different, then edit it and comment out
|
13
13
|
## the sub! line in the Rakefile
|
14
14
|
s.name = 'horcrux'
|
15
|
-
s.version = '0.0.
|
16
|
-
s.date = '2012-01-
|
15
|
+
s.version = '0.0.2'
|
16
|
+
s.date = '2012-01-03'
|
17
17
|
s.rubyforge_project = 'horcrux'
|
18
18
|
|
19
19
|
## Make sure your summary is short. The description may be as long
|
@@ -40,12 +40,19 @@ Gem::Specification.new do |s|
|
|
40
40
|
## THE MANIFEST COMMENTS, they are used as delimiters by the task.
|
41
41
|
# = MANIFEST =
|
42
42
|
s.files = %w[
|
43
|
+
Gemfile
|
43
44
|
LICENSE.md
|
44
45
|
README.md
|
45
46
|
Rakefile
|
46
47
|
horcrux.gemspec
|
47
48
|
lib/horcrux.rb
|
49
|
+
lib/horcrux/entity.rb
|
50
|
+
lib/horcrux/serializers/gzip_serializer.rb
|
51
|
+
lib/horcrux/serializers/message_pack_serializer.rb
|
52
|
+
test/entity_test.rb
|
53
|
+
test/helper.rb
|
48
54
|
test/memory_test.rb
|
55
|
+
test/serializer_test.rb
|
49
56
|
]
|
50
57
|
# = MANIFEST =
|
51
58
|
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require File.expand_path('../../horcrux', __FILE__)
|
2
|
+
require 'set'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Horcrux
|
6
|
+
module Entity
|
7
|
+
def self.included(base)
|
8
|
+
class << base
|
9
|
+
attr_accessor :attributes, :writable_attributes, :default_attribute
|
10
|
+
end
|
11
|
+
|
12
|
+
base.attributes = Set.new
|
13
|
+
base.writable_attributes = Set.new
|
14
|
+
base.extend ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
# Public
|
19
|
+
def attr(type, *keys)
|
20
|
+
method = "build_#{type}_attr"
|
21
|
+
if respond_to?(method)
|
22
|
+
send method, keys, false
|
23
|
+
else
|
24
|
+
raise TypeError, "Type #{type.inspect} not recognized."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public
|
29
|
+
def readonly(type, *keys)
|
30
|
+
method = "build_#{type}_attr"
|
31
|
+
if respond_to?(method)
|
32
|
+
send method, keys, true
|
33
|
+
else
|
34
|
+
raise TypeError, "Type #{type.inspect} not recognized."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public
|
39
|
+
def from(hash = {})
|
40
|
+
hash.respond_to?(@default_attribute) ? hash : new(hash)
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_string_attr(keys, is_readonly)
|
44
|
+
attr_reader *keys
|
45
|
+
|
46
|
+
build_attrs(keys, is_readonly) do |key_s|
|
47
|
+
ivar_key = "@#{key_s}"
|
48
|
+
define_method "#{key_s}=" do |value|
|
49
|
+
instance_variable_set ivar_key, value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_bool_attr(keys, is_readonly)
|
55
|
+
attr_reader *keys
|
56
|
+
|
57
|
+
build_attrs(keys, is_readonly) do |key_s|
|
58
|
+
ivar_key = "@#{key_s}"
|
59
|
+
|
60
|
+
define_method "#{key_s}?" do
|
61
|
+
!!instance_variable_get(ivar_key)
|
62
|
+
end
|
63
|
+
|
64
|
+
define_method "#{key_s}=" do |value|
|
65
|
+
instance_variable_set ivar_key, case value
|
66
|
+
when Fixnum then value > 0
|
67
|
+
when /t|f/i then value =~ /t/i ? true : false
|
68
|
+
else !!value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def build_array_attr(keys, is_readonly)
|
75
|
+
build_class_attr(Array, keys, is_readonly)
|
76
|
+
end
|
77
|
+
|
78
|
+
def build_hash_attr(keys, is_readonly)
|
79
|
+
build_class_attr(Hash, keys, is_readonly)
|
80
|
+
end
|
81
|
+
|
82
|
+
def build_time_attr(keys, is_readonly)
|
83
|
+
attr_reader *keys
|
84
|
+
|
85
|
+
build_attrs(keys, is_readonly) do |key_s|
|
86
|
+
ivar_key = "@#{key_s}"
|
87
|
+
define_method "#{key_s}=" do |value|
|
88
|
+
instance_variable_set ivar_key,
|
89
|
+
value.respond_to?(:utc) ?
|
90
|
+
value :
|
91
|
+
Time.at(value.to_i).utc
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def build_class_attr(klass, keys, is_readonly)
|
97
|
+
build_attrs(keys, is_readonly) do |key_s|
|
98
|
+
ivar_key = "@#{key_s}"
|
99
|
+
define_method key_s do
|
100
|
+
instance_variable_get(ivar_key) ||
|
101
|
+
instance_variable_set(ivar_key, klass.new)
|
102
|
+
end
|
103
|
+
|
104
|
+
define_method "#{key_s}=" do |value|
|
105
|
+
unless value.is_a?(klass)
|
106
|
+
raise TypeError, "#{key_s} should be a #{klass}: #{value.inspect}"
|
107
|
+
end
|
108
|
+
instance_variable_set(ivar_key, value)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def build_attrs(keys, is_readonly)
|
114
|
+
keys.each do |key|
|
115
|
+
key_s = key.to_s
|
116
|
+
@default_attribute ||= key_s
|
117
|
+
@attributes << key_s
|
118
|
+
@writable_attributes << key_s unless is_readonly
|
119
|
+
yield key_s
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Public
|
125
|
+
def initialize(hash = {})
|
126
|
+
update_attrs(hash, all = true)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Public: Updates multiple attributes.
|
130
|
+
#
|
131
|
+
# hash - A Hash containing valid attributes.
|
132
|
+
#
|
133
|
+
# Returns this Entity.
|
134
|
+
def update_attrs(hash, all = false)
|
135
|
+
attr = all ? self.class.attributes : self.class.writable_attributes
|
136
|
+
hash.each do |key, value|
|
137
|
+
key_s = key.to_s
|
138
|
+
|
139
|
+
if !attr.include?(key_s)
|
140
|
+
raise ArgumentError, "Invalid property: #{key.inspect}"
|
141
|
+
end
|
142
|
+
|
143
|
+
send "#{key_s}=", value
|
144
|
+
end
|
145
|
+
|
146
|
+
self
|
147
|
+
end
|
148
|
+
|
149
|
+
# Public
|
150
|
+
def each_attr(writable_only = false)
|
151
|
+
attr_method = writable_only ? :writable_attributes : :attributes
|
152
|
+
self.class.send(attr_method).each do |key|
|
153
|
+
if value = send(key)
|
154
|
+
yield key, value
|
155
|
+
end
|
156
|
+
end
|
157
|
+
self
|
158
|
+
end
|
159
|
+
|
160
|
+
# Public
|
161
|
+
def ==(other)
|
162
|
+
default_attr = self.class.default_attribute
|
163
|
+
other.class == self.class && other.send(default_attr) == send(default_attr)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Public: Converts the valid attributes into a Hash of Symbol key and Object
|
167
|
+
# value.
|
168
|
+
#
|
169
|
+
# Returns a Hash.
|
170
|
+
def to_hash(writable_only = false)
|
171
|
+
hash = {}
|
172
|
+
each_attr(writable_only) do |key, value|
|
173
|
+
hash[key.to_sym] = value
|
174
|
+
end
|
175
|
+
hash
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path('../../../horcrux', __FILE__)
|
2
|
+
require 'zlib'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module Horcrux
|
6
|
+
class GzipSerializer
|
7
|
+
def initialize(serializer)
|
8
|
+
@serializer = serializer
|
9
|
+
end
|
10
|
+
|
11
|
+
def dump(value)
|
12
|
+
s = StringIO.new
|
13
|
+
z = Zlib::GzipWriter.new(s)
|
14
|
+
z.write @serializer.dump(value)
|
15
|
+
s.string
|
16
|
+
ensure
|
17
|
+
z.close if z
|
18
|
+
end
|
19
|
+
|
20
|
+
def load(value)
|
21
|
+
s = StringIO.new(value)
|
22
|
+
z = Zlib::GzipReader.new(s)
|
23
|
+
@serializer.load(z.read)
|
24
|
+
ensure
|
25
|
+
z.close if z
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path('../../../horcrux', __FILE__)
|
2
|
+
require 'msgpack'
|
3
|
+
|
4
|
+
module Horcrux
|
5
|
+
module MessagePackSerializer
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def dump(value)
|
9
|
+
value.to_msgpack
|
10
|
+
end
|
11
|
+
|
12
|
+
def load(str)
|
13
|
+
MessagePack.unpack(str)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
if Time.now.respond_to?(:to_msgpack)
|
19
|
+
raise LoadError, "Time#to_msgpack should not exist"
|
20
|
+
else
|
21
|
+
class Time
|
22
|
+
def to_msgpack(*args)
|
23
|
+
to_i.to_msgpack(*args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
data/lib/horcrux.rb
CHANGED
data/test/entity_test.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
module Horcrux
|
4
|
+
class EntityTest < Test::Unit::TestCase
|
5
|
+
class Entity
|
6
|
+
include Horcrux::Entity
|
7
|
+
|
8
|
+
attr :string, :name
|
9
|
+
attr :bool, :admin
|
10
|
+
attr :array, :roles
|
11
|
+
attr :time, :updated_at
|
12
|
+
|
13
|
+
readonly :string, :type
|
14
|
+
readonly :hash, :codes
|
15
|
+
readonly :time, :created_at
|
16
|
+
|
17
|
+
def initialize(hash)
|
18
|
+
super(hash)
|
19
|
+
@created_at ||= Time.now.utc
|
20
|
+
end
|
21
|
+
|
22
|
+
def type
|
23
|
+
@type ||= 'default'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_accesses_attributes
|
28
|
+
ent = entity
|
29
|
+
assert_equal 'bob', ent.name
|
30
|
+
assert_equal 'person', ent.type
|
31
|
+
assert_equal true, ent.admin
|
32
|
+
assert ent.admin?
|
33
|
+
|
34
|
+
ent.name = 'bobby'
|
35
|
+
assert_equal 'bobby', ent.name
|
36
|
+
|
37
|
+
ent.admin = 0
|
38
|
+
assert !ent.admin?
|
39
|
+
|
40
|
+
ent.admin = 'true'
|
41
|
+
assert ent.admin?
|
42
|
+
|
43
|
+
ent.admin = false
|
44
|
+
assert !ent.admin?
|
45
|
+
|
46
|
+
ent.roles = %w(c)
|
47
|
+
assert_equal %w(c), ent.roles
|
48
|
+
|
49
|
+
assert_equal 2000, ent.created_at.year
|
50
|
+
assert_equal 2012, ent.updated_at.year
|
51
|
+
|
52
|
+
ent.updated_at = Time.utc 2010
|
53
|
+
assert_equal 2010, ent.updated_at.year
|
54
|
+
|
55
|
+
assert_raises ArgumentError do
|
56
|
+
ent.update_attrs :type => 'troll'
|
57
|
+
end
|
58
|
+
|
59
|
+
assert_raises ArgumentError do
|
60
|
+
ent.update_attrs :codes => 'troll'
|
61
|
+
end
|
62
|
+
|
63
|
+
assert_raises ArgumentError do
|
64
|
+
ent.update_attrs :created_at => Time.now
|
65
|
+
end
|
66
|
+
|
67
|
+
assert_raises TypeError do
|
68
|
+
ent.roles = 1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_initializes_with_default_value
|
73
|
+
ent = entity(:created_at => nil)
|
74
|
+
assert_equal Time.now.utc.year, ent.created_at.year
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_dumps_entity_to_hash
|
78
|
+
hash = entity.to_hash
|
79
|
+
assert_equal 'bob', hash[:name]
|
80
|
+
assert_equal 'person', hash[:type]
|
81
|
+
assert_equal true, hash[:admin]
|
82
|
+
assert_equal %w(a b), hash[:roles]
|
83
|
+
assert_equal({"a" => 1, "b" => 2}, hash[:codes])
|
84
|
+
assert_equal 2000, hash[:created_at].year
|
85
|
+
assert_equal 2012, hash[:updated_at].year
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_tracks_attributes
|
89
|
+
assert Entity.attributes.include?('name')
|
90
|
+
assert Entity.attributes.include?('admin')
|
91
|
+
assert Entity.attributes.include?('type')
|
92
|
+
assert Entity.attributes.include?('roles')
|
93
|
+
assert Entity.attributes.include?('codes')
|
94
|
+
assert Entity.attributes.include?('created_at')
|
95
|
+
assert Entity.attributes.include?('updated_at')
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_tracks_writable_attributes
|
99
|
+
assert Entity.writable_attributes.include?('name')
|
100
|
+
assert Entity.writable_attributes.include?('admin')
|
101
|
+
assert Entity.writable_attributes.include?('roles')
|
102
|
+
assert Entity.writable_attributes.include?('updated_at')
|
103
|
+
assert !Entity.writable_attributes.include?('codes')
|
104
|
+
assert !Entity.writable_attributes.include?('type')
|
105
|
+
assert !Entity.writable_attributes.include?('created_at')
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_tracks_default_attribute
|
109
|
+
assert_equal 'name', Entity.default_attribute
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_convert_hash_to_entity
|
113
|
+
data = entity_data :name => "fred"
|
114
|
+
ent = entity :name => 'fred'
|
115
|
+
assert_equal ent, Entity.from(data)
|
116
|
+
assert_equal ent, Entity.from(ent)
|
117
|
+
end
|
118
|
+
|
119
|
+
def entity(options = {})
|
120
|
+
Entity.new(entity_data(options))
|
121
|
+
end
|
122
|
+
|
123
|
+
def entity_data(options = {})
|
124
|
+
hash = {:name => 'bob', :type => 'person', :admin => 't',
|
125
|
+
:created_at => Time.utc(2000), :updated_at => 1325619163,
|
126
|
+
:roles => %w(a b), :codes => {'a' => 1, 'b' => 2}}
|
127
|
+
options.each do |key, value|
|
128
|
+
if value.nil?
|
129
|
+
hash.delete key
|
130
|
+
else
|
131
|
+
hash[key] = value
|
132
|
+
end
|
133
|
+
end
|
134
|
+
hash
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
data/test/helper.rb
ADDED
data/test/memory_test.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
module Horcrux
|
4
|
+
class SerializerTest < Test::Unit::TestCase
|
5
|
+
def test_null_serializer
|
6
|
+
assert_equal 1, NullSerializer.dump(1)
|
7
|
+
assert_equal 1, NullSerializer.load(1)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_string_serializer
|
11
|
+
assert_equal '1', StringSerializer.dump(1)
|
12
|
+
assert_equal '1', StringSerializer.load('1')
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_gzip_serializer
|
16
|
+
data = '0' * 500
|
17
|
+
gzip = GzipSerializer.new(NullSerializer)
|
18
|
+
zipped = gzip.dump(data)
|
19
|
+
assert zipped.size < data.size
|
20
|
+
assert_equal data, gzip.load(zipped)
|
21
|
+
end
|
22
|
+
|
23
|
+
begin
|
24
|
+
require File.expand_path("../../lib/horcrux/serializers/message_pack_serializer", __FILE__)
|
25
|
+
|
26
|
+
def test_msgpack_serializer
|
27
|
+
hash = {:a => 1, :b => {:a => 'a'}, :c => Time.utc(2000)}
|
28
|
+
|
29
|
+
data = MessagePackSerializer.dump(hash)
|
30
|
+
loaded = MessagePackSerializer.load(data)
|
31
|
+
assert_equal 1, loaded['a']
|
32
|
+
assert_equal({"a" => "a"}, loaded["b"])
|
33
|
+
assert_equal 946684800, loaded['c']
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
puts "Skipping MessagePack test. run 'gem install msgpack'"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: horcrux
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-01-
|
12
|
+
date: 2012-01-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &70123004569960 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70123004569960
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: test-unit
|
27
|
-
requirement: &
|
27
|
+
requirement: &70123004569280 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,19 +32,26 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70123004569280
|
36
36
|
description: Simple key/value adapter library.
|
37
37
|
email: technoweenie@gmail.com
|
38
38
|
executables: []
|
39
39
|
extensions: []
|
40
40
|
extra_rdoc_files: []
|
41
41
|
files:
|
42
|
+
- Gemfile
|
42
43
|
- LICENSE.md
|
43
44
|
- README.md
|
44
45
|
- Rakefile
|
45
46
|
- horcrux.gemspec
|
46
47
|
- lib/horcrux.rb
|
48
|
+
- lib/horcrux/entity.rb
|
49
|
+
- lib/horcrux/serializers/gzip_serializer.rb
|
50
|
+
- lib/horcrux/serializers/message_pack_serializer.rb
|
51
|
+
- test/entity_test.rb
|
52
|
+
- test/helper.rb
|
47
53
|
- test/memory_test.rb
|
54
|
+
- test/serializer_test.rb
|
48
55
|
homepage: http://github.com/technoweenie/horcrux
|
49
56
|
licenses: []
|
50
57
|
post_install_message:
|
@@ -70,4 +77,7 @@ signing_key:
|
|
70
77
|
specification_version: 2
|
71
78
|
summary: Simple key/value adapter library.
|
72
79
|
test_files:
|
80
|
+
- test/entity_test.rb
|
81
|
+
- test/helper.rb
|
73
82
|
- test/memory_test.rb
|
83
|
+
- test/serializer_test.rb
|