switch_board 0.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.
- checksums.yaml +7 -0
- data/.document +4 -0
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/ChangeLog.rdoc +4 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +39 -0
- data/LICENSE.txt +20 -0
- data/README.md +78 -0
- data/Rakefile +41 -0
- data/lib/switch_board/configuration.rb +13 -0
- data/lib/switch_board/datasets/abstract_dataset.rb +62 -0
- data/lib/switch_board/datasets/redis_dataset.rb +102 -0
- data/lib/switch_board/version.rb +3 -0
- data/lib/switch_board.rb +26 -0
- data/run_test +1 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/switch_board_spec.rb +166 -0
- data/switch_board.gemspec +23 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 91e063073337aaae7434db2dfac68242ede19862
|
4
|
+
data.tar.gz: 69c598242c91f8cae70bc15e1d36bd1bec949fc4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 710f0b21c6665cfaa75493c1fa3c02a464a45ed8589c5542868573558b180d9b3d6cc193132b230656139fbc972b033da111811ee72670696daf95e2db04d0a7
|
7
|
+
data.tar.gz: 50449a1bb33c60dec1cbc2e4673f5b71ce99d44428d2dcc2d5b8c1978526a8456a561111e1f9a5fe9a1f6a23e7deb6b8f96ced6fadfb78da1cb6cf12a02f104c
|
data/.document
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour --format documentation
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
switch_board
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.0.0-p247
|
data/ChangeLog.rdoc
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
switch_board (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
builder (3.2.2)
|
10
|
+
diff-lcs (1.2.4)
|
11
|
+
json (1.8.0)
|
12
|
+
rdoc (4.0.1)
|
13
|
+
json (~> 1.4)
|
14
|
+
redis (3.0.2)
|
15
|
+
redis-objects (0.6.1)
|
16
|
+
redis (>= 3.0.2)
|
17
|
+
rsolr (1.0.9)
|
18
|
+
builder (>= 2.1.2)
|
19
|
+
rspec (2.14.1)
|
20
|
+
rspec-core (~> 2.14.0)
|
21
|
+
rspec-expectations (~> 2.14.0)
|
22
|
+
rspec-mocks (~> 2.14.0)
|
23
|
+
rspec-core (2.14.3)
|
24
|
+
rspec-expectations (2.14.0)
|
25
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
26
|
+
rspec-mocks (2.14.1)
|
27
|
+
rubygems-tasks (0.2.4)
|
28
|
+
|
29
|
+
PLATFORMS
|
30
|
+
ruby
|
31
|
+
|
32
|
+
DEPENDENCIES
|
33
|
+
rdoc
|
34
|
+
redis
|
35
|
+
redis-objects
|
36
|
+
rsolr
|
37
|
+
rspec
|
38
|
+
rubygems-tasks
|
39
|
+
switch_board!
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Avner Cohen
|
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.md
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
## switch_board
|
2
|
+
|
3
|
+

|
4
|
+
|
5
|
+
### Description
|
6
|
+
|
7
|
+
**SwitchBoard** is a utility gem designed to help in the coordination of locked objects by a set of "lockers".
|
8
|
+
Think of a bank's cashiers, where customers are in line to be served.
|
9
|
+
When a customer is served by a cashier, it is still in line to be served, but is now "locked".
|
10
|
+
|
11
|
+
Locking expiration is allowed so that if during "serving" a cashier got some other business to do and runs home, it will go back to the queue when lock expires.
|
12
|
+
|
13
|
+
The overall scope of the gem is:
|
14
|
+
|
15
|
+
* Allow "lockers" to register themselves
|
16
|
+
* Allow "lockers" to set a "lock" on object, with a predefined expiration period
|
17
|
+
* Allow lockers with special "roles" to force locks
|
18
|
+
* Allow external observes to see current state of locked object
|
19
|
+
|
20
|
+
### Features
|
21
|
+
|
22
|
+
The system is light weight and is designed to have low number of "lockers" with many object to be locked.
|
23
|
+
It is pluggable and well tested so should allow extensions as needed.
|
24
|
+
|
25
|
+
### Examples
|
26
|
+
|
27
|
+
````ruby
|
28
|
+
|
29
|
+
require 'switch_board'
|
30
|
+
|
31
|
+
|
32
|
+
#create a new switch_board configuration
|
33
|
+
sb = SwitchBoard::Configuration.new #Redis backends
|
34
|
+
dataset = sb.dataset
|
35
|
+
|
36
|
+
#Register Lockers (unique identifier, Name/Alias)
|
37
|
+
dataset.register_locker(1, "Django")
|
38
|
+
dataset.register_locker(2, "Pier")
|
39
|
+
dataset.register_locker(3, "Mark")
|
40
|
+
|
41
|
+
#Print out the list of active users
|
42
|
+
p dataset.list_lockers
|
43
|
+
|
44
|
+
#Lock IDs for Pier - IDed as 2
|
45
|
+
dataset.lock_id(2, "qwerfggj", 5) # lock for 5 seconds
|
46
|
+
dataset.lock_id(2, "12345", 600) #Lock for 10 minutes
|
47
|
+
|
48
|
+
#Check to see if ID is locked
|
49
|
+
dataset.id_locked?("12345") #=> true
|
50
|
+
dataset.id_locked?("qwerfggj") #=> true
|
51
|
+
dataset.id_locked?("not_locked_id") #=> false
|
52
|
+
|
53
|
+
#Show all locked objects
|
54
|
+
dataset.get_all_locked_ids #=> {"12345"=>"2", "qwerfggj"=>"2"}
|
55
|
+
|
56
|
+
````
|
57
|
+
### Other Gems
|
58
|
+
|
59
|
+
Worth mentioning that there are other nice gems that takes care of Redis-backed Mutex implementaion:
|
60
|
+
|
61
|
+
* https://github.com/dv/redis-semaphore
|
62
|
+
* https://github.com/mlanett/redis-lock
|
63
|
+
* https://github.com/kenn/redis-mutex
|
64
|
+
|
65
|
+
However, this gem is not target a protection on specific a single resource during operation,
|
66
|
+
instead it is targated to manage Distrbition of work between multiple clients/lockers that can take longer time to process the locked resources.
|
67
|
+
It is also not targated for high scale systems, locking is done by humans so there is little to no risk in race conditions.
|
68
|
+
And lastly, the gem provides an API to get all currently locked IDs which is important for the "switch_board" problem where some high level managment of the currently locked ID is needed.
|
69
|
+
|
70
|
+
|
71
|
+
### Install
|
72
|
+
|
73
|
+
````
|
74
|
+
$ gem install switch_board
|
75
|
+
````
|
76
|
+
### Copyright
|
77
|
+
|
78
|
+
See LICENSE.txt for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rake'
|
5
|
+
|
6
|
+
begin
|
7
|
+
gem 'rubygems-tasks', '~> 0.2'
|
8
|
+
require 'rubygems/tasks'
|
9
|
+
|
10
|
+
Gem::Tasks.new
|
11
|
+
rescue LoadError => e
|
12
|
+
warn e.message
|
13
|
+
warn "Run `gem install rubygems-tasks` to install Gem::Tasks."
|
14
|
+
end
|
15
|
+
|
16
|
+
begin
|
17
|
+
gem 'rdoc', '~> 3.0'
|
18
|
+
require 'rdoc/task'
|
19
|
+
|
20
|
+
RDoc::Task.new do |rdoc|
|
21
|
+
rdoc.title = "switch_board"
|
22
|
+
end
|
23
|
+
rescue LoadError => e
|
24
|
+
warn e.message
|
25
|
+
warn "Run `gem install rdoc` to install 'rdoc/task'."
|
26
|
+
end
|
27
|
+
task :doc => :rdoc
|
28
|
+
|
29
|
+
begin
|
30
|
+
gem 'rspec', '~> 2.4'
|
31
|
+
require 'rspec/core/rake_task'
|
32
|
+
|
33
|
+
RSpec::Core::RakeTask.new
|
34
|
+
rescue LoadError => e
|
35
|
+
task :spec do
|
36
|
+
abort "Please run `gem install rspec` to install RSpec."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
task :test => :spec
|
41
|
+
task :default => :spec
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#define API for the data set
|
2
|
+
module SwitchBoard
|
3
|
+
class AbstractDataset
|
4
|
+
|
5
|
+
attr_accessor :persistance
|
6
|
+
|
7
|
+
def set_persistance(persistance)
|
8
|
+
@persistance = persistance
|
9
|
+
end
|
10
|
+
|
11
|
+
#Returns the next Model/ID that is available from the dataset
|
12
|
+
def get_next(limit = 1)
|
13
|
+
raise "#{__method__} not implemented in #{self.class.name}"
|
14
|
+
end
|
15
|
+
|
16
|
+
#get the IDs that are now locked by other lockers
|
17
|
+
def get_locked
|
18
|
+
raise "#{__method__} not implemented in #{self.class.name}"
|
19
|
+
end
|
20
|
+
|
21
|
+
#setup a new switchboard, a coordination persistence schema
|
22
|
+
def switchboard
|
23
|
+
raise "#{__method__} not implemented in #{self.class.name}"
|
24
|
+
end
|
25
|
+
|
26
|
+
#Add a new locker to the switchboard for future coordination
|
27
|
+
def register_locker(uid, name)
|
28
|
+
raise "#{__method__} not implemented in #{self.class.name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
#list all the lockers registerd for this switchboard
|
32
|
+
def list_lockers
|
33
|
+
raise "#{__method__} not implemented in #{self.class.name}"
|
34
|
+
end
|
35
|
+
|
36
|
+
#list retrive data of a specific locker
|
37
|
+
def locker(uid)
|
38
|
+
raise "#{__method__} not implemented in #{self.class.name}"
|
39
|
+
end
|
40
|
+
|
41
|
+
#Set ID of an object as locked for a specific uid
|
42
|
+
def lock_id(locker_uid, id_to_lock, expire_in_sec = 60)
|
43
|
+
raise "#{__method__} not implemented in #{self.class.name}"
|
44
|
+
end
|
45
|
+
|
46
|
+
#Set ID of an object as locked for a specific uid
|
47
|
+
def unlock_id(locker_uid, id_to_unlock)
|
48
|
+
raise "#{__method__} not implemented in #{self.class.name}"
|
49
|
+
end
|
50
|
+
|
51
|
+
#Check to see if a certain ID is locked or not
|
52
|
+
def id_locked?(uid)
|
53
|
+
raise "#{__method__} not implemented in #{self.class.name}"
|
54
|
+
end
|
55
|
+
|
56
|
+
#Retrive all the locked ids in the switchboard
|
57
|
+
def get_all_locked_ids
|
58
|
+
raise "#{__method__} not implemented in #{self.class.name}"
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'switch_board/datasets/abstract_dataset'
|
2
|
+
require "redis"
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module SwitchBoard
|
6
|
+
|
7
|
+
class RedisDataset < SwitchBoard::AbstractDataset
|
8
|
+
|
9
|
+
|
10
|
+
LOCK_MAP_KEY = "switch_board::locked_ids"
|
11
|
+
attr_accessor :con, :switchboard, :name
|
12
|
+
|
13
|
+
def initialize(host = "127.0.0.1", port = 6379, name = "redis_switchbord")
|
14
|
+
@con = Redis.new(:host => host, :port => port)
|
15
|
+
@name = name
|
16
|
+
end
|
17
|
+
|
18
|
+
def cleanup
|
19
|
+
## clean up keys, used mainly for testing
|
20
|
+
@con.del @name
|
21
|
+
@con.del "#{LOCK_MAP_KEY}_z"
|
22
|
+
@con.del "#{LOCK_MAP_KEY}_h"
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_locked
|
26
|
+
active_lockers = list_lockers.map { |item| JSON.parse(item)}
|
27
|
+
active_lockers
|
28
|
+
end
|
29
|
+
|
30
|
+
def switchboard
|
31
|
+
@switchboard ||= @con.smembers @name
|
32
|
+
end
|
33
|
+
|
34
|
+
def register_locker(uid, name)
|
35
|
+
@con.sadd @name, {uid: uid, name: name, created_at: redis_time}.to_json.to_s
|
36
|
+
list_lockers ## update lockers list
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def list_lockers
|
41
|
+
list_lockers ||= (@con.smembers @name).map { |item| JSON.parse(item)}
|
42
|
+
end
|
43
|
+
|
44
|
+
def locker(uid)
|
45
|
+
(list_lockers.select {|locker| locker["uid"] == uid}).first
|
46
|
+
end
|
47
|
+
|
48
|
+
#Locking mechanisem is based on sorted set, sorted set is used to allow a simulation
|
49
|
+
# of expiration time on the keys in the map
|
50
|
+
def lock_id(locker_uid, id_to_lock, expire_in_sec = 5)
|
51
|
+
now = redis_time
|
52
|
+
@con.multi do
|
53
|
+
@con.zadd("#{LOCK_MAP_KEY}_z", (now + expire_in_sec), id_to_lock)
|
54
|
+
@con.hset("#{LOCK_MAP_KEY}_h", id_to_lock, locker_uid)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
#Check if key exists to see if it is locked and it has not expired
|
59
|
+
#before getting keys, remove expired keys
|
60
|
+
def id_locked?(id_to_check)
|
61
|
+
@con.hexists("#{LOCK_MAP_KEY}_h", id_to_check)
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def unlock_id(locker_uid, id_to_unlock)
|
66
|
+
@con.hset("#{LOCK_MAP_KEY}_h", id_to_lock, locker_uid)
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_all_locked_ids
|
70
|
+
clean_old_keys
|
71
|
+
@con.hgetall "#{LOCK_MAP_KEY}_h"
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_all_their_locked_ids(uid)
|
75
|
+
res = get_all_locked_ids
|
76
|
+
res.reject {|key, key_uid| key_uid.to_s == uid.to_s }
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_all_my_locked_ids(uid)
|
80
|
+
res = get_all_locked_ids
|
81
|
+
res.select {|key, key_uid| key_uid.to_s == uid.to_s }
|
82
|
+
end
|
83
|
+
|
84
|
+
##################### Private Methods #################
|
85
|
+
private
|
86
|
+
|
87
|
+
def clean_old_keys
|
88
|
+
keys = @con.zrangebyscore("#{LOCK_MAP_KEY}_z", 0, redis_time)
|
89
|
+
if keys.size > 0
|
90
|
+
@con.zremrangebyscore("#{LOCK_MAP_KEY}_z", 0, redis_time)
|
91
|
+
keys.each {|key| @con.hdel("#{LOCK_MAP_KEY}_h", key)}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def redis_time
|
96
|
+
instant = @con.time
|
97
|
+
Time.at(instant[0], instant[1]).to_i
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
data/lib/switch_board.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'switch_board/version'
|
2
|
+
require 'switch_board/configuration'
|
3
|
+
|
4
|
+
module SwitchBoard
|
5
|
+
|
6
|
+
def self.logger
|
7
|
+
@logger ||= (rails_logger || default_logger)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.rails_logger
|
11
|
+
(defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
|
12
|
+
(defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:debug) && RAILS_DEFAULT_LOGGER)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.default_logger
|
16
|
+
require 'logger'
|
17
|
+
l = Logger.new(STDOUT)
|
18
|
+
l.level = Logger::INFO
|
19
|
+
l
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.logger=(logger)
|
23
|
+
@logger = logger
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/run_test
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rspec ./spec/**.*
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
describe SwitchBoard do
|
2
|
+
it "should have a VERSION constant" do
|
3
|
+
subject.const_get('VERSION').should_not be_empty
|
4
|
+
end
|
5
|
+
|
6
|
+
it "should have a logger defined" do
|
7
|
+
subject.logger.should_not be_nil
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should have good working logger" do
|
11
|
+
subject.logger.should respond_to(:info, :error, :debug)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe :Configuration do
|
16
|
+
it "should have a dataset" do
|
17
|
+
conf = SwitchBoard::Configuration.new(SwitchBoard::RedisDataset.new("127.0.0.1", 6379, "testing_playground"))
|
18
|
+
conf.should respond_to(:dataset)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe :ApplicationLifeCycle do
|
23
|
+
it "should not lose locks with multiple workers starting a new dataset" do
|
24
|
+
dataset1 = SwitchBoard::Configuration.new(SwitchBoard::RedisDataset.new("127.0.0.1", 6379, "testing_playground")).dataset
|
25
|
+
dataset1.cleanup
|
26
|
+
dataset1.register_locker(1, "Moshe")
|
27
|
+
dataset1.list_lockers.count.should eq 1
|
28
|
+
dataset2 = SwitchBoard::Configuration.new(SwitchBoard::RedisDataset.new("127.0.0.1", 6379, "testing_playground")).dataset
|
29
|
+
#dataset1 should still show single locker
|
30
|
+
dataset1.list_lockers.count.should eq 1
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should be possible to name switchboard dataset" do
|
34
|
+
dataset = SwitchBoard::RedisDataset.new("127.0.0.1", 6379, "testing_playground")
|
35
|
+
dataset.name.should eq "testing_playground"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe :RedisDataset do
|
40
|
+
let!(:dataset) {
|
41
|
+
dataset = SwitchBoard::Configuration.new(SwitchBoard::RedisDataset.new("127.0.0.1", 6379, "testing_playground")).dataset
|
42
|
+
dataset.cleanup
|
43
|
+
dataset
|
44
|
+
}
|
45
|
+
|
46
|
+
it "should implemenet get_locked" do
|
47
|
+
expect { dataset.get_locked }.not_to raise_error
|
48
|
+
end
|
49
|
+
|
50
|
+
describe :RedisDatasetSwitchBoard do
|
51
|
+
let!(:switchboard) {SwitchBoard::Configuration.new(SwitchBoard::RedisDataset.new("127.0.0.1", 6379, "testing_playground")).dataset.switchboard }
|
52
|
+
|
53
|
+
it "should be able to create a new locking set" do
|
54
|
+
switchboard.should match_array([])
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should have clean locker board on startup" do
|
58
|
+
lockers = dataset.list_lockers
|
59
|
+
lockers.count.should eq 0
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should allow registering lockers" do
|
63
|
+
dataset.register_locker(1, "Moshe")
|
64
|
+
dataset.register_locker(2, "Raz")
|
65
|
+
dataset.register_locker(3, "Pupik")
|
66
|
+
lockers = dataset.list_lockers
|
67
|
+
lockers.count.should eq 3
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should allow registering with strings" do
|
71
|
+
dataset.register_locker("muke", "Moshe")
|
72
|
+
dataset.register_locker("uke", "Raz")
|
73
|
+
lockers = dataset.list_lockers
|
74
|
+
lockers.count.should eq 2
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should allow getting a locker registerd with a non int UID" do
|
78
|
+
dataset.register_locker("muke", "Moshe")
|
79
|
+
dataset.locker("muke")["name"].should eq("Moshe")
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should allow getting name of a registered locker by uid" do
|
83
|
+
dataset.register_locker(1, "Pupik")
|
84
|
+
dataset.register_locker(2, "Raz")
|
85
|
+
dataset.register_locker(3, "Moshe")
|
86
|
+
dataset.locker(3)["name"].should eq "Moshe"
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should return nil for non existing uid" do
|
90
|
+
dataset.register_locker(1, "Pupik")
|
91
|
+
dataset.register_locker(2, "Raz")
|
92
|
+
dataset.register_locker(3, "Moshe")
|
93
|
+
dataset.locker(4).should be_nil
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should allow locking object id for specific locker" do
|
97
|
+
dataset.register_locker(1, "Pupik")
|
98
|
+
dataset.register_locker(2, "Raz")
|
99
|
+
expect { dataset.lock_id(1, "SOME_ID") }.not_to raise_error
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should allow checking lock state for a given id " do
|
103
|
+
dataset.register_locker(1, "Pupik")
|
104
|
+
dataset.register_locker(2, "Raz")
|
105
|
+
expect { dataset.lock_id(1, "SOME_ID_E") }.not_to raise_error
|
106
|
+
is_locked = dataset.id_locked?("SOME_ID_E")
|
107
|
+
is_locked.should eq true
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
it "should allow should lock id only for as per expiration time" do
|
112
|
+
dataset.register_locker(1, "Pupik")
|
113
|
+
dataset.register_locker(2, "Raz")
|
114
|
+
expect { dataset.lock_id(1, "SOME_ID_2", 1) }.not_to raise_error
|
115
|
+
dataset.id_locked?("SOME_ID_2").should be_true
|
116
|
+
sleep(2)
|
117
|
+
dataset.id_locked?("SOME_ID_3").should be_false
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should return unlocked of unlocked key" do
|
121
|
+
dataset.register_locker(1, "Pupik")
|
122
|
+
dataset.register_locker(2, "Raz")
|
123
|
+
dataset.id_locked?("SOME_ID_4").should be_false
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should allow getting all the locked IDs" do
|
127
|
+
dataset.register_locker(1, "Pupik")
|
128
|
+
dataset.register_locker(2, "Raz")
|
129
|
+
expect { dataset.lock_id(1, "SOME_ID_5") }.not_to raise_error
|
130
|
+
expect { dataset.lock_id(1, "SOME_OTHER_ID") }.not_to raise_error
|
131
|
+
expect { dataset.lock_id(2, "SOME_THIRD_ID") }.not_to raise_error
|
132
|
+
expect { dataset.lock_id(2, "SOME_FOURTH_ID") }.not_to raise_error
|
133
|
+
dataset.get_all_locked_ids.count.should eq 4
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should get clean results when no IDs are locked" do
|
137
|
+
dataset.register_locker(1, "Pupik")
|
138
|
+
dataset.register_locker(2, "Raz")
|
139
|
+
dataset.get_all_locked_ids.count.should eq 0
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should allow users to get all IDs not locked by itself" do
|
143
|
+
dataset.register_locker(1, "Pupik")
|
144
|
+
dataset.register_locker(2, "Raz")
|
145
|
+
dataset.lock_id(1, "SOME_ID_6")
|
146
|
+
dataset.lock_id(1, "SOME_ID_7")
|
147
|
+
dataset.lock_id(1, "SOME_ID_8")
|
148
|
+
dataset.lock_id(2, "SOME_ID_9")
|
149
|
+
dataset.get_all_their_locked_ids(2).count.should eq 3
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
it "should allow users to get all IDs locked by themselves" do
|
154
|
+
dataset.register_locker(1, "Pupik")
|
155
|
+
dataset.register_locker(2, "Raz")
|
156
|
+
dataset.lock_id(1, "SOME_ID_6")
|
157
|
+
dataset.lock_id(1, "SOME_ID_7")
|
158
|
+
dataset.lock_id(1, "SOME_ID_8")
|
159
|
+
dataset.lock_id(2, "SOME_ID_9")
|
160
|
+
dataset.get_all_my_locked_ids(2).count.should eq 1
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path('../lib/switch_board/version', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "switch_board"
|
7
|
+
gem.version = SwitchBoard::VERSION
|
8
|
+
gem.summary = %q{Coordinate a set of persisted data and distribute to an active set of registerd clients}
|
9
|
+
gem.description = %q{Coordinate a set of persisted data models and distribute to an active set of registerd clients. Allows locking and automatic expiration for locking on portions of the data}
|
10
|
+
gem.license = "MIT"
|
11
|
+
gem.authors = ["Avner Cohen"]
|
12
|
+
gem.email = "israbirding@gmail.com"
|
13
|
+
gem.homepage = "https://rubygems.org/gems/switch_board"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ['lib']
|
19
|
+
|
20
|
+
gem.add_development_dependency 'rdoc', '~> 3.0'
|
21
|
+
gem.add_development_dependency 'rspec', '~> 2.4'
|
22
|
+
gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: switch_board
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Avner Cohen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-09-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rdoc
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.4'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubygems-tasks
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.2'
|
55
|
+
description: Coordinate a set of persisted data models and distribute to an active
|
56
|
+
set of registerd clients. Allows locking and automatic expiration for locking on
|
57
|
+
portions of the data
|
58
|
+
email: israbirding@gmail.com
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- .document
|
64
|
+
- .gitignore
|
65
|
+
- .rspec
|
66
|
+
- .ruby-gemset
|
67
|
+
- .ruby-version
|
68
|
+
- ChangeLog.rdoc
|
69
|
+
- Gemfile
|
70
|
+
- Gemfile.lock
|
71
|
+
- LICENSE.txt
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- lib/switch_board.rb
|
75
|
+
- lib/switch_board/configuration.rb
|
76
|
+
- lib/switch_board/datasets/abstract_dataset.rb
|
77
|
+
- lib/switch_board/datasets/redis_dataset.rb
|
78
|
+
- lib/switch_board/version.rb
|
79
|
+
- run_test
|
80
|
+
- spec/spec_helper.rb
|
81
|
+
- spec/switch_board_spec.rb
|
82
|
+
- switch_board.gemspec
|
83
|
+
homepage: https://rubygems.org/gems/switch_board
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.0.3
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Coordinate a set of persisted data and distribute to an active set of registerd
|
107
|
+
clients
|
108
|
+
test_files:
|
109
|
+
- spec/spec_helper.rb
|
110
|
+
- spec/switch_board_spec.rb
|