redis_array 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
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
@@ -0,0 +1,5 @@
1
+ guard 'rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
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
@@ -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
@@ -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
@@ -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: []