horcrux 0.0.1 → 0.0.2
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/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
|