tickr_client 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.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: []