tickr_client 0.0.1

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/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm use 1.9.3-p385@tickr-ruby-client --create
2
+
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'thread_safe', '~> 0.1'
4
+ gem 'json', '~> 1.8'
5
+
6
+ group :development do
7
+ gem 'bundler', '~> 1.2'
8
+ gem 'fakeweb', '~> 1.3'
9
+ gem 'jeweler', '~> 1.8'
10
+ gem 'rspec', '~> 2.13'
11
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,37 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ atomic (1.1.10)
5
+ diff-lcs (1.2.4)
6
+ fakeweb (1.3.0)
7
+ git (1.2.5)
8
+ jeweler (1.8.4)
9
+ bundler (~> 1.0)
10
+ git (>= 1.2.5)
11
+ rake
12
+ rdoc
13
+ json (1.8.0)
14
+ rake (10.1.0)
15
+ rdoc (4.0.1)
16
+ json (~> 1.4)
17
+ rspec (2.13.0)
18
+ rspec-core (~> 2.13.0)
19
+ rspec-expectations (~> 2.13.0)
20
+ rspec-mocks (~> 2.13.0)
21
+ rspec-core (2.13.1)
22
+ rspec-expectations (2.13.0)
23
+ diff-lcs (>= 1.1.3, < 2.0)
24
+ rspec-mocks (2.13.1)
25
+ thread_safe (0.1.0)
26
+ atomic
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ bundler (~> 1.2)
33
+ fakeweb (~> 1.3)
34
+ jeweler (~> 1.8)
35
+ json (~> 1.8)
36
+ rspec (~> 2.13)
37
+ thread_safe (~> 0.1)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Wistia
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,36 @@
1
+ # tickr_client
2
+
3
+ tickr_client is a Ruby library for talking to a [tickr ticketing server](http://github.com/wistia/tickr-server).
4
+
5
+ ## Getting Started
6
+
7
+ It is recommended that you create a global instance of tickr_client and call it directly. TickrClients are threadsafe and the `#get_ticket` method may be called safely on a single instance from all of your request handlers and workers.
8
+
9
+ Note that the threadsafe guarantee for a ticket is only its uniqueness; tickr makes no guarantee about sequentiality.
10
+
11
+ $my_tickr = TickrClient.new(
12
+ servers: [
13
+ {host: '192.168.1.1', port: 8080},
14
+ {host: '192.168.1.2', port: 8080},
15
+ {host: '192.168.1.3', port: 8080}
16
+ ],
17
+ timeout: 1000, # Try next host after 1 second.
18
+ cache_size: 100, # Keep up to 100 tickets (unique IDs) at the ready.
19
+ replenish_cache_at: 10, # Load 90 more tickets when we get down to 10.
20
+ )
21
+
22
+ new_id = $my_tickr.get_ticket
23
+
24
+
25
+
26
+ ## Contributing to tickr_client
27
+
28
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
29
+ * Fork the project.
30
+ * Start a feature/bugfix branch.
31
+ * Commit and push until you are happy with your contribution.
32
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
33
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise
34
+ necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
35
+
36
+
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 = 'tickr_client'
18
+ gem.homepage = 'http://github.com/wistia/tickr-ruby-client'
19
+ gem.license = 'MIT'
20
+ gem.summary = 'A Ruby Client for interacting with a Tickr server'
21
+ gem.description = 'A Ruby Client for interacting with a Tickr server'
22
+ gem.email = 'robby@freerobby.com'
23
+ gem.authors = ['Robby Grossman']
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 = "tickr_client #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,82 @@
1
+ require 'json'
2
+ require 'net/http'
3
+ require 'thread_safe'
4
+ require 'timeout'
5
+
6
+ class TickrClient
7
+ attr_accessor :servers, :timeout, :cache_size, :replenish_cache_at
8
+
9
+ def initialize(opts)
10
+ self.servers = opts[:servers]
11
+ self.timeout = opts[:timeout] || 1000
12
+ self.cache_size = opts[:cache_size] || 100
13
+ self.replenish_cache_at = opts[:replenish_cache_at] || 10
14
+
15
+ self.query_in_progress = false
16
+ self.next_server_index = Random.new.rand(servers.count)
17
+ self.tickets = ThreadSafe::Array.new
18
+
19
+ fetch_tickets
20
+ end
21
+
22
+ def get_ticket
23
+ fetch_tickets if tickets.count == 0
24
+ this_ticket = tickets.shift
25
+ fetch_tickets_async if tickets.count <= replenish_cache_at
26
+
27
+ this_ticket
28
+ end
29
+
30
+ protected
31
+ attr_accessor :next_server_index, :tickets, :query_in_progress
32
+
33
+ def fetch_tickets
34
+ # Try every server up to three times.
35
+ while !fetch_tickets_from_server(next_server_index) do
36
+ self.next_server_index = (self.next_server_index + 1) % servers.count
37
+ end
38
+ end
39
+
40
+ def fetch_tickets_from_server(index)
41
+ new_ticket_group = begin
42
+ uri = URI("http://#{servers[index][:host]}:#{servers[index][:port]}/tickets/create/#{replenish_capacity}")
43
+ Timeout::timeout(timeout / 1000.0) do
44
+ JSON.parse(Net::HTTP.get(uri))
45
+ end
46
+ rescue Timeout::Error
47
+ return false
48
+ end
49
+
50
+ new_tickets = create_tickets_from_ticket_group(new_ticket_group)
51
+ tickets.concat(new_tickets)
52
+ true
53
+ end
54
+
55
+ def fetch_tickets_async
56
+ return if self.query_in_progress
57
+ self.query_in_progress = true
58
+ Thread.new do
59
+ fetch_tickets
60
+ self.query_in_progress = false
61
+ end
62
+ end
63
+
64
+ def replenish_capacity
65
+ cache_size - tickets.count
66
+ end
67
+
68
+ # Create an array of tickets based on a 'ticket group' array of size 3:
69
+ # 1. First element of ticket group is the first ticket.
70
+ # 2. Second element of ticket group is the increment between consecutive tickets.
71
+ # 3. Third element of ticket group is the number of tickets to create.
72
+ def create_tickets_from_ticket_group(group)
73
+ initial_ticket, diff, num_of_tickets = group
74
+ new_tickets = [initial_ticket]
75
+
76
+ (num_of_tickets - 1).times do
77
+ new_tickets << new_tickets.last + diff
78
+ end
79
+
80
+ new_tickets
81
+ end
82
+ end
@@ -0,0 +1,20 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+ require 'fakeweb'
5
+ require 'rspec'
6
+ require 'tickr_client'
7
+
8
+ # Requires supporting files with custom matchers and macros, etc,
9
+ # in ./support/ and its subdirectories.
10
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
11
+
12
+ RSpec.configure do |config|
13
+ config.before(:all) do
14
+ FakeWeb.allow_net_connect = false
15
+ end
16
+
17
+ config.before(:each) do
18
+ FakeWeb.clean_registry
19
+ end
20
+ end
@@ -0,0 +1,160 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'tickr_client')
4
+
5
+ require 'json'
6
+
7
+ describe TickrClient do
8
+ after do
9
+ # Make sure threads finish before we fail based on expectations
10
+ Thread.list.each {|t| t.join unless t == Thread.current}
11
+ end
12
+ def get_client(opts = {})
13
+ TickrClient.new(
14
+ {servers: [{host: '127.0.0.1', port: 8080}, {host: '127.0.0.1', port: 8081}]}.merge(opts)
15
+ )
16
+ end
17
+ describe '#initialize' do
18
+ it 'sets configurable instance variables' do
19
+ TickrClient.any_instance.stub :fetch_tickets
20
+ client = TickrClient.new(
21
+ servers: [{host: '127.0.0.1', port: 8080}, {host: '127.0.0.1', port: 8081}],
22
+ timeout: 150,
23
+ cache_size: 500,
24
+ replenish_cache_at: 100
25
+ )
26
+
27
+ client.servers.count.should == 2
28
+ client.timeout.should == 150
29
+ client.cache_size.should == 500
30
+ client.replenish_cache_at.should == 100
31
+ end
32
+ it 'applies sensible defaults' do
33
+ TickrClient.any_instance.stub :fetch_tickets
34
+ client = TickrClient.new(
35
+ servers: [{host: '127.0.0.1', port: 8080}, {host: '127.0.0.1', port: 8081}]
36
+ )
37
+ client.timeout.should_not be_nil
38
+ client.cache_size.should_not be_nil
39
+ client.replenish_cache_at.should_not be_nil
40
+ end
41
+ it 'fetches tickets synchronously' do
42
+ TickrClient.any_instance.should_receive(:fetch_tickets).at_least(1)
43
+ TickrClient.new(
44
+ servers: [{host: '127.0.0.1', port: 8080}, {host: '127.0.0.1', port: 8081}]
45
+ )
46
+ end
47
+ end
48
+
49
+ describe '#get_ticket' do
50
+ it 'loads ticket from cache and removes it from the cache' do
51
+ TickrClient.any_instance.stub :fetch_tickets
52
+ client = get_client
53
+
54
+ client.send(:tickets=, [1, 2, 3, 4])
55
+ client.get_ticket.should == 1
56
+ client.send(:tickets).should == [2, 3, 4]
57
+ end
58
+
59
+ it 'asynchronously fetches more tickets after falling below the replenesh_cache_at threshold' do
60
+ TickrClient.any_instance.stub :fetch_tickets
61
+
62
+ client = get_client(cache_size: 10, replenesh_cache_at: 2)
63
+ client.send(:tickets=, [5, 6, 7])
64
+ client.should_receive :fetch_tickets_async
65
+ client.get_ticket
66
+ end
67
+ end
68
+
69
+ describe 'private instance methods' do
70
+ describe '#fetch_tickets' do
71
+ it 'fetches tickets from servers one at a time' do
72
+ Net::HTTP.should_receive(:get).and_return([1, 2, 3, 4, 5].to_json)
73
+ client = TickrClient.new(
74
+ servers: [
75
+ {host: '127.0.0.1', port: 8080},
76
+ {host: '127.0.0.1', port: 8081},
77
+ {host: '127.0.0.1', port: 8082},
78
+ {host: '127.0.0.1', port: 8083}
79
+ ]
80
+ )
81
+ Thread.list.each {|t| t.join unless t == Thread.current}
82
+
83
+ client.send(:next_server_index=, 0)
84
+ client.should_receive(:fetch_tickets_from_server).with(0).and_return(false)
85
+ client.should_receive(:fetch_tickets_from_server).with(1).and_return(false)
86
+ client.should_receive(:fetch_tickets_from_server).with(2).and_return(false)
87
+ client.should_receive(:fetch_tickets_from_server).with(3).and_return(true)
88
+ client.send(:fetch_tickets)
89
+ end
90
+ end
91
+
92
+ describe '#fetch_tickets_async' do
93
+ it 'fetches tickets in a separate thread' do
94
+ FakeWeb.register_uri :get, 'http://127.0.0.1:8080/tickets/create/10', body: [1, 1, 2].to_json
95
+ client = TickrClient.new(
96
+ servers: [
97
+ {host: '127.0.0.1', port: 8080}
98
+ ],
99
+ cache_size: 10
100
+ )
101
+ client.send(:tickets=, [1, 2])
102
+ FakeWeb.register_uri :get, 'http://127.0.0.1:8080/tickets/create/8', body: [5, 1, 8].to_json
103
+
104
+ client.send(:fetch_tickets_async)
105
+ client.send(:tickets).should == [1, 2] # Thread will not have finished yet
106
+ Thread.list.each {|t| t.join unless t == Thread.current}
107
+ client.send(:tickets).should == [1, 2, 5, 6, 7, 8, 9, 10, 11, 12]
108
+ end
109
+ end
110
+
111
+ describe '#fetch_tickets_from_server' do
112
+ it 'returns false on timeout error' do
113
+ TickrClient.any_instance.stub :fetch_tickets
114
+ client = get_client
115
+
116
+ Net::HTTP.should_receive(:get).and_raise(Timeout::Error)
117
+ client.send(:fetch_tickets_from_server, 0).should be_false
118
+ end
119
+
120
+ it 'adds tickets to array and returns true' do
121
+ TickrClient.any_instance.stub :fetch_tickets
122
+ client = TickrClient.new(
123
+ servers: [
124
+ {host: '127.0.0.1', port: 8080},
125
+ {host: '127.0.0.1', port: 8081},
126
+ {host: '127.0.0.1', port: 8082}
127
+ ],
128
+ cache_size: 10
129
+ )
130
+ client.send(:tickets=, [1, 2])
131
+ client.send(:next_server_index=, 1)
132
+
133
+ FakeWeb.register_uri :get, 'http://127.0.0.1:8081/tickets/create/8', body: [5, 1, 8].to_json
134
+
135
+ client.send(:fetch_tickets_from_server, 1).should be_true
136
+ client.send(:tickets).should == [1, 2, 5, 6, 7, 8, 9, 10, 11, 12]
137
+ end
138
+ end
139
+
140
+ describe '#replenish_capacity' do
141
+ it 'is the difference between the cache size and the number of tickets' do
142
+ TickrClient.any_instance.stub :fetch_tickets
143
+
144
+ client = get_client(cache_size: 10)
145
+ client.send(:tickets=, [5, 6, 7])
146
+ client.send(:replenish_capacity).should == 7
147
+ end
148
+ end
149
+
150
+ describe '#create_tickets_from_ticket_group' do
151
+ it 'should create an array of tickets' do
152
+ TickrClient.any_instance.stub :fetch_tickets
153
+
154
+ client = get_client
155
+ tickets = client.send(:create_tickets_from_ticket_group, [100, 100, 5])
156
+ tickets.should == [100, 200, 300, 400, 500]
157
+ end
158
+ end
159
+ end
160
+ end
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tickr_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Robby Grossman
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-07-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thread_safe
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.1'
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.1'
30
+ - !ruby/object:Gem::Dependency
31
+ name: json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.8'
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: '1.8'
46
+ - !ruby/object:Gem::Dependency
47
+ name: bundler
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '1.2'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.2'
62
+ - !ruby/object:Gem::Dependency
63
+ name: fakeweb
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '1.3'
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.3'
78
+ - !ruby/object:Gem::Dependency
79
+ name: jeweler
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '1.8'
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.8'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rspec
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '2.13'
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: '2.13'
110
+ description: A Ruby Client for interacting with a Tickr server
111
+ email: robby@freerobby.com
112
+ executables: []
113
+ extensions: []
114
+ extra_rdoc_files:
115
+ - LICENSE.txt
116
+ - README.md
117
+ files:
118
+ - .document
119
+ - .rspec
120
+ - .rvmrc
121
+ - Gemfile
122
+ - Gemfile.lock
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - VERSION
127
+ - lib/tickr_client.rb
128
+ - spec/spec_helper.rb
129
+ - spec/tickr_client_spec.rb
130
+ homepage: http://github.com/wistia/tickr-ruby-client
131
+ licenses:
132
+ - MIT
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ segments:
144
+ - 0
145
+ hash: -4284658083442582303
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ none: false
148
+ requirements:
149
+ - - ! '>='
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ requirements: []
153
+ rubyforge_project:
154
+ rubygems_version: 1.8.25
155
+ signing_key:
156
+ specification_version: 3
157
+ summary: A Ruby Client for interacting with a Tickr server
158
+ test_files: []