memcache_array 1.0

Sign up to get free protection for your applications and to get access to all the features.
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