memcache_array 1.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.
- data/LICENSE +20 -0
- data/README.rdoc +33 -0
- data/lib/memcache_array.rb +78 -0
- data/spec/memcache_array_spec.rb +188 -0
- metadata +80 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Travel IQ GmbH
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
= MemcacheArray
|
2
|
+
|
3
|
+
MemcacheArray is a wrapper for Memcache so it can be used as shared memory holding arrays.
|
4
|
+
It is intended as a mechanism to move data from one ruby process to another.
|
5
|
+
|
6
|
+
Warning: When writing to the same MemcacheArray (several instances using the same key) concurrently, there is a slim chance that writes are lost.
|
7
|
+
The time window of this to happen is one read of a small bucket from memcache, pushing an integer into an array and writing this small array back to memcache. With a normal setup, this schould not be more than 1-2 ms.
|
8
|
+
|
9
|
+
|
10
|
+
When creating an new instance you pass the key and optionally an instance of a memcache store. If no store is passed, Rails.cache is assumed.
|
11
|
+
require 'active_support'
|
12
|
+
require 'memcache_array'
|
13
|
+
|
14
|
+
mamcache_array = MemcacheArray.new('my_key', ActiveSupport::Cache::MemCacheStore.new)
|
15
|
+
mamcache_array << [1, 2, 3]
|
16
|
+
mamcache_array << [4, 5, 6]
|
17
|
+
mamcache_array.all
|
18
|
+
=> [1, 2, 3, 1, 2, 3, 4, 5, 6]
|
19
|
+
mamcache_array.all(:delete => true)
|
20
|
+
=> [1, 2, 3, 1, 2, 3, 4, 5, 6]
|
21
|
+
mamcache_array.all
|
22
|
+
=> []
|
23
|
+
|
24
|
+
You can also pass metadata and filter for it when accessing the data. This way you can avoid reading large buckets only to discard them later.
|
25
|
+
require 'active_support'
|
26
|
+
require 'memcache_array'
|
27
|
+
|
28
|
+
mamcache_array = MemcacheArray.new('another_key', ActiveSupport::Cache::MemCacheStore.new)
|
29
|
+
mamcache_array.<<([1, 3, 5], 'odd')
|
30
|
+
mamcache_array.<<([2, 4, 6], 'even')
|
31
|
+
mamcache_array.<<([7], 'odd')
|
32
|
+
mamcache_array.all{|meta| meta == 'odd'}
|
33
|
+
=> [1, 3, 5, 7]
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class MemcacheArray
|
2
|
+
|
3
|
+
attr_reader :key
|
4
|
+
|
5
|
+
def initialize(key, client = nil)
|
6
|
+
@key = key
|
7
|
+
@client = client || Rails.cache
|
8
|
+
end
|
9
|
+
|
10
|
+
def <<(data, metadata = nil)
|
11
|
+
raise ArgumentError, "You can only append arrays." unless data.is_a? Array
|
12
|
+
index = next_index
|
13
|
+
@client.write(metadata_key_with_index(index), metadata) if metadata
|
14
|
+
@client.write(key_with_index(index), data)
|
15
|
+
end
|
16
|
+
|
17
|
+
def all(options = {})
|
18
|
+
delete = options[:delete]
|
19
|
+
indices.inject([]) do |accumulator, i|
|
20
|
+
key = key_with_index(i)
|
21
|
+
include_data =
|
22
|
+
if block_given?
|
23
|
+
metadata = @client.read(metadata_key_with_index(i))
|
24
|
+
metadata ? yield(metadata) : false
|
25
|
+
else
|
26
|
+
true
|
27
|
+
end
|
28
|
+
if include_data
|
29
|
+
value = @client.read(key)
|
30
|
+
delete_bucket(i) if delete
|
31
|
+
accumulator += value.is_a?(Array) ? value : []
|
32
|
+
else
|
33
|
+
accumulator
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete!
|
39
|
+
indices.each do |i|
|
40
|
+
@client.delete(key_with_index(i))
|
41
|
+
@client.delete(metadata_key_with_index(i))
|
42
|
+
end
|
43
|
+
@client.delete(index_key)
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete_bucket(index)
|
47
|
+
delete_index(index)
|
48
|
+
@client.delete(key_with_index(index))
|
49
|
+
end
|
50
|
+
|
51
|
+
def next_index
|
52
|
+
indices_new = indices.dup || []
|
53
|
+
indices_new << (indices.last || -1) + 1
|
54
|
+
@client.write(index_key, indices_new)
|
55
|
+
indices.last
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete_index(index)
|
59
|
+
@client.write(index_key, (indices - [index]))
|
60
|
+
end
|
61
|
+
|
62
|
+
def indices
|
63
|
+
@client.read(index_key) || []
|
64
|
+
end
|
65
|
+
|
66
|
+
def index_key
|
67
|
+
"#{@key}_indices"
|
68
|
+
end
|
69
|
+
|
70
|
+
def key_with_index(index)
|
71
|
+
"#{@key}_#{index}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def metadata_key_with_index(index)
|
75
|
+
"#{key_with_index(index)}_meta"
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require 'memcache_array'
|
3
|
+
|
4
|
+
class MockClient < Hash
|
5
|
+
def read(key)
|
6
|
+
self[key]
|
7
|
+
end
|
8
|
+
def write(key, value)
|
9
|
+
self[key] = value
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe MemcacheArray, 'expose its key' do
|
14
|
+
|
15
|
+
it 'should expose its key' do
|
16
|
+
client = MockClient.new
|
17
|
+
array = MemcacheArray.new('the_key', client)
|
18
|
+
array.key.should == 'the_key'
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe MemcacheArray, 'finding indices' do
|
24
|
+
|
25
|
+
before :each do
|
26
|
+
@client = MockClient.new
|
27
|
+
@client['the_key_indices'] = [0,1,2,3]
|
28
|
+
@array_few = MemcacheArray.new('the_key', @client)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should find the and lock the next index' do
|
32
|
+
@array_few.next_index.should == 4
|
33
|
+
@client['the_key_indices'].should == [0,1,2,3,4]
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
describe MemcacheArray, 'deleting indices' do
|
39
|
+
|
40
|
+
before :each do
|
41
|
+
@client = MockClient.new
|
42
|
+
@client['the_key_indices'] = [0,1,2,3]
|
43
|
+
@array = MemcacheArray.new('the_key', @client)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should find the and lock the next index' do
|
47
|
+
@client.should_receive(:write).with('the_key_indices', [0, 1, 3])
|
48
|
+
@array.delete_index(2)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
describe MemcacheArray, 'deleting data' do
|
54
|
+
|
55
|
+
before :each do
|
56
|
+
@client = MockClient.new
|
57
|
+
@array = MemcacheArray.new('the_key', @client)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should find the and lock the next index' do
|
61
|
+
@array.should_receive(:delete_index).with(2)
|
62
|
+
@client.should_receive(:delete).with('the_key_2')
|
63
|
+
@array.delete_bucket(2)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
describe MemcacheArray, 'writing' do
|
69
|
+
|
70
|
+
it 'should write' do
|
71
|
+
client = mock('Client')
|
72
|
+
array = MemcacheArray.new('the_key', client)
|
73
|
+
array.should_receive(:next_index).and_return(4)
|
74
|
+
client.should_receive('write').with('the_key_4', ['the_data'])
|
75
|
+
array << ['the_data']
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should write metadata' do
|
79
|
+
client = mock('Client')
|
80
|
+
array = MemcacheArray.new('the_key', client)
|
81
|
+
array.should_receive(:next_index).and_return(4)
|
82
|
+
client.should_receive(:write).with('the_key_4_meta', 'metadata')
|
83
|
+
client.should_receive(:write).with('the_key_4', ['the_data'])
|
84
|
+
array.<<(['the_data'], 'metadata')
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should raise an error when no array is passed' do
|
88
|
+
client = mock('Client')
|
89
|
+
array = MemcacheArray.new('the_key', client)
|
90
|
+
lambda {array << 'lala!'}.should raise_error
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
describe MemcacheArray, 'reading' do
|
96
|
+
|
97
|
+
before :each do
|
98
|
+
@client = MockClient.new
|
99
|
+
@client['the_key_indices'] = [0,1,2]
|
100
|
+
@client['the_key_0'] = ['null', 'zero']
|
101
|
+
@client['the_key_1'] = ['one']
|
102
|
+
@client['the_key_2'] = ['two']
|
103
|
+
@array = MemcacheArray.new('the_key', @client)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should read continous elements' do
|
107
|
+
@array.all.should == ['null', 'zero','one', 'two']
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should ignore nil elements' do
|
111
|
+
@client['the_key_indices'] = [0,1,2,3,4,5]
|
112
|
+
@client['the_key_5'] = ['five']
|
113
|
+
@array.all.should == ['null', 'zero', 'one', 'two', 'five']
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
describe MemcacheArray, 'reading with deleting' do
|
119
|
+
|
120
|
+
before :each do
|
121
|
+
@client = MockClient.new
|
122
|
+
@client['the_key_indices'] = [0,1,2]
|
123
|
+
@client['the_key_0'] = ['null', 'zero']
|
124
|
+
@client['the_key_1'] = ['one']
|
125
|
+
@client['the_key_2'] = ['two']
|
126
|
+
@array = MemcacheArray.new('the_key', @client)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should read continous elements' do
|
130
|
+
@array.all(:delete => true).should == ['null', 'zero','one', 'two']
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should read continous elements' do
|
134
|
+
@client.should_receive(:delete).with('the_key_0')
|
135
|
+
@client.should_receive(:delete).with('the_key_1')
|
136
|
+
@client.should_receive(:delete).with('the_key_2')
|
137
|
+
@array.all(:delete => true)
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
describe MemcacheArray, 'reading with metadata' do
|
143
|
+
|
144
|
+
before :each do
|
145
|
+
@client = MockClient.new
|
146
|
+
@client['the_key_indices'] = [0,1,2,3]
|
147
|
+
@client['the_key_0'] = ['null', 'zero']
|
148
|
+
@client['the_key_0_meta'] = 0
|
149
|
+
@client['the_key_1'] = ['one']
|
150
|
+
@client['the_key_1_meta'] = 1
|
151
|
+
@client['the_key_2'] = ['two']
|
152
|
+
@client['the_key_2_meta'] = 2
|
153
|
+
@client['the_key_3'] = ['three']
|
154
|
+
@array = MemcacheArray.new('the_key', @client)
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should respect block when getting elements' do
|
158
|
+
@array.all{|m| m > 0}.should == ['one', 'two']
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
describe MemcacheArray, 'deleting with metadata' do
|
164
|
+
|
165
|
+
before :each do
|
166
|
+
@client = MockClient.new
|
167
|
+
@client['the_key_indices'] = [0,1,2]
|
168
|
+
@client['the_key_0'] = ['null', 'zero']
|
169
|
+
@client['the_key_0_meta'] = 0
|
170
|
+
@client['the_key_1'] = ['one']
|
171
|
+
@client['the_key_1_meta'] = 1
|
172
|
+
@client['the_key_2'] = ['two']
|
173
|
+
@client['the_key_2_meta'] = 2
|
174
|
+
@array = MemcacheArray.new('the_key', @client)
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'should delete all data' do
|
178
|
+
@client.should_receive(:delete).with('the_key_0')
|
179
|
+
@client.should_receive(:delete).with('the_key_1')
|
180
|
+
@client.should_receive(:delete).with('the_key_2')
|
181
|
+
@client.should_receive(:delete).with('the_key_0_meta')
|
182
|
+
@client.should_receive(:delete).with('the_key_1_meta')
|
183
|
+
@client.should_receive(:delete).with('the_key_2_meta')
|
184
|
+
@client.should_receive(:delete).with('the_key_indices')
|
185
|
+
@array.delete!
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: memcache_array
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
version: "1.0"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- Florian Odronitz
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2011-02-22 00:00:00 +01:00
|
17
|
+
default_executable:
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: rspec
|
21
|
+
prerelease: false
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 2
|
29
|
+
- 3
|
30
|
+
- 0
|
31
|
+
version: 2.3.0
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
description:
|
35
|
+
email: odo@mac.com
|
36
|
+
executables: []
|
37
|
+
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files: []
|
41
|
+
|
42
|
+
files:
|
43
|
+
- README.rdoc
|
44
|
+
- LICENSE
|
45
|
+
- lib/memcache_array.rb
|
46
|
+
- spec/memcache_array_spec.rb
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: http://github.com/traveliq/memcache_array
|
49
|
+
licenses:
|
50
|
+
- MIT
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.3.7
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: MemcacheArray is a wrapper for Memcache so it can be used as shared memory holding arrays.
|
79
|
+
test_files:
|
80
|
+
- spec/memcache_array_spec.rb
|