restruct 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ module Restruct
2
+ class Id < String
3
+
4
+ attr_reader :separator
5
+
6
+ def initialize(id, separator=nil)
7
+ @separator = separator || Restruct.id_separator
8
+ super id.to_s
9
+ end
10
+
11
+ def [](id)
12
+ Id.new "#{to_s}#{separator}#{id}", separator
13
+ end
14
+
15
+ def sections
16
+ split(separator).map { |s| Id.new s, separator }
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ module Restruct
2
+ class MarshalArray < Array
3
+ include Marshalizable
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Restruct
2
+ class MarshalHash < Hash
3
+ include Marshalizable
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Restruct
2
+ class MarshalSet < Set
3
+ include Marshalizable
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ module Restruct
2
+ module Marshalizable
3
+
4
+ def serialize(element)
5
+ Marshal.dump element
6
+ end
7
+
8
+ def deserialize(element)
9
+ Marshal.load element if element
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,108 @@
1
+ module Restruct
2
+ class NestedHash
3
+
4
+ def self.new(type)
5
+ Class.new Structure do
6
+
7
+ include Enumerable
8
+
9
+ const_set :TYPE, type
10
+
11
+ def [](key)
12
+ self.class::TYPE.new id: id[key], redis: redis, parent: self
13
+ end
14
+
15
+ def fetch(key)
16
+ raise KeyError, "key not found: #{key}" unless key? key
17
+ self[key]
18
+ end
19
+
20
+ def delete(key)
21
+ self[key].tap(&:destroy)
22
+ end
23
+
24
+ def delete_if
25
+ each { |k,v| delete k if yield k, v }
26
+ self
27
+ end
28
+
29
+ def keep_if
30
+ each { |k,v| delete k unless yield k, v }
31
+ self
32
+ end
33
+ alias_method :select!, :keep_if
34
+
35
+ def clear
36
+ destroy
37
+ self
38
+ end
39
+
40
+ def keys
41
+ sections = id.sections.count + 1
42
+ redis.call('KEYS', id['*']).map do |k|
43
+ Id.new(k).sections.take(sections).last
44
+ end.uniq.sort
45
+ end
46
+
47
+ def values
48
+ keys.map { |key| self[key] }
49
+ end
50
+
51
+ def values_at(*keys)
52
+ keys.map { |key| self[key] }
53
+ end
54
+
55
+ def key?(key)
56
+ keys.include? key.to_s
57
+ end
58
+ alias_method :has_key?, :key?
59
+
60
+ def size
61
+ keys.count
62
+ end
63
+ alias_method :count, :size
64
+ alias_method :length, :size
65
+
66
+ def empty?
67
+ size == 0
68
+ end
69
+
70
+ def each
71
+ keys.each { |key| yield key, self[key] }
72
+ end
73
+ alias_method :each_pair, :each
74
+
75
+ def each_key
76
+ each { |k,v| yield k }
77
+ end
78
+
79
+ def each_value
80
+ each { |k,v| yield v }
81
+ end
82
+
83
+ def to_h
84
+ each_with_object({}) do |(key, value), hash|
85
+ hash[key] = value.respond_to?(:to_primitive) ? value.to_primitive : value
86
+ end
87
+ end
88
+ alias_method :to_primitive, :to_h
89
+
90
+ def dump
91
+ each_with_object({}) do |(key, value), hash|
92
+ hash[key] = value.dump
93
+ end
94
+ end
95
+
96
+ def restore(dump)
97
+ dump.each { |f,d| self[f].restore d }
98
+ end
99
+
100
+ def destroy
101
+ values.each(&:destroy)
102
+ end
103
+
104
+ end
105
+ end
106
+
107
+ end
108
+ end
@@ -0,0 +1,119 @@
1
+ module Restruct
2
+ class Set < Structure
3
+
4
+ include Enumerable
5
+ extend Forwardable
6
+
7
+ def_delegators :to_set, :union, :|, :+,
8
+ :intersection, :&,
9
+ :difference, :-,
10
+ :proper_subset?, :subset?,
11
+ :proper_superset?, :superset?,
12
+ :^
13
+
14
+ def add(member)
15
+ _add member
16
+ self
17
+ end
18
+ alias_method :<<, :add
19
+
20
+ def add?(member)
21
+ _add(member) == 0 ? nil : self
22
+ end
23
+
24
+ def merge(members)
25
+ _add *members
26
+ self
27
+ end
28
+
29
+ def delete(member)
30
+ _delete member
31
+ self
32
+ end
33
+
34
+ def delete?(member)
35
+ _delete(member) == 0 ? nil : self
36
+ end
37
+
38
+ def subtract(members)
39
+ _delete *members
40
+ self
41
+ end
42
+
43
+ def delete_if
44
+ each { |e| delete e if yield e }
45
+ self
46
+ end
47
+
48
+ def keep_if
49
+ each { |e| delete e unless yield e }
50
+ self
51
+ end
52
+ alias_method :select!, :keep_if
53
+
54
+ def clear
55
+ destroy
56
+ self
57
+ end
58
+
59
+ def size
60
+ redis.call 'SCARD', id
61
+ end
62
+ alias_method :count, :size
63
+ alias_method :length, :size
64
+
65
+ def empty?
66
+ size == 0
67
+ end
68
+
69
+ def include?(member)
70
+ redis.call('SISMEMBER', id, serialize(member)) == 1
71
+ end
72
+
73
+ def each(&block)
74
+ to_a.each(&block)
75
+ end
76
+
77
+ def to_a
78
+ redis.call('SMEMBERS', id).map { |e| deserialize e }
79
+ end
80
+
81
+ def to_set
82
+ to_a.to_set
83
+ end
84
+ alias_method :to_primitive, :to_set
85
+
86
+ alias_method :<, :proper_subset?
87
+ alias_method :<=, :subset?
88
+ alias_method :>, :proper_superset?
89
+ alias_method :>=, :superset?
90
+
91
+ def intersect?(set)
92
+ !disjoint? set
93
+ end
94
+
95
+ def disjoint?(set)
96
+ (to_a & set.to_a).empty?
97
+ end
98
+
99
+ private
100
+
101
+ def _add(*members)
102
+ redis.call 'SADD', id, *members.map { |m| serialize m }
103
+ end
104
+
105
+ def _delete(*members)
106
+ redis.call 'SREM', id, *members.map { |m| serialize m }
107
+ end
108
+
109
+
110
+ def serialize(string)
111
+ string
112
+ end
113
+
114
+ def deserialize(string)
115
+ string
116
+ end
117
+
118
+ end
119
+ end
@@ -0,0 +1,32 @@
1
+ module Restruct
2
+ class Structure
3
+
4
+ attr_reader :redis, :id
5
+
6
+ def initialize(options={})
7
+ @redis = options[:redis] || Restruct.redis
8
+ @id = Id.new options[:id] || Restruct.generate_id
9
+ end
10
+
11
+ def ==(object)
12
+ object.class == self.class &&
13
+ object.id == id &&
14
+ object.redis == redis
15
+ end
16
+ alias_method :eql?, :==
17
+
18
+ def dump
19
+ redis.call 'DUMP', id
20
+ end
21
+
22
+ def restore(dump)
23
+ destroy
24
+ redis.call 'RESTORE', id, 0, dump
25
+ end
26
+
27
+ def destroy
28
+ redis.call 'DEL', id
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module Restruct
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'restruct/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'restruct'
8
+ spec.version = Restruct::VERSION
9
+ spec.authors = ['Gabriel Naiman']
10
+ spec.email = ['gabynaiman@gmail.com']
11
+ spec.summary = 'Redis structures'
12
+ spec.description = 'Redis structures'
13
+ spec.homepage = 'https://github.com/gabynaiman/restruct'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'redic', '~> 1.1.1'
22
+ spec.add_dependency 'class_config', '~> 0.0.1'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.6'
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_development_dependency 'minitest', '~> 4.7'
27
+ spec.add_development_dependency 'turn', '~> 0.9'
28
+ spec.add_development_dependency 'simplecov'
29
+ spec.add_development_dependency 'pry-nav'
30
+ end
@@ -0,0 +1,338 @@
1
+ require 'minitest_helper'
2
+
3
+ [Restruct::Array, Restruct::MarshalArray].each do |klass|
4
+
5
+ describe klass do
6
+
7
+ let(:array) { klass.new }
8
+
9
+ def fill(elements)
10
+ redis.call 'RPUSH', array.id, *(elements.map { |e| array.send(:serialize, e) })
11
+ end
12
+
13
+ describe 'Getters' do
14
+
15
+ it '[]' do
16
+ fill %w(a b c d e)
17
+
18
+ array[0].must_equal 'a'
19
+ array[1].must_equal 'b'
20
+ array[2].must_equal 'c'
21
+ array[6].must_be_nil
22
+
23
+ array[0..-1].must_equal %w(a b c d e)
24
+ array[1..3].must_equal %w(b c d)
25
+ array[4..7].must_equal ['e']
26
+ array[5..10].must_equal []
27
+ array[6..10].must_be_nil
28
+
29
+ array[-3,3].must_equal %w(c d e)
30
+ array[1,2].must_equal %w(b c)
31
+ array[5,1].must_equal []
32
+ array[6,1].must_be_nil
33
+
34
+ error = proc { array[1,2,3,4] }.must_raise ArgumentError
35
+ error.message.must_equal 'wrong number of arguments (4 for 1..2)'
36
+
37
+ error = proc { array['x'] }.must_raise TypeError
38
+ error.message.must_equal 'no implicit conversion from string to integer'
39
+ end
40
+
41
+ it 'at' do
42
+ fill %w(a b c d)
43
+
44
+ array.at(0).must_equal 'a'
45
+ array.at(1).must_equal 'b'
46
+ array.at(-1).must_equal 'd'
47
+ array.at(10).must_be_nil
48
+ end
49
+
50
+ it 'values_at' do
51
+ fill %w(a b c d e f)
52
+
53
+ array.values_at(1, 3, 5).must_equal %w(b d f)
54
+ array.values_at(1, 3, 5, 7).must_equal ['b', 'd', 'f', nil]
55
+ array.values_at(-1, -2, -2, -7).must_equal ['f', 'e', 'e', nil]
56
+ end
57
+
58
+ it 'fetch' do
59
+ fill %w(a b c)
60
+
61
+ array.fetch(-1).must_equal 'c'
62
+ array.fetch(0).must_equal 'a'
63
+ array.fetch(4, 'x').must_equal 'x'
64
+ array.fetch(4) { |i| (i + 1).to_s }.must_equal '5'
65
+
66
+ error = proc { array.fetch(4) }.must_raise IndexError
67
+ error.message.must_equal 'index 4 outside of array bounds: -3...3'
68
+ end
69
+
70
+ it 'first' do
71
+ fill %w(a b c)
72
+ array.first.must_equal 'a'
73
+ end
74
+
75
+ it 'last' do
76
+ fill %w(a b c)
77
+ array.last.must_equal 'c'
78
+ end
79
+
80
+ end
81
+
82
+ describe 'Setters' do
83
+
84
+ it '[]=' do
85
+ fill %w(a b c d)
86
+
87
+ (array[0] = 'x').must_equal 'x'
88
+ array.to_a.must_equal %w(x b c d)
89
+
90
+ (array[-1] = 'z').must_equal 'z'
91
+ array.to_a.must_equal %w(x b c z)
92
+
93
+ error = proc { array[10] = '.' }.must_raise IndexError
94
+ error.message.must_equal 'index 10 outside of array bounds: -4...4'
95
+
96
+ error = proc { array['k'] = '.' }.must_raise TypeError
97
+ error.message.must_equal 'no implicit conversion from string to integer'
98
+ end
99
+
100
+ it 'push' do
101
+ fill %w(a b c)
102
+
103
+ array.push('d').must_equal array
104
+ array.to_a.must_equal %w(a b c d)
105
+
106
+ array.push('x', 'y', 'z').must_equal array
107
+ array.to_a.must_equal %w(a b c d x y z)
108
+ end
109
+
110
+ it '<<' do
111
+ fill %w(a b c)
112
+
113
+ (array << 'd').must_equal array
114
+ array.to_a.must_equal %w(a b c d)
115
+ end
116
+
117
+ it 'pop' do
118
+ fill %w(a b c d e f)
119
+
120
+ (array.pop).must_equal 'f'
121
+ array.to_a.must_equal %w(a b c d e)
122
+
123
+ (array.pop(2)).must_equal %w(d e)
124
+ array.to_a.must_equal %w(a b c)
125
+
126
+ (array.pop(5)).must_equal %w(a b c)
127
+ array.to_a.must_equal []
128
+ end
129
+
130
+ it 'shift' do
131
+ fill %w(a b c d e f)
132
+
133
+ (array.shift).must_equal 'a'
134
+ array.to_a.must_equal %w(b c d e f)
135
+
136
+ (array.shift(2)).must_equal %w(b c)
137
+ array.to_a.must_equal %w(d e f)
138
+
139
+ (array.shift(5)).must_equal %w(d e f)
140
+ array.to_a.must_equal []
141
+ end
142
+
143
+ it 'insert' do
144
+ fill %w(a b c d)
145
+
146
+ array.insert(0, 'A').must_equal array
147
+ array.to_a.must_equal %w(A a b c d)
148
+
149
+ array.insert(2, 'B', 'B').must_equal array
150
+ array.to_a.must_equal %w(A a B B b c d)
151
+
152
+ array.insert(-2, 'x', 'y', 'z').must_equal array
153
+ array.to_a.must_equal %w(A a B B b c x y z d)
154
+
155
+ array.insert(-5, 'w').must_equal array
156
+ array.to_a.must_equal %w(A a B B b c w x y z d)
157
+ end
158
+
159
+ it 'concat' do
160
+ fill %w(a b c)
161
+
162
+ array.concat(%w(x y z)).must_equal array
163
+ array.to_a.must_equal %w(a b c x y z)
164
+ end
165
+
166
+ it 'delete' do
167
+ fill %w(a b a b a b)
168
+
169
+ array.delete('b').must_equal 'b'
170
+ array.to_a.must_equal %w(a a a)
171
+
172
+ array.delete('c').must_be_nil
173
+ array.to_a.must_equal %w(a a a)
174
+ end
175
+
176
+ it 'delete_at' do
177
+ fill %w(a b c a b c a b c)
178
+
179
+ array.delete_at(3).must_equal 'a'
180
+ array.to_a.must_equal %w(a b c b c a b c)
181
+
182
+ array.delete_at(-4).must_equal 'c'
183
+ array.to_a.must_equal %w(a b c b a b c)
184
+
185
+ array.delete_at(10).must_be_nil
186
+ array.to_a.must_equal %w(a b c b a b c)
187
+ end
188
+
189
+ it 'delete_if' do
190
+ fill %w(a b c a b c a b c)
191
+
192
+ array.delete_if { |e| e == 'a' }.must_equal array
193
+ array.to_a.must_equal %w(b c b c b c)
194
+ end
195
+
196
+ %w(keep_if select!).each do |method|
197
+ it method do
198
+ fill %w(a b c a b c a b c)
199
+
200
+ array.send(method) { |e| e == 'a' }.must_equal array
201
+ array.to_a.must_equal %w(a a a)
202
+ end
203
+ end
204
+
205
+ it 'clear' do
206
+ fill %w(a b c d)
207
+
208
+ array.clear.must_equal array
209
+ array.must_be_empty
210
+ end
211
+
212
+ end
213
+
214
+ describe 'Info' do
215
+
216
+ %w(size count length).each do |method|
217
+ it method do
218
+ fill %w(a b c)
219
+ array.send(method).must_equal 3
220
+ end
221
+ end
222
+
223
+ it 'empty?' do
224
+ array.must_be :empty?
225
+ fill %w(a b c)
226
+ array.wont_be :empty?
227
+ end
228
+
229
+ it 'include?' do
230
+ fill %w(a b c)
231
+
232
+ assert array.include? 'a'
233
+ refute array.include? 'z'
234
+ end
235
+
236
+ end
237
+
238
+ describe 'Transformations' do
239
+
240
+ %w(to_a to_ary to_primitive).each do |method|
241
+ it method do
242
+ fill %w(a b c)
243
+ array.send(method).must_equal %w(a b c)
244
+ end
245
+ end
246
+
247
+ it 'join' do
248
+ fill %w(a b c)
249
+
250
+ array.join.must_equal 'abc'
251
+ array.join('-').must_equal 'a-b-c'
252
+ end
253
+
254
+ it 'uniq' do
255
+ fill %w(a1 a1 a2 a2 b1 b1 b2 b2)
256
+
257
+ array.uniq.must_equal %w(a1 a2 b1 b2)
258
+ array.uniq { |e| e[1] }.must_equal %w(a1 a2)
259
+ end
260
+
261
+ it 'reverse' do
262
+ fill %w(a b c)
263
+ array.reverse.must_equal %w(c b a)
264
+ end
265
+
266
+ end
267
+
268
+ describe 'Enumerable' do
269
+
270
+ it 'included module' do
271
+ assert klass.included_modules.include? Enumerable
272
+ end
273
+
274
+ it 'each' do
275
+ fill %w(a b c)
276
+
277
+ list = []
278
+ array.each { |e| list << e }
279
+
280
+ list.must_equal array.to_a
281
+ end
282
+
283
+ it 'each_index' do
284
+ fill %w(a b c)
285
+
286
+ list = []
287
+ array.each_index { |i| list << i }
288
+
289
+ list.must_equal [0,1,2]
290
+ end
291
+
292
+ end
293
+
294
+ describe 'Sets' do
295
+
296
+ it '+' do
297
+ fill %w(a b c)
298
+ (array + %w(x y z)).must_equal %w(a b c x y z)
299
+ end
300
+
301
+ it '-' do
302
+ fill %w(a b c)
303
+ (array - %w(a z)).must_equal %w(b c)
304
+ end
305
+
306
+ it '&' do
307
+ fill %w(a b c)
308
+ (array & %w(b a z)).must_equal %w(a b)
309
+ end
310
+
311
+ it '|' do
312
+ fill %w(a b c)
313
+ (array | %w(b c d)).must_equal %w(a b c d)
314
+ end
315
+
316
+ end
317
+
318
+ it 'Equality' do
319
+ copy = klass.new id: array.id
320
+ assert array == copy
321
+ assert array.eql? copy
322
+ refute array.equal? copy
323
+ end
324
+
325
+ it 'Dump/Restore' do
326
+ fill %w(a b c)
327
+
328
+ dump = array.dump
329
+ other = klass.new
330
+ other.restore dump
331
+
332
+ other.id.wont_equal array.id
333
+ other.to_primitive.must_equal array.to_primitive
334
+ end
335
+
336
+ end
337
+
338
+ end