redis_array 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +23 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +76 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/redis_array.rb +207 -0
- data/redis_array.gemspec +72 -0
- data/spec/redis_array_spec.rb +306 -0
- data/spec/spec_helper.rb +13 -0
- metadata +190 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
gem 'redis'
|
9
|
+
gem 'rake'
|
10
|
+
gem 'jeweler'
|
11
|
+
|
12
|
+
group :development do
|
13
|
+
gem 'guard', "~> 1.6.0"
|
14
|
+
gem "guard-rspec", "~> 1.2.0"
|
15
|
+
gem 'rb-inotify', "~> 0.9.0", :require => false
|
16
|
+
gem 'rb-fsevent', "~> 0.9.3", :require => false
|
17
|
+
gem 'rb-fchange', "~> 0.0.6", :require => false
|
18
|
+
end
|
19
|
+
|
20
|
+
group :test do
|
21
|
+
gem "rspec", "~> 2.8.0"
|
22
|
+
gem "fakeredis", :require => "fakeredis/rspec", :git => "git://github.com/guilleiguaran/fakeredis.git"
|
23
|
+
end
|
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 James Richard
|
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,76 @@
|
|
1
|
+
= redis_array
|
2
|
+
|
3
|
+
This library provides a way to store Ruby arrays in redis with *little* fuss. You're free to store anything that can be
|
4
|
+
converted to a string and other arrays that contain objects that can be converted into a string. Anything else will throw
|
5
|
+
an error because Redis only supports string storage. In addition, if you store a value that is not a string but can
|
6
|
+
be converted into a string, such as an integer, when you access the value it will be a string. A work-around will be
|
7
|
+
developed in the near future to support serialized objects and typecasting, though.
|
8
|
+
|
9
|
+
redis_array works by creating a list in redis for each array. Array keys are unique, so you cannot have multiple
|
10
|
+
"test" keys, for example.
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
list1 = RedisArray.new("list1") # Redis key will be redisarray:list1
|
14
|
+
list2 = RedisMultiList.new("list2") # Redis key will be redisarray:list2
|
15
|
+
list1.push list2 # The value of index 0 in the list redisarray:list1 will be redisarray:~>list2
|
16
|
+
list1[0] # Will return a RedisArray object representing the values within redisarray:list2
|
17
|
+
```
|
18
|
+
|
19
|
+
All retreival actions are done through Redis, so you don't have to worry about concurrency between processes when
|
20
|
+
accessing a List, or creating a different list instance using the same key.
|
21
|
+
|
22
|
+
redis_array attempts to act more like a Ruby Array then a Redis List. For example, LPOP in redis removes the first
|
23
|
+
item within a list, whereas #pop on a RedisArray instance will remove the last.
|
24
|
+
|
25
|
+
== Configuration
|
26
|
+
|
27
|
+
redis_array needs to have a redis connection to work properly. Where ever you open your Redis connection you should set it.
|
28
|
+
For example:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
redis = Redis.new(ENV['OPENREDIS_URI'])
|
32
|
+
RedisArray.redis = redis
|
33
|
+
```
|
34
|
+
|
35
|
+
redis_array namespaces the arrays it stores. By default, it uses "redisarray". Feel free to change it to
|
36
|
+
whatever works and is unique.
|
37
|
+
|
38
|
+
== Usage
|
39
|
+
|
40
|
+
Use RedisArray.new("list-name") to create or obtain a list. If the list already exists in Redis this will not create
|
41
|
+
a new one; it will just access the existing. You can also use RedisArray.get("list-name"), which is just an alias for
|
42
|
+
new. Use this whether the list already exists or not. Nothing will be added into redis until a value is pushed in. You
|
43
|
+
can also call it without a list name, and a random one will be generated for you:
|
44
|
+
|
45
|
+
```
|
46
|
+
RedisArray.new
|
47
|
+
=> #<RedisArray:0x007fba4c4503b8 @key="e5f0dfbfa8fdcf2cef63f4ca6369a98c">
|
48
|
+
```
|
49
|
+
|
50
|
+
Each RedisArray instance supports the following methods, which behave like the Ruby Array class, along
|
51
|
+
with including the Enumerable module: [], all, count, []=, <<, +, push, pop, delete_at, delete, and clear.
|
52
|
+
|
53
|
+
== Gotchas
|
54
|
+
|
55
|
+
Redis doesn't include a way to delete by index. We've implemented a way to handle that process, but it is going to be
|
56
|
+
slow, and there's a chance of concurrency issues. I'll work on making a better way to handle the process itself, but
|
57
|
+
until delete-by-index is supported using that method should be avoided. It's better to delete by value.
|
58
|
+
|
59
|
+
Concatenating a List will append the rvalue to the list, rather then make a new one composed of the two values. This is
|
60
|
+
to maintain the key layout, but it is useful to add many items at once by providing them in an array.
|
61
|
+
|
62
|
+
== Contributing to redis_array
|
63
|
+
|
64
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
65
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
66
|
+
* Fork the project.
|
67
|
+
* Start a feature/bugfix branch.
|
68
|
+
* Commit and push until you are happy with your contribution.
|
69
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
70
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
71
|
+
|
72
|
+
== Copyright
|
73
|
+
|
74
|
+
Copyright (c) 2013 James Richard. See LICENSE.txt for
|
75
|
+
further details.
|
76
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "redis_array"
|
18
|
+
gem.homepage = "http://github.com/byliner/redis_array"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Easy Multi Dimensional Arrays in Redis}
|
21
|
+
gem.description = %Q{Create persistent, redis-backed arrays with a simple, familiar syntax}
|
22
|
+
gem.email = "ketzu@me.com"
|
23
|
+
gem.authors = ["James Richard"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :spec
|
40
|
+
|
41
|
+
require 'rdoc/task'
|
42
|
+
Rake::RDocTask.new do |rdoc|
|
43
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "redis_array #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/redis_array.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
class RedisArray
|
5
|
+
include Enumerable
|
6
|
+
attr_reader :key
|
7
|
+
|
8
|
+
def self.redis=(redis)
|
9
|
+
raise InvalidRedisInstanceError unless redis.is_a?(Redis)
|
10
|
+
@@redis = redis
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.redis
|
14
|
+
@@redis
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.get(key)
|
18
|
+
raise NoRedisConnectionError if !defined?(@@redis) || @@redis.nil?
|
19
|
+
RedisArray.new(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.namespace=(namespace)
|
23
|
+
@@namespace = namespace
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.namespace
|
27
|
+
(defined?(@@namespace) && !@@namespace.nil?) ? @@namespace : "redisarray"
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(key = nil)
|
31
|
+
@key = key.nil? ? generated_key : key
|
32
|
+
end
|
33
|
+
|
34
|
+
# -- Selection / Iteration
|
35
|
+
def each
|
36
|
+
@@redis.lrange(namespaced_key, 0, @@redis.llen(namespaced_key)).each do |value|
|
37
|
+
if sublist?(value)
|
38
|
+
yield RedisArray.new(remove_namespace(value))
|
39
|
+
else
|
40
|
+
yield value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def [](index)
|
46
|
+
value = @@redis.lindex(namespaced_key, index)
|
47
|
+
return nil if value.nil?
|
48
|
+
sublist?(value) ? RedisArray.new(remove_namespace(value)) : value
|
49
|
+
end
|
50
|
+
|
51
|
+
def all
|
52
|
+
array = @@redis.lrange(namespaced_key, 0, @@redis.llen(namespaced_key))
|
53
|
+
array.each_with_index do |value, i|
|
54
|
+
array[i] = RedisArray.new(remove_namespace(value)) if sublist?(value)
|
55
|
+
end
|
56
|
+
|
57
|
+
array
|
58
|
+
end
|
59
|
+
|
60
|
+
def count
|
61
|
+
@@redis.llen(namespaced_key)
|
62
|
+
end
|
63
|
+
|
64
|
+
# -- Comparison
|
65
|
+
# In the case where comp is another RedisArray object we just compare the key
|
66
|
+
# We don't want to be comparing redis values because the results will be the same
|
67
|
+
# if the keys match
|
68
|
+
def ==(comp)
|
69
|
+
comp.is_a?(RedisArray) ? @key == comp.key : to_a == comp
|
70
|
+
end
|
71
|
+
|
72
|
+
# -- Conversion
|
73
|
+
# This returns a raw, deep array
|
74
|
+
def to_a
|
75
|
+
array = all
|
76
|
+
array.each_with_index do |value, i|
|
77
|
+
array[i] = value.to_a if value.is_a?(RedisArray)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# -- Modification
|
82
|
+
def []=(index, value)
|
83
|
+
value = storable_value(value)
|
84
|
+
|
85
|
+
if index > 0 && !@@redis.exists(namespaced_key)
|
86
|
+
index.times { @@redis.rpush(namespaced_key, "") }
|
87
|
+
end
|
88
|
+
|
89
|
+
llen = @@redis.llen(namespaced_key)
|
90
|
+
|
91
|
+
if index < llen
|
92
|
+
@@redis.lset(namespaced_key, index, value)
|
93
|
+
else
|
94
|
+
(index - llen).times { @@redis.rpush(namespaced_key, "") }
|
95
|
+
@@redis.rpush(namespaced_key, value)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def <<(value)
|
100
|
+
push(value)
|
101
|
+
end
|
102
|
+
|
103
|
+
def +(value)
|
104
|
+
if value.is_a?(Array)
|
105
|
+
value.each do |item|
|
106
|
+
push(item)
|
107
|
+
end
|
108
|
+
else
|
109
|
+
push(value)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def push(value)
|
114
|
+
@@redis.rpush(namespaced_key, storable_value(value))
|
115
|
+
end
|
116
|
+
|
117
|
+
def pop
|
118
|
+
@@redis.ltrim(namespaced_key, 0, -2)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Redis doesn't support delete at index, so we're copying the values in redis, keeping all but the index we're
|
122
|
+
# removing, deleting the list, and reloading it. Due to the complexity of this call it is recommended that you
|
123
|
+
# do not use it.
|
124
|
+
def delete_at(index)
|
125
|
+
len = count
|
126
|
+
values = @@redis.lrange(namespaced_key, 0, len-1)
|
127
|
+
@@redis.multi do
|
128
|
+
new_values = []
|
129
|
+
|
130
|
+
values.each_with_index do |value, i|
|
131
|
+
new_values << value unless i == index
|
132
|
+
end
|
133
|
+
|
134
|
+
@@redis.del(namespaced_key)
|
135
|
+
new_values.each do |value|
|
136
|
+
@@redis.rpush(namespaced_key, value)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def delete(value)
|
142
|
+
value = value.is_a?(RedisArray) ? sublist_value(value) : value
|
143
|
+
@@redis.lrem(namespaced_key, 0, value)
|
144
|
+
end
|
145
|
+
|
146
|
+
def clear
|
147
|
+
@@redis.del(namespaced_key)
|
148
|
+
end
|
149
|
+
|
150
|
+
# -- Storage helpers
|
151
|
+
def self.namespaced_key_for(key)
|
152
|
+
"#{RedisArray.namespace}:#{key}"
|
153
|
+
end
|
154
|
+
|
155
|
+
def namespaced_key
|
156
|
+
RedisArray.namespaced_key_for(@key)
|
157
|
+
end
|
158
|
+
|
159
|
+
protected
|
160
|
+
|
161
|
+
def remove_namespace(key)
|
162
|
+
@@_remove_namespace_regex ||= Regexp.new("#{RedisArray.namespace}:(~>)?(.+)")
|
163
|
+
key.match(@@_remove_namespace_regex)[2]
|
164
|
+
end
|
165
|
+
|
166
|
+
def storable_value(value)
|
167
|
+
raise ArgumentError, "The value #{value} does not represent a list, is not a string, and cannot be made a string" unless value_storable?(value)
|
168
|
+
determine_stored_value(value)
|
169
|
+
end
|
170
|
+
|
171
|
+
def value_storable?(value)
|
172
|
+
return true if value.is_a?(RedisArray) || value.is_a?(Array)
|
173
|
+
return true if value.respond_to? :to_s
|
174
|
+
false
|
175
|
+
end
|
176
|
+
|
177
|
+
def determine_stored_value(value)
|
178
|
+
if value.is_a?(RedisArray)
|
179
|
+
sublist_value(value)
|
180
|
+
elsif value.is_a?(Array)
|
181
|
+
list = RedisArray.new
|
182
|
+
value.each do |subvalue|
|
183
|
+
list << subvalue
|
184
|
+
end
|
185
|
+
|
186
|
+
sublist_value(list)
|
187
|
+
else
|
188
|
+
value.to_s
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def generated_key
|
193
|
+
SecureRandom.hex
|
194
|
+
end
|
195
|
+
|
196
|
+
def sublist?(value)
|
197
|
+
@@_sublist_matcher = Regexp.new("^#{RedisArray.namespace}:~>")
|
198
|
+
value.match(@@_sublist_matcher)
|
199
|
+
end
|
200
|
+
|
201
|
+
def sublist_value(list)
|
202
|
+
"#{RedisArray.namespace}:~>#{list.key}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
class NoRedisConnectionError < StandardError; end
|
207
|
+
class InvalidRedisInstanceError < StandardError; end
|
data/redis_array.gemspec
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "redis_array"
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["James Richard"]
|
12
|
+
s.date = "2013-02-08"
|
13
|
+
s.description = "Create persistent, redis-backed arrays with a simple, familiar syntax"
|
14
|
+
s.email = "ketzu@me.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rspec",
|
22
|
+
"Gemfile",
|
23
|
+
"Guardfile",
|
24
|
+
"LICENSE.txt",
|
25
|
+
"README.rdoc",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"lib/redis_array.rb",
|
29
|
+
"redis_array.gemspec",
|
30
|
+
"spec/redis_array_spec.rb",
|
31
|
+
"spec/spec_helper.rb"
|
32
|
+
]
|
33
|
+
s.homepage = "http://github.com/byliner/redis_array"
|
34
|
+
s.licenses = ["MIT"]
|
35
|
+
s.require_paths = ["lib"]
|
36
|
+
s.rubygems_version = "1.8.24"
|
37
|
+
s.summary = "Easy Multi Dimensional Arrays in Redis"
|
38
|
+
|
39
|
+
if s.respond_to? :specification_version then
|
40
|
+
s.specification_version = 3
|
41
|
+
|
42
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
43
|
+
s.add_runtime_dependency(%q<redis>, [">= 0"])
|
44
|
+
s.add_runtime_dependency(%q<rake>, [">= 0"])
|
45
|
+
s.add_runtime_dependency(%q<jeweler>, [">= 0"])
|
46
|
+
s.add_development_dependency(%q<guard>, ["~> 1.6.0"])
|
47
|
+
s.add_development_dependency(%q<guard-rspec>, ["~> 1.2.0"])
|
48
|
+
s.add_development_dependency(%q<rb-inotify>, ["~> 0.9.0"])
|
49
|
+
s.add_development_dependency(%q<rb-fsevent>, ["~> 0.9.3"])
|
50
|
+
s.add_development_dependency(%q<rb-fchange>, ["~> 0.0.6"])
|
51
|
+
else
|
52
|
+
s.add_dependency(%q<redis>, [">= 0"])
|
53
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
54
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
55
|
+
s.add_dependency(%q<guard>, ["~> 1.6.0"])
|
56
|
+
s.add_dependency(%q<guard-rspec>, ["~> 1.2.0"])
|
57
|
+
s.add_dependency(%q<rb-inotify>, ["~> 0.9.0"])
|
58
|
+
s.add_dependency(%q<rb-fsevent>, ["~> 0.9.3"])
|
59
|
+
s.add_dependency(%q<rb-fchange>, ["~> 0.0.6"])
|
60
|
+
end
|
61
|
+
else
|
62
|
+
s.add_dependency(%q<redis>, [">= 0"])
|
63
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
64
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
65
|
+
s.add_dependency(%q<guard>, ["~> 1.6.0"])
|
66
|
+
s.add_dependency(%q<guard-rspec>, ["~> 1.2.0"])
|
67
|
+
s.add_dependency(%q<rb-inotify>, ["~> 0.9.0"])
|
68
|
+
s.add_dependency(%q<rb-fsevent>, ["~> 0.9.3"])
|
69
|
+
s.add_dependency(%q<rb-fchange>, ["~> 0.0.6"])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
@@ -0,0 +1,306 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe RedisArray do
|
4
|
+
after :each do
|
5
|
+
if defined?(Redis::Connection::Memory) # fake redis
|
6
|
+
Redis::Connection::Memory.reset_all_databases
|
7
|
+
else # Sometimes we test against a live redis connection.
|
8
|
+
RedisArray.redis.del(RedisArray.redis.keys("#{RedisArray.namespace}:*"))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "defining redis" do
|
13
|
+
before(:each) do
|
14
|
+
RedisArray.class_variable_set :@@redis, nil
|
15
|
+
end
|
16
|
+
|
17
|
+
it "provides an interface to set the redis instance" do
|
18
|
+
RedisArray.respond_to?(:redis=).should be_true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "raises a InvalidRedisInstanceError if we attempt to set redis to a non-redis instance" do
|
22
|
+
expect { RedisArray.redis = Array.new }.to raise_exception InvalidRedisInstanceError
|
23
|
+
end
|
24
|
+
|
25
|
+
it "can return the redis instance" do
|
26
|
+
redis = Redis.new
|
27
|
+
RedisArray.redis = redis
|
28
|
+
RedisArray.redis.should == redis
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "getting lists" do
|
33
|
+
before(:each) do
|
34
|
+
RedisArray.class_variable_set :@@redis, nil
|
35
|
+
end
|
36
|
+
|
37
|
+
it "raises a NoRedisConnectionError if we attempt to use get without a redis connection" do
|
38
|
+
expect { RedisArray.get("some key") }.to raise_exception NoRedisConnectionError
|
39
|
+
end
|
40
|
+
|
41
|
+
it "returns a RedisArray object representing the key" do
|
42
|
+
RedisArray.redis = Redis.new
|
43
|
+
list = RedisArray.get("test")
|
44
|
+
list.should be_an_instance_of RedisArray
|
45
|
+
list.key.should == "test"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "namespacing" do
|
50
|
+
before(:each) do
|
51
|
+
RedisArray.class_variable_set :@@namespace, nil
|
52
|
+
end
|
53
|
+
|
54
|
+
it "can set a custom namespace" do
|
55
|
+
RedisArray.namespace = "rml"
|
56
|
+
RedisArray.namespace.should == "rml"
|
57
|
+
end
|
58
|
+
|
59
|
+
it "sets a default key namespace to RedisArray" do
|
60
|
+
RedisArray.namespace.should == "redisarray"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "list usage" do
|
65
|
+
before(:each) do
|
66
|
+
RedisArray.redis = Redis.new
|
67
|
+
end
|
68
|
+
|
69
|
+
context "element access" do
|
70
|
+
it "can access a random item within the list" do
|
71
|
+
k = namespaced_key("test")
|
72
|
+
RedisArray.redis.rpush(k, "test-value")
|
73
|
+
RedisArray.redis.rpush(k, "test-value2")
|
74
|
+
list = RedisArray.new("test")
|
75
|
+
list[0].should == "test-value"
|
76
|
+
list[1].should == "test-value2"
|
77
|
+
end
|
78
|
+
|
79
|
+
it "returns nil if the index does not exist within the list" do
|
80
|
+
k = namespaced_key("test")
|
81
|
+
RedisArray.redis.rpush(k, "test-value")
|
82
|
+
RedisArray.redis.rpush(k, "test-value2")
|
83
|
+
list = RedisArray.new("test")
|
84
|
+
list[2].should be_nil
|
85
|
+
end
|
86
|
+
|
87
|
+
it "can set a random item within the list that has already been set" do
|
88
|
+
k = namespaced_key("test")
|
89
|
+
RedisArray.redis.rpush(k, "test-value")
|
90
|
+
list = RedisArray.new("test")
|
91
|
+
list[0] = "test-value2"
|
92
|
+
RedisArray.redis.lindex(k, 0).should == "test-value2"
|
93
|
+
end
|
94
|
+
|
95
|
+
it "can set a random item within the list that has not been set" do
|
96
|
+
k = namespaced_key("test")
|
97
|
+
list = RedisArray.new("test")
|
98
|
+
list[0] = "test-value0"
|
99
|
+
list[4] = "test-value4"
|
100
|
+
RedisArray.redis.lrange(k, 0, 5).should == ["test-value0", "", "", "", "test-value4"]
|
101
|
+
end
|
102
|
+
|
103
|
+
it "can append an item to the list" do
|
104
|
+
k = namespaced_key("test")
|
105
|
+
list = RedisArray.new("test")
|
106
|
+
list << "test-value0"
|
107
|
+
RedisArray.redis.lindex(k, 0).should == "test-value0"
|
108
|
+
end
|
109
|
+
|
110
|
+
it "can set a sub list within a list that has been set" do
|
111
|
+
k = namespaced_key("test")
|
112
|
+
RedisArray.redis.rpush(k, "test-value")
|
113
|
+
list = RedisArray.new("test")
|
114
|
+
sublist = RedisArray.new("test2")
|
115
|
+
list[0] = sublist
|
116
|
+
RedisArray.redis.lindex(k, 0).should == "redisarray:~>test2"
|
117
|
+
end
|
118
|
+
|
119
|
+
it "returns a List object when accessing subscript and the value points to a list" do
|
120
|
+
k = namespaced_key("test")
|
121
|
+
RedisArray.redis.rpush(k, "redisarray:~>test2")
|
122
|
+
list = RedisArray.new("test")
|
123
|
+
sublist = RedisArray.new("test2")
|
124
|
+
list[0].should == sublist
|
125
|
+
end
|
126
|
+
|
127
|
+
it "can set a sub list within a list that has not been set" do
|
128
|
+
k = namespaced_key("test")
|
129
|
+
list = RedisArray.new("test")
|
130
|
+
list[0] = RedisArray.new("test2")
|
131
|
+
RedisArray.redis.lindex(k, 0).should == "redisarray:~>test2"
|
132
|
+
end
|
133
|
+
|
134
|
+
it "can append a sub list within a list" do
|
135
|
+
k = namespaced_key("test")
|
136
|
+
list = RedisArray.new("test")
|
137
|
+
sublist = RedisArray.new("test2")
|
138
|
+
list << sublist
|
139
|
+
RedisArray.redis.lindex(k, 0).should == "redisarray:~>test2"
|
140
|
+
end
|
141
|
+
|
142
|
+
it "can store a sub list without a name" do
|
143
|
+
k = namespaced_key("test")
|
144
|
+
list = RedisArray.new("test")
|
145
|
+
sublist = RedisArray.new
|
146
|
+
list << sublist
|
147
|
+
RedisArray.redis.lindex(k, 0).should == "redisarray:~>#{sublist.key}"
|
148
|
+
end
|
149
|
+
|
150
|
+
it "can store a list without a name" do
|
151
|
+
list = RedisArray.new
|
152
|
+
list << "test-value"
|
153
|
+
RedisArray.redis.lindex("redisarray:#{list.key}", 0).should == "test-value"
|
154
|
+
end
|
155
|
+
|
156
|
+
it "can store a regular array as a sub list" do
|
157
|
+
k = namespaced_key("test")
|
158
|
+
list = RedisArray.new("test")
|
159
|
+
sublist = %w(test test2 test3)
|
160
|
+
list << sublist
|
161
|
+
k2 = "redisarray:#{list[0].key}"
|
162
|
+
RedisArray.redis.lindex(k, 0).should == "redisarray:~>#{list[0].key}"
|
163
|
+
RedisArray.redis.lrange("redisarray:#{list[0].key}", 0, 2).should == sublist
|
164
|
+
end
|
165
|
+
|
166
|
+
it "can loop through the elements in the list" do
|
167
|
+
k = namespaced_key("test")
|
168
|
+
RedisArray.redis.rpush(k, "test-value")
|
169
|
+
RedisArray.redis.rpush(k, "test-value2")
|
170
|
+
RedisArray.redis.rpush(k, "test-value3")
|
171
|
+
list = RedisArray.new("test")
|
172
|
+
list.each_with_index do |item, i|
|
173
|
+
case i
|
174
|
+
when 0 then item.should == "test-value"
|
175
|
+
when 1 then item.should == "test-value2"
|
176
|
+
when 2 then item.should == "test-value3"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
it "can concatenate an array of items/lists" do
|
182
|
+
list = RedisArray.new("test")
|
183
|
+
sublist = RedisArray.new("subtest")
|
184
|
+
sublist << "subtest1"
|
185
|
+
sublist << "subtest2"
|
186
|
+
list += ["test1", "test2", sublist]
|
187
|
+
list.should == ["test1", "test2", ["subtest1", "subtest2"]]
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
it "can be converted into a plain array" do
|
193
|
+
list = RedisArray.new("test")
|
194
|
+
|
195
|
+
sublist1 = RedisArray.new("sublist1")
|
196
|
+
sublist1 << "subtest1-1"
|
197
|
+
sublist1 << "subtest1-2"
|
198
|
+
|
199
|
+
sublist2 = RedisArray.new("sublist2")
|
200
|
+
sublist2 << "subtest2-1"
|
201
|
+
sublist2 << "subtest2-2"
|
202
|
+
|
203
|
+
list << "test1"
|
204
|
+
list << "test2"
|
205
|
+
list << sublist1
|
206
|
+
list << "test3"
|
207
|
+
list << sublist2
|
208
|
+
|
209
|
+
list.to_a.should == ["test1", "test2", ["subtest1-1", "subtest1-2"], "test3", ["subtest2-1", "subtest2-2"]]
|
210
|
+
end
|
211
|
+
|
212
|
+
it "can determine the number of elements in the list" do
|
213
|
+
list = RedisArray.new("test")
|
214
|
+
list << "test1"
|
215
|
+
list << "test2"
|
216
|
+
list << "test3"
|
217
|
+
list.count.should == 3
|
218
|
+
end
|
219
|
+
|
220
|
+
it "can determine if there are any elements in the list" do
|
221
|
+
list = RedisArray.new("test")
|
222
|
+
list.any?.should_not be_true
|
223
|
+
list << "test1"
|
224
|
+
list.any?.should be_true
|
225
|
+
end
|
226
|
+
|
227
|
+
it "can remove an item from the list by index" do
|
228
|
+
k = namespaced_key("test")
|
229
|
+
RedisArray.redis.rpush(k, "test-value")
|
230
|
+
RedisArray.redis.rpush(k, "test-value2")
|
231
|
+
RedisArray.redis.rpush(k, "test-value3")
|
232
|
+
list = RedisArray.new("test")
|
233
|
+
list.delete_at(1)
|
234
|
+
RedisArray.redis.lrange(k, 0, 3).should == %w(test-value test-value3)
|
235
|
+
end
|
236
|
+
|
237
|
+
it "can pop the last element from a list" do
|
238
|
+
k = namespaced_key("test")
|
239
|
+
RedisArray.redis.rpush(k, "test-value")
|
240
|
+
RedisArray.redis.rpush(k, "test-value2")
|
241
|
+
RedisArray.redis.rpush(k, "test-value3")
|
242
|
+
list = RedisArray.new("test")
|
243
|
+
list.pop
|
244
|
+
RedisArray.redis.lrange(k, 0, 3).should == %w(test-value test-value2)
|
245
|
+
end
|
246
|
+
|
247
|
+
it "can remove all items from a list" do
|
248
|
+
k = namespaced_key("test")
|
249
|
+
RedisArray.redis.rpush(k, "test-value")
|
250
|
+
RedisArray.redis.rpush(k, "test-value2")
|
251
|
+
RedisArray.redis.rpush(k, "test-value3")
|
252
|
+
list = RedisArray.new("test")
|
253
|
+
list.clear
|
254
|
+
RedisArray.redis.exists(k).should_not be_true
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
it "can remove all items from a list as well as all sublists" do
|
259
|
+
pending "the addition of deep removals"
|
260
|
+
end
|
261
|
+
|
262
|
+
it "can remove an item matching a value from a list" do
|
263
|
+
k = namespaced_key("test")
|
264
|
+
RedisArray.redis.rpush(k, "test-value")
|
265
|
+
RedisArray.redis.rpush(k, "test-value2")
|
266
|
+
RedisArray.redis.rpush(k, "test-value3")
|
267
|
+
list = RedisArray.new("test")
|
268
|
+
list.delete("test-value")
|
269
|
+
RedisArray.redis.lrange(k, 0, 2).should == %w(test-value2 test-value3)
|
270
|
+
end
|
271
|
+
|
272
|
+
it "can remove a sublist from a list" do
|
273
|
+
k = namespaced_key("test")
|
274
|
+
RedisArray.redis.rpush(k, "test-value")
|
275
|
+
RedisArray.redis.rpush(k, "redisarray:~>test2")
|
276
|
+
RedisArray.redis.rpush(k, "test-value3")
|
277
|
+
list = RedisArray.new("test")
|
278
|
+
list2 = RedisArray.new("test2")
|
279
|
+
list.delete(list2)
|
280
|
+
RedisArray.redis.lrange(k, 0, 2).should == %w(test-value test-value3)
|
281
|
+
end
|
282
|
+
|
283
|
+
it "does not remove elements of a sublist when the sublist is removed from a parent list" do
|
284
|
+
k = namespaced_key("test")
|
285
|
+
k2 = "RedisArray:~>test2"
|
286
|
+
RedisArray.redis.rpush(k, k2)
|
287
|
+
RedisArray.redis.rpush(k2, "test-value")
|
288
|
+
RedisArray.redis.rpush(k2, "test-value2")
|
289
|
+
|
290
|
+
list = RedisArray.new("test")
|
291
|
+
list2 = RedisArray.new("test2")
|
292
|
+
|
293
|
+
list.delete(list2)
|
294
|
+
RedisArray.redis.lrange(k2, 0, 2).should == %w(test-value test-value2)
|
295
|
+
end
|
296
|
+
|
297
|
+
it "can remove a sublist from a list as well as all sublists" do
|
298
|
+
pending "the addition of deep removals"
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
def namespaced_key(key)
|
304
|
+
"#{RedisArray.namespace}:#{key}"
|
305
|
+
end
|
306
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'redis_array'
|
5
|
+
require 'fakeredis'
|
6
|
+
|
7
|
+
# Requires supporting files with custom matchers and macros, etc,
|
8
|
+
# in ./support/ and its subdirectories.
|
9
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redis_array
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- James Richard
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: redis
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: jeweler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: guard
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.6.0
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.6.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: guard-rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 1.2.0
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.2.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rb-inotify
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 0.9.0
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 0.9.0
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rb-fsevent
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.9.3
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 0.9.3
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rb-fchange
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ~>
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 0.0.6
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ~>
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 0.0.6
|
142
|
+
description: Create persistent, redis-backed arrays with a simple, familiar syntax
|
143
|
+
email: ketzu@me.com
|
144
|
+
executables: []
|
145
|
+
extensions: []
|
146
|
+
extra_rdoc_files:
|
147
|
+
- LICENSE.txt
|
148
|
+
- README.rdoc
|
149
|
+
files:
|
150
|
+
- .document
|
151
|
+
- .rspec
|
152
|
+
- Gemfile
|
153
|
+
- Guardfile
|
154
|
+
- LICENSE.txt
|
155
|
+
- README.rdoc
|
156
|
+
- Rakefile
|
157
|
+
- VERSION
|
158
|
+
- lib/redis_array.rb
|
159
|
+
- redis_array.gemspec
|
160
|
+
- spec/redis_array_spec.rb
|
161
|
+
- spec/spec_helper.rb
|
162
|
+
homepage: http://github.com/byliner/redis_array
|
163
|
+
licenses:
|
164
|
+
- MIT
|
165
|
+
post_install_message:
|
166
|
+
rdoc_options: []
|
167
|
+
require_paths:
|
168
|
+
- lib
|
169
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
170
|
+
none: false
|
171
|
+
requirements:
|
172
|
+
- - ! '>='
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
segments:
|
176
|
+
- 0
|
177
|
+
hash: -873787881241495935
|
178
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
179
|
+
none: false
|
180
|
+
requirements:
|
181
|
+
- - ! '>='
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0'
|
184
|
+
requirements: []
|
185
|
+
rubyforge_project:
|
186
|
+
rubygems_version: 1.8.24
|
187
|
+
signing_key:
|
188
|
+
specification_version: 3
|
189
|
+
summary: Easy Multi Dimensional Arrays in Redis
|
190
|
+
test_files: []
|