userq 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.
Files changed (46) hide show
  1. checksums.yaml +15 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +102 -0
  4. data/Rakefile +32 -0
  5. data/lib/generators/userq/install_generator.rb +44 -0
  6. data/lib/generators/userq/templates/migration.rb +12 -0
  7. data/lib/userq.rb +201 -0
  8. data/lib/userq/version.rb +3 -0
  9. data/test/Gemfile.test +5 -0
  10. data/test/dummy/Rakefile +6 -0
  11. data/test/dummy/app/assets/javascripts/application.js +13 -0
  12. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  13. data/test/dummy/app/controllers/application_controller.rb +5 -0
  14. data/test/dummy/app/helpers/application_helper.rb +2 -0
  15. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  16. data/test/dummy/bin/bundle +3 -0
  17. data/test/dummy/bin/rails +4 -0
  18. data/test/dummy/bin/rake +4 -0
  19. data/test/dummy/config.ru +4 -0
  20. data/test/dummy/config/application.rb +23 -0
  21. data/test/dummy/config/boot.rb +5 -0
  22. data/test/dummy/config/database.yml +25 -0
  23. data/test/dummy/config/environment.rb +5 -0
  24. data/test/dummy/config/environments/development.rb +29 -0
  25. data/test/dummy/config/environments/production.rb +80 -0
  26. data/test/dummy/config/environments/test.rb +36 -0
  27. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  28. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  29. data/test/dummy/config/initializers/inflections.rb +16 -0
  30. data/test/dummy/config/initializers/mime_types.rb +5 -0
  31. data/test/dummy/config/initializers/secret_token.rb +12 -0
  32. data/test/dummy/config/initializers/session_store.rb +3 -0
  33. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  34. data/test/dummy/config/locales/en.yml +23 -0
  35. data/test/dummy/config/routes.rb +56 -0
  36. data/test/dummy/db/schema.rb +16 -0
  37. data/test/dummy/db/test.sqlite3 +0 -0
  38. data/test/dummy/log/development.log +414 -0
  39. data/test/dummy/log/test.log +1955 -0
  40. data/test/dummy/public/404.html +58 -0
  41. data/test/dummy/public/422.html +58 -0
  42. data/test/dummy/public/500.html +57 -0
  43. data/test/dummy/public/favicon.ico +0 -0
  44. data/test/test_helper.rb +15 -0
  45. data/test/userq_test.rb +102 -0
  46. metadata +181 -0
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MWVhMzUwNTA1MzU1NjIwOGE0NzA2MDFjYzY3ZGU0ZWU3YWVkODE0MA==
5
+ data.tar.gz: !binary |-
6
+ ZDkxNWIzZTdlOTVmNzlkZWYzYmY3OTYwZjNjNTFhYWEzZGUwMWNmMg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZTBkZjBmODIwMDBkNDQ0NmIwODNhNmI5YTI3NzNmZmQ4Zjc4MTUyYTAwMjAy
10
+ MWUwNDU3OTY4NTE3YjcyM2ExODIwNjdjNDU4YTEzNjhkYjk4YzBiMjlkMjgy
11
+ NTgxN2EyM2YyN2Q2MGU5NmMyMDI5OWY4YmJjMjIyZGRhMjIwMjI=
12
+ data.tar.gz: !binary |-
13
+ ZWIzZWNkNTA1MTY4OGM3ZWFhMzRlMTBkYTNjYzE1Y2NjMjdmZDQ3NDQ2ZTRh
14
+ YzQ1ZmM3NjdkMzFlYzBiMzAyZDExZTEwNzZhYzBjMmI3MzQ1NmNiMTU1Yzgw
15
+ YzIxN2YxOGMxNmY2YTFkNjdmMTNiY2MwZmUyZTU4ZDFjYzUxNjE=
@@ -0,0 +1,20 @@
1
+ Copyright 2013 StudentHack
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.
@@ -0,0 +1,102 @@
1
+ # UserQ: Simple User Queues for Rails
2
+
3
+ UserQ allows you to **very quickly** integrate awesome user-based queues into your Rails application. Many large ticketing websites such as [Ticketmaster](http://www.ticketmaster.com) use queues as a means of user experience.
4
+
5
+ [![Build Status](https://travis-ci.org/studenthack/userq.png?branch=master)](https://travis-ci.org/studenthack/userq)
6
+ [![Dependency Status](https://gemnasium.com/studenthack/userq.png)](https://gemnasium.com/studenthack/userq)
7
+ ![Gen Version](https://d25lcipzij17d.cloudfront.net/badge.png?title=gem&type=3c&v=0.0.1)
8
+
9
+ ![Screenshot](http://i.imgur.com/uqYjMyF.gif)
10
+
11
+ ### Example code
12
+ ```ruby
13
+ queue = UserQ::Queue.new(capacity: 50)
14
+
15
+ ## Can we enter the queue?
16
+ if queue.enter_into_queue?
17
+ entry = queue.enter
18
+ # They have a place in the queue. Sell them a ticket!
19
+ else
20
+ # No place in queue. Run this block again after n seconds.
21
+ end
22
+
23
+ # Default expiry: 180 seconds
24
+ puts entry.expires # => 180
25
+ sleep(5)
26
+ puts entry.expires # => 175
27
+ ```
28
+
29
+ ### How does it work?
30
+ Tell it how much you capacity you have (i.e. how many available tickets are left) and then UserQ does its beautiful magic. Some of the questions you can ask UserQ:
31
+
32
+ * How many "people" are in the queue?
33
+ * When does the current users spot in the queue expire?
34
+ * When do you think another spot in the queue will open? (i.e. average wait time)
35
+ * Can I extend/shorten the current users spot in the queue?
36
+
37
+ ### Install
38
+ ```
39
+ Install the gem:
40
+ $ gem install userq
41
+
42
+ Install the database model (automatically migrates):
43
+ $ rails generate userq:install
44
+
45
+ You're all setup!
46
+ ```
47
+
48
+
49
+ ### UserQ Documentation
50
+ See the [full documentation on the Wiki](/studenthack/userq/wiki/Documentation) to see what you can do with UserQ.
51
+
52
+
53
+ ### Uninstall UserQ
54
+ ```
55
+ Revoke the UserQ migration:
56
+ $ rake db:rollback
57
+
58
+ Remove everything UserQ:
59
+ $ rails destroy userq:install
60
+ ```
61
+
62
+
63
+ ### Development
64
+ Directly clone the repository (or fork it and clone your fork):
65
+ ```
66
+ $ git clone https://github.com/studenthack/userq.git
67
+ ```
68
+
69
+ Do some awesome stuff. To test simply run
70
+ ```
71
+ $ rake test
72
+ ```
73
+
74
+ We love pull requests! Make sure you write a test for your contribution.
75
+
76
+ ### Roadmap
77
+ - Lots of more awesome looking tests
78
+ - Develop the UserQ Wiki
79
+ - Assign queue places in a chronological order (first into queue = first entry) instead of randomly
80
+
81
+ ### Release History
82
+ - 23/12/13: The initial version
83
+
84
+ ### LICENCE
85
+ Permission is hereby granted, free of charge, to any person obtaining
86
+ a copy of this software and associated documentation files (the
87
+ "Software"), to deal in the Software without restriction, including
88
+ without limitation the rights to use, copy, modify, merge, publish,
89
+ distribute, sublicense, and/or sell copies of the Software, and to
90
+ permit persons to whom the Software is furnished to do so, subject to
91
+ the following conditions:
92
+
93
+ The above copyright notice and this permission notice shall be
94
+ included in all copies or substantial portions of the Software.
95
+
96
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
97
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
98
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
99
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
100
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
101
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
102
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'UserQ'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
22
+ require 'rake/testtask'
23
+
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << 'lib'
26
+ t.libs << 'test'
27
+ t.pattern = 'test/**/*_test.rb'
28
+ t.verbose = false
29
+ end
30
+
31
+
32
+ task default: :test
@@ -0,0 +1,44 @@
1
+ module Userq
2
+ class InstallGenerator < ::Rails::Generators::Base
3
+ source_root File.expand_path("../templates", __FILE__)
4
+
5
+ # Setup and create the migration
6
+ def do_migration
7
+ migration_exists = Dir["db/migrate/*_create_user_queues.rb"].count > 0
8
+
9
+ if migration_exists and installing?
10
+ puts "UserQ is already installed. Maybe a 'rake db:migrate' command might help?"
11
+ return
12
+ end
13
+
14
+ create_migration
15
+
16
+ puts "Success! UserQ is installed. You can now use it in your application." if installing?
17
+ puts "UserQ has already been uninstalled. Remember the 'user_queue' table needs to be manually destroyed." if destroying? and migration_exists == false
18
+ puts "Success! UserQ is uninstalled. The table 'userq_queue' needs to be destroyed manually to complete removal." if destroying? and migration_exists
19
+ end
20
+
21
+ private
22
+
23
+ def create_migration
24
+ migration_id = Time.now.to_i
25
+ migration_exists = Dir["db/migrate/*_create_user_queues.rb"].count > 0
26
+
27
+ copy_file "migration.rb", "db/migrate/#{migration_id}_create_user_queues.rb" if installing?
28
+ copy_file "migration.rb", Dir["db/migrate/*_create_user_queues.rb"][0].to_s if destroying? and migration_exists
29
+
30
+ run "rake db:migrate VERSION=#{migration_id}"
31
+ end
32
+
33
+ # Thanks to ynkr: http://stackoverflow.com/a/8829177/408177
34
+ protected
35
+
36
+ def installing?
37
+ :invoke == behavior
38
+ end
39
+
40
+ def destroying?
41
+ :revoke == behavior
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,12 @@
1
+ class CreateUserQueues < ActiveRecord::Migration
2
+ def change
3
+ create_table :user_queues do |t|
4
+ t.string :code
5
+ t.datetime :expires_at
6
+ t.text :data
7
+ t.string :context
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,201 @@
1
+ module UserQ
2
+
3
+ class Queue
4
+ attr_accessor :queue_constraints
5
+
6
+ # pre: constraints is a hash - capacity (int), taken (int), entry_time (int of seconds)
7
+ # post: instance of UserQ::Queue
8
+ def initialize constraints = {}
9
+ self.queue_constraints = { context: 'all', capacity: 2, taken: 0, entry_time: 180, auto_clean: true }.merge(constraints)
10
+ end
11
+
12
+ def constraints input # Update the constraints
13
+ queue_constraints.merge!(input)
14
+ end
15
+
16
+ def constraint input # Alias of self.constraints
17
+ constraints(input)
18
+ end
19
+
20
+
21
+ # Beautiful syntax.
22
+
23
+ def enter_into_queue? # Check if enough space in queue
24
+ current_limit = queue_constraints[:capacity].to_i
25
+ current_usage = queue_constraints[:taken].to_i + UserQ::UserQueue.count_unexpired(queue_constraints[:context])
26
+
27
+ # Assess whether enough space left into queue
28
+ current_usage < current_limit
29
+ end
30
+
31
+ def nearest_entry # In seconds how long the next entry in the queue will expire.
32
+ UserQ::UserQueue.next_in_seconds(queue_constraints[:context])
33
+ end
34
+
35
+ def avg_wait_time # nearest_time but more human-readable
36
+ case nearest_entry.floor
37
+ when 0..30
38
+ return "Less than 30 seconds"
39
+ when 31..60
40
+ return "Less than a minute"
41
+ else
42
+ return "approximately #{(nearest_entry.floor / 60).floor} minutes"
43
+ end
44
+ end
45
+
46
+ def empty?
47
+ UserQ::UserQueue.count_in_context(queue_constraints[:context]) == 0
48
+ end
49
+
50
+ def empty_queue # Clean entries that aren't ever going to be re-used.
51
+ UserQ::UserQueue.empty(queue_constraints[:context])
52
+ true
53
+ end
54
+
55
+ def reset
56
+ UserQ::UserQueue.reset(queue_constraints[:context])
57
+ true
58
+ end
59
+
60
+ def enter data = {}
61
+ return false unless enter_into_queue?
62
+
63
+ entry = UserQ::UserQueue.new
64
+ entry.code = UserQ::Internal.generate_code
65
+ entry.expires_at = UserQ::Internal.current_time + queue_constraints[:entry_time]
66
+ entry.data = data.to_json
67
+ entry.context = queue_constraints[:context]
68
+ entry.save
69
+
70
+ # Automatically clean expired tokens. Woohoo!
71
+ empty_queue if queue_constraints[:auto_clean]
72
+
73
+ Entry.new(entry.code)
74
+ end
75
+
76
+ end
77
+
78
+ class Entry
79
+ attr_accessor :code, :entry
80
+
81
+ def initialize code
82
+ self.entry = UserQ::UserQueue.find_by_code(self.code = code)
83
+ raise "Entry code #{code} not valid" if self.entry.count == 0
84
+
85
+ # Woohoo!
86
+ self.entry = self.entry.first
87
+ end
88
+
89
+ def valid_context? context
90
+ entry.context == context
91
+ end
92
+
93
+ def expired?
94
+ entry.expires_at < UserQ::Internal.current_time
95
+ end
96
+
97
+ def alive? # Opposite of expired?
98
+ entry.expires_at >= UserQ::Internal.current_time
99
+ end
100
+
101
+ def expires
102
+ expires = (entry.expires_at - UserQ::Internal.current_time).floor
103
+ expires > -1 ? expires : -1
104
+ end
105
+
106
+ def expire
107
+ entry.expires_at = UserQ::Internal.current_time - 1
108
+ entry.save
109
+ true
110
+ end
111
+
112
+ def remove
113
+ entry.destroy
114
+ entry = nil
115
+ code = nil
116
+ true
117
+ end
118
+
119
+ def removed?
120
+ true if entry.nil? or code.nil?
121
+ false
122
+ end
123
+
124
+ def shorten seconds
125
+
126
+ entry.expires_at -= seconds if entry.expires_at >= UserQ::Internal.current_time
127
+ entry.save
128
+
129
+ expires
130
+ end
131
+
132
+ def extend seconds
133
+
134
+ if entry.expires_at < UserQ::Internal.current_time
135
+ entry.expires_at = UserQ::Internal.current_time + seconds
136
+ else
137
+ entry.expires_at += seconds
138
+ end
139
+
140
+ entry.save
141
+ expires
142
+ end
143
+
144
+ def alive
145
+ entry.created_at - UserQ::Internal.current_time
146
+ end
147
+
148
+ def data
149
+ entry.data || Hash.new
150
+ end
151
+ end
152
+
153
+ # UserQ::Queue.constraint(capacity: 100)
154
+
155
+
156
+ class UserQueue < ActiveRecord::Base
157
+ def self.find_by_code code
158
+ where(code: code)
159
+ end
160
+
161
+ def self.count_unexpired context
162
+ where(context: context).where("expires_at > ?", UserQ::Internal.current_time).count
163
+ end
164
+
165
+ def self.count_in_context context
166
+ where(context: context).count
167
+ end
168
+
169
+ def self.reset context
170
+ where(context: context).destroy_all
171
+ end
172
+
173
+ def self.next_in_seconds context
174
+ seconds = (where(context: context).order(expires_at: :asc).first.expires_at - UserQ::Internal.current_time).to_i
175
+ seconds > 0 ? seconds : 0
176
+ end
177
+
178
+ def self.empty context
179
+ where(context: context).where("expires_at <= ?", UserQ::Internal.current_time).destroy_all
180
+ end
181
+ end
182
+
183
+
184
+ # Useful internal tools we use to make it beautiful.
185
+ class Internal
186
+ def self.symbolize array
187
+ result = {}
188
+ array.each { |key, value| result[key.to_sym] = value }
189
+ result
190
+ end
191
+
192
+ def self.current_time # Time all comes from here. Beautiful to update.
193
+ Time.now
194
+ end
195
+
196
+ def self.generate_code
197
+ rand(36**24).to_s(36)
198
+ end
199
+ end
200
+
201
+ end
@@ -0,0 +1,3 @@
1
+ module UserQ
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,5 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem "rails"
4
+ gem "sqlite3"
5
+ gem "json"
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Dummy::Application.load_tasks