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 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