afterparty 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
- metadata.gz: d5a18d9460144527504869b72c7a377ce2c6eda0
4
- data.tar.gz: 4d9b52a88c5fea21d37da73b1a13f5f5bf180c9e
3
+ metadata.gz: b61c1a12ac1be3fb98d8615c4126689032529792
4
+ data.tar.gz: 9ee9c0fd733e0d96133040e956a021e0f3f5c356
5
5
  !binary "U0hBNTEy":
6
- metadata.gz: d6323eb6eb7a72b1090207d498ae02890dbfca347bdc37414453f67441725e966b4205651d53d4756e15ce7a786ff1a1058c98572439c67e931ab0f150ce70c7
7
- data.tar.gz: 1888cd7d556ee3f5164935ac9fb7a9b57dcf9fb1808ff8eae57315bd8175f3080e2a1dca599437bbf87770f11acc48b598fd62a1272a4c8077f0c7385304d9a1
6
+ metadata.gz: 6d4e7ebafeff397b8aa751c60e8e1198a435701984be74d7f5275be86b55ac5cc41512db4846a42a81abecf57a5a56f9770554b39ef5fc9380570fb053e2e3f1
7
+ data.tar.gz: c744c87dc122d7901d6ebcc9b758740c6c563aa01ff8a802e23a7f49aa68d1b198bafe2460da9ba826efd3640cefde72e8e5f047d7139ad22a16f670c919dffe
@@ -3,4 +3,6 @@ rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
5
  services:
6
- - redis-server
6
+ - redis-server
7
+ env:
8
+ - AFTERPARTY_JOB_TIME=15 AFTERPARTY_SLOW_TIME=50
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Afterparty
2
2
 
3
- A Rails 4 compatible queue with support for executing jobs in the future and serialization with Redis.
3
+ [![Build Status](https://travis-ci.org/hstove/afterparty.png?branch=master)](https://travis-ci.org/hstove/afterparty)
4
+
5
+ A Rails 4 compatible queue with support for executing jobs in the future and persistence with Redis.
4
6
 
5
7
  ## Installation
6
8
 
@@ -41,7 +43,7 @@ end
41
43
  Then add it to the queue at any time.
42
44
 
43
45
  ~~~Ruby
44
- Rails.queue << Job.new
46
+ Rails.configuration.queue << Job.new
45
47
  ~~~
46
48
 
47
49
  If your job responds to an `execute_at` method, the queue will wait to process that job until the specified time.
@@ -19,6 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency "redis"
22
+ spec.add_dependency "iconv"
22
23
 
23
24
  spec.add_development_dependency "bundler", "~> 1.3"
24
25
  spec.add_development_dependency "rake"
@@ -0,0 +1,14 @@
1
+ $(document).ready ->
2
+ # $('.debug').click (e) ->
3
+ # $el = $(e.target)
4
+ # $tr = $el.parents('tr')
5
+ # $tr.next().toggle()
6
+ # $tr.toggleClass('debugged')
7
+ # false
8
+ $('.job-row').click (e) ->
9
+ $el = $(e.target)
10
+ return true if $el.hasClass('job-action')
11
+ $tr = $el.parents('tr')
12
+ $tr.next().toggle()
13
+ $tr.toggleClass('debugged')
14
+ false
@@ -0,0 +1,85 @@
1
+ body
2
+ :font-family helvetica
3
+ :background-color whitesmoke
4
+ :color lighten(black,30%)
5
+ :margin 0 20px
6
+
7
+ a:visited, a
8
+ :color rgb(96, 89, 180)
9
+ :text-decoration none
10
+
11
+ span
12
+ :font-size 12px
13
+
14
+ .job-table
15
+ :border-spacing 0
16
+ :max-width 100%
17
+ :margin 0px auto
18
+ thead tr:first-child
19
+ th
20
+ :border-width 1px 1px 1px 0
21
+ &:first-child
22
+ :border-width 1px 1px 1px 1px
23
+ th:first-child
24
+ :border-top-left-radius 2px
25
+ th:last-child
26
+ :border-top-right-radius 2px
27
+ tbody tr:last-child
28
+ td:first-child
29
+ :border-bottom-left-radius 2px
30
+ td:last-child
31
+ :border-bottom-right-radius 2px
32
+ tr
33
+ td, th
34
+ :border 1px solid #ccc
35
+ :border-width 0 1px 1px 0
36
+ :background-color lighten(whitesmoke, 5%)
37
+ :padding 12px 5px
38
+ :text-align left
39
+ :vertical-align middle
40
+ :word-wrap break-word
41
+ &:nth-child(2)
42
+ :width 70%
43
+ &:first-child
44
+ :border-width 0 1px 1px 1px
45
+ &:first-child, &:last-child
46
+ :width 15% !important
47
+ &.debugged
48
+ td
49
+ :background-color rgba(255,255,0,0.25)
50
+ &.job-row
51
+ :cursor pointer
52
+ h3
53
+ :margin 0
54
+ &:hover
55
+ td
56
+ :background-color rgba(0,0,0,0.15)
57
+ .description
58
+ :color white
59
+ .debug-row
60
+ :display none
61
+ :max-width 100%
62
+ td pre
63
+ :word-wrap break-word
64
+ :white-space pre-wrap
65
+
66
+ .distance-past, .distance-future
67
+ :font-size 12px
68
+ .distance-future
69
+ :color darken(rgb(0,255,0), 20%)
70
+ .distance-past
71
+ :color rgba(255,0,0,0.6)
72
+
73
+ .description
74
+ :font-size 12px
75
+ :color #bbb
76
+ :margin-left 10px
77
+
78
+ .job-action
79
+ :text-decoration underline
80
+ :font-size 12px
81
+ :margin 0 2px
82
+
83
+ .job_id
84
+ :color #bbb
85
+ :font-weight 700
@@ -0,0 +1,20 @@
1
+ module Afterparty
2
+ class DashboardController < ApplicationController
3
+ layout false
4
+ def index
5
+ @queues = Afterparty.queues
6
+ if params[:completed]
7
+ @jobs = queue.completed_with_scores
8
+ else
9
+ @jobs = queue.jobs_with_scores
10
+ end
11
+ end
12
+
13
+ def queue
14
+ Rails.configuration.queue
15
+ end
16
+
17
+ def run
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,66 @@
1
+ !!!
2
+ %html
3
+
4
+ %head
5
+ %title== #{Rails.application.class.parent_name} Job Queue
6
+ %meta{"http-equiv"=>"Content-Type", :content=>"text/html; charset=utf-8"}
7
+ / %meta{name: "viewport", content: "width=device-width, initial-scale=1.0"}
8
+ = stylesheet_link_tag "afterparty"
9
+ = javascript_include_tag "jquery", "afterparty"
10
+ = csrf_meta_tag
11
+ = favicon_link_tag
12
+ = yield(:head)
13
+
14
+ %body
15
+ %h1
16
+ Viewing
17
+ - if params[:completed]
18
+ = pluralize @jobs.size, "completed job"
19
+ %h3= link_to "View Job Queue", afterparty_engine.dashboard_path
20
+ - else
21
+ = pluralize @jobs.size, "job"
22
+ %h3= link_to "View Completed Jobs", afterparty_engine.dashboard_path(completed: true)
23
+ = @queues
24
+ %table.job-table
25
+ %thead
26
+ %tr
27
+ %th Execute At
28
+ / %th Job
29
+ / %th Actions
30
+ %tbody
31
+ - if @jobs.empty?
32
+ %tr
33
+ %td{colspan: 3}
34
+ %em No jobs to show...
35
+ - else
36
+ - @jobs.each do |job_container|
37
+ %tr.job-row
38
+ %td
39
+ %h3
40
+ - if job_container.job
41
+ = link_to job_container.job_class, "#", class: 'debug'
42
+ - if job_container.job.respond_to? :description
43
+ %span.description= job_container.job.description
44
+ - else
45
+ %em Error marshaling job
46
+ =# job_container.execute_at.strftime("%B %d, %Y at %l:%M %P")
47
+ - distance = time_ago_in_words(job_container.execute_at)
48
+ - if job_container.execute_at > Time.now
49
+ %span.distance-future= "in #{distance}"
50
+ - else
51
+ %span.distance-past= "#{distance} ago"
52
+ - if (_id = job_container.job_id) && (_queue = job_container.queue_name)
53
+ %span.job_id== ##{_id} #{_queue}
54
+ - unless params[:completed]
55
+ = link_to "run", afterparty_engine.run_job_path(job_id: _id, queue: _queue), class: 'job-action'
56
+ = link_to "delete", afterparty_engine.delete_job_path(job_id: _id, queue: _queue), class: 'job-action'
57
+ %tr.debug-row
58
+ %td
59
+ - if job_container.job
60
+ = debug job_container.job
61
+ - else
62
+ = job_container.raw_string
63
+
64
+ %p
65
+ Current Time:
66
+ = Time.now.strftime("%B %d, %Y at %l:%M %P")
@@ -0,0 +1,9 @@
1
+ Afterparty::Engine.routes.draw do
2
+ get "/" => "afterparty/dashboard#index", as: :dashboard
3
+ get "/run" => "afterparty/dashboard#run", as: :run_job
4
+ get "/delete" => "afterparty/dashboard#run", as: :delete_job
5
+ end
6
+
7
+ Rails.application.routes.draw do
8
+ mount Afterparty::Engine, at: "afterparty", as: "afterparty_engine"
9
+ end
@@ -1,14 +1,63 @@
1
1
  require 'logger'
2
2
  require 'afterparty/queue_helpers'
3
3
  require 'afterparty/redis_queue'
4
+ require 'redis'
4
5
  Dir[File.expand_path('../afterparty/*', __FILE__)].each { |f| require f }
5
6
 
6
7
 
7
8
  module Afterparty
9
+ @@redis = Redis.new
10
+
8
11
  def self.redis
9
12
  @@redis
10
13
  end
11
14
  def self.redis=(redis)
12
15
  @@redis = redis
13
16
  end
17
+
18
+ def self.clear namespace=:default
19
+ redis_call namespace, :del
20
+ end
21
+
22
+ def self.redis_call namespace, command, *args
23
+ @@redis.send(command, redis_queue_name(namespace), *args)
24
+ end
25
+
26
+ def self.redis_queue_name namespace=:default
27
+ "afterparty_#{namespace}_queue"
28
+ end
29
+
30
+ def self.queues
31
+ @@redis.smembers "afterparty_queues"
32
+ end
33
+
34
+ def self.add_queue name
35
+ @@redis.sadd "afterparty_queues", name
36
+ end
37
+
38
+ def self.next_job_id namespace=:default
39
+ @@redis.incr "afterparty_#{namespace.to_s}_job_id"
40
+ end
41
+
42
+ def self.load(raw)
43
+ begin
44
+ begin
45
+ job = Marshal.load(raw)
46
+ job = Marshal.load(job) if String === job
47
+ return job
48
+ rescue NameError => e
49
+ # lots of marshal load errors are because something that hasn't been
50
+ # required. recursively require on these errors
51
+ name = e.message.gsub("uninitialized constant ","").downcase
52
+ begin
53
+ require "#{name}"
54
+ return load(raw)
55
+ rescue LoadError
56
+ end
57
+ end
58
+ rescue
59
+ return nil
60
+ end
61
+ end
62
+
14
63
  end
@@ -0,0 +1,7 @@
1
+ require 'rails'
2
+ module Afterparty
3
+ class Engine < Rails::Engine
4
+ engine_name :afterparty
5
+
6
+ end
7
+ end
@@ -0,0 +1,36 @@
1
+ require 'iconv'
2
+ require 'date'
3
+
4
+ module Afterparty
5
+ class JobContainer
6
+ attr_accessor :job, :raw, :execute_at, :job_id, :queue_name
7
+
8
+ #intialized from redis's WITHSCORES function
9
+ def initialize _raw, timestamp
10
+ @execute_at = Time.at(timestamp)
11
+ begin
12
+ @job = Afterparty.load(_raw)
13
+ @job_id = job.afterparty_job_id if @job.respond_to? :afterparty_job_id
14
+ @queue_name = job.afterparty_queue if @job.respond_to? :afterparty_queue
15
+ rescue Exception => e
16
+ ap "Error during load: #{e.message}"
17
+ @job = nil
18
+ end
19
+ @raw = _raw
20
+ self
21
+ end
22
+
23
+ def job_class
24
+ if @job
25
+ @job.class
26
+ else
27
+ nil
28
+ end
29
+ end
30
+
31
+ def raw_string
32
+ ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
33
+ ic.iconv(@raw.dup + ' ')[0..-2]
34
+ end
35
+ end
36
+ end
@@ -4,8 +4,9 @@ module Afterparty
4
4
  @temp_namespace = namespace
5
5
  end
6
6
 
7
- def redis_queue_name
8
- "afterparty_#{@temp_namespace || @options[:namespace]}_queue"
7
+ def redis_queue_name
8
+ puts (a = Afterparty.redis_queue_name(@temp_namespace || @options[:namespace]))
9
+ a
9
10
  end
10
11
 
11
12
  def clear
@@ -13,7 +14,7 @@ module Afterparty
13
14
  end
14
15
 
15
16
  def redis_call command, *args
16
- result = Afterparty.redis.send(command, redis_queue_name, *args)
17
+ result = Afterparty.redis_call (@temp_namespace || @options[:namespace]), command, *args
17
18
  @temp_namespace = nil
18
19
  result
19
20
  end
@@ -31,13 +32,17 @@ module Afterparty
31
32
  end
32
33
 
33
34
  def jobs_with_scores
34
- redis_call :zrange, 0, -1, {withscores: true}
35
+ hash_from_scores(redis_call(:zrange, 0, -1, {withscores: true}))
35
36
  end
36
37
 
37
38
  def valid_jobs
38
39
  redis_call :zrangebyscore, 0, Time.now.to_i
39
40
  end
40
41
 
42
+ def next_valid_job
43
+ valid_jobs.first
44
+ end
45
+
41
46
  def jobs_empty?
42
47
  count = total_jobs_count
43
48
  # ap count
@@ -52,8 +57,32 @@ module Afterparty
52
57
  @@redis
53
58
  end
54
59
 
60
+ def last_completed
61
+ @temp_namespace = "completed"
62
+ redis_call(:zrange, -1, -1).first
63
+ end
64
+
65
+ def completed
66
+ @temp_namespace = "completed"
67
+ redis_call(:zrange, -20, -1).reverse
68
+ end
69
+
70
+ def completed_with_scores
71
+ @temp_namespace = "completed"
72
+ hash_from_scores(redis_call(:zrange, -20, -1, withscores: true)).reverse
73
+ end
74
+
75
+
55
76
  private
56
77
 
78
+ def hash_from_scores raw
79
+ arr = []
80
+ raw.each do |group|
81
+ arr << Afterparty::JobContainer.new(group[0], group[1])
82
+ end
83
+ arr
84
+ end
85
+
57
86
  # returns true if job has an :execute_at value
58
87
  def job_valid? job
59
88
  job.respond_to?(:execute_at) && !job.execute_at.nil?
@@ -4,9 +4,10 @@ module Afterparty
4
4
  include Afterparty::QueueHelpers
5
5
 
6
6
  def initialize options={}, consumer_options={}
7
- @consumer = ThreadedQueueConsumer.new(self, consumer_options).start
7
+ # @consumer = ThreadedQueueConsumer.new(self, consumer_options).start
8
8
  @options = options
9
9
  @options[:namespace] ||= "default"
10
+ Afterparty.add_queue @options[:namespace]
10
11
  @options[:sleep] ||= 5
11
12
  @mutex = Mutex.new
12
13
  end
@@ -14,8 +15,13 @@ module Afterparty
14
15
  def push job
15
16
  @mutex.synchronize do
16
17
  return nil if job.nil?
18
+ job.class.module_eval do
19
+ attr_accessor :afterparty_job_id, :afterparty_queue
20
+ end
21
+ queue_name = @temp_namespace || @options[:namespace]
22
+ job.afterparty_queue = queue_name
23
+ job.afterparty_job_id = Afterparty.next_job_id queue_name
17
24
  async_redis_call{ redis_call :zadd, queue_time(job), Marshal.dump(job) }
18
- @consumer.start unless @consumer.thread.alive?
19
25
  @temp_namespace = nil
20
26
  end
21
27
  end
@@ -25,16 +31,20 @@ module Afterparty
25
31
  def pop
26
32
  @mutex.synchronize do
27
33
  while true do
28
- if jobs_empty?
29
- @consumer.shutdown
30
- elsif !(_jobs = valid_jobs).empty?
34
+ if !(_jobs = valid_jobs).empty?
31
35
  job_dump = _jobs[0]
32
36
  async_redis_call do
33
37
  redis_call :zrem, job_dump
34
38
  @temp_namespace = "completed"
35
39
  redis_call :zadd, Time.now.to_i, job_dump
36
40
  end
37
- return Marshal.load(job_dump)
41
+ begin
42
+ return Marshal.load(job_dump)
43
+ rescue ArgumentException => e
44
+ puts "You encountered an argument exception while deserializing a job."
45
+ puts "Message: #{e.message}"
46
+ raise e
47
+ end
38
48
  end
39
49
  sleep(@options[:sleep])
40
50
  end
@@ -1,3 +1,3 @@
1
1
  module Afterparty
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -0,0 +1,56 @@
1
+ module Afterparty
2
+ class Worker
3
+ include QueueHelpers
4
+
5
+ def initialize options = {}
6
+ @options = options
7
+ @options[:adapter] ||= :redis
8
+ @options[:namespace] ||= :default
9
+ @options[:sleep] ||= 10
10
+ @options[:logger] ||= Logger.new($stderr)
11
+ self
12
+ end
13
+
14
+ def consume
15
+ @stopped = false
16
+ # puts "starting worker with namespace [#{@options[:namespace]}]."
17
+ @thread = Thread.new {
18
+ consume_sync
19
+ }
20
+ @thread
21
+ end
22
+
23
+ def consume_sync
24
+ while !@stopped
25
+ job = next_valid_job
26
+ if job
27
+ async_redis_call do
28
+ @temp_namespace = "completed"
29
+ redis_call :zadd, Time.now.to_i, Marshal.dump(job)
30
+ redis_call :zrem, job
31
+ end
32
+ run job
33
+ else
34
+ sleep(@options[:sleep])
35
+ end
36
+ end
37
+ end
38
+
39
+ def stop
40
+ @stopped = true
41
+ @thread.join(0)
42
+ end
43
+
44
+ def run(job)
45
+ fork do
46
+ Marshal.load(job).run
47
+ end
48
+ rescue Exception => exception
49
+ handle_exception job, exception
50
+ end
51
+
52
+ def handle_exception(job, exception)
53
+ @options[:logger].error "Job Error: #{job.inspect}\n#{exception.message}\n#{exception.backtrace.join("\n")}"
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,25 @@
1
+ namespace :jobs do
2
+ require 'mail'
3
+
4
+ desc "Start a new worker"
5
+ task work: :environment do
6
+ worker = Afterparty::Worker.new
7
+ worker.consume_sync
8
+ end
9
+
10
+ desc "Clear all jobs"
11
+ task clear: :environment do
12
+ Rails.configuration.queue.clear
13
+ end
14
+
15
+ desc "List Jobs"
16
+ task list: :environment do
17
+ jobs = Rails.configuration.queue.jobs_with_scores
18
+ puts "#{jobs.values.size} total jobs."
19
+ jobs.each do |time, job|
20
+ puts time
21
+ puts job
22
+ end
23
+ end
24
+ end
25
+
@@ -8,10 +8,16 @@ describe Afterparty::RedisQueue do
8
8
  @q = Afterparty::TestRedisQueue.new({sleep: 0.5})
9
9
  end
10
10
 
11
+ after do
12
+ @worker.stop
13
+ end
14
+
11
15
  before :each do
12
- @q.completed_jobs.clear
16
+ @worker = Afterparty::Worker.new({sleep: 0.5})
17
+ @worker.consume
13
18
  @q.clear
14
- Afterparty.redis.quit
19
+ @job_time = (ENV['AFTERPARTY_JOB_TIME'] || 3).to_i
20
+ @slow_job_time = (ENV['AFTERPARTY_SLOW_TIME'] || 10).to_i
15
21
  end
16
22
 
17
23
  it "pushes nil without errors" do
@@ -27,34 +33,35 @@ describe Afterparty::RedisQueue do
27
33
  it "executes the job" do
28
34
  job = TestJob.new
29
35
  @q.push(job)
30
- complete.size.should eq(0)
31
- chill(3)
32
- complete.size.should eq(1)
36
+ @q.jobs.size.should eq(1)
37
+ chill(@job_time)
38
+ @q.jobs.size.should eq(0)
33
39
  end
34
40
 
35
41
  it "removes items from the queue after running them" do
36
42
  @q.push TestJob.new
37
- chill(1)
38
- @q.jobs.should_not include(@job)
43
+ chill(@job_time)
44
+ @q.jobs.size.should == 0
39
45
  end
40
46
 
41
47
  it "doesn't execute jobs that execute in a while" do
42
48
  job = TestJob.new
43
49
  job.execute_at = Time.now + 200
44
50
  @q.push job
45
- chill(3)
46
- complete.size.should eq(0)
51
+ chill(@job_time)
52
+ @q.jobs.size.should eq(1)
47
53
  end
48
54
 
49
55
  it "waits the correct amount of time to execute a job" do
50
56
  job = TestJob.new
51
- job.execute_at = Time.now + 2
57
+ job.execute_at = Time.now + 5
52
58
  @q.push(job)
53
- chill(8)
54
- complete.size.should eq(1)
59
+ @q.jobs.size.should eq(1)
60
+ chill(@slow_job_time)
61
+ @q.jobs.size.should eq(0)
55
62
  end
56
63
 
57
- it "doesn't execute the job synchronously when added" do
64
+ it "doesn't wait and execute the job synchronously when added" do
58
65
  job = test_job 100
59
66
  t = Time.now
60
67
  @q.push(job)
@@ -66,9 +73,9 @@ describe Afterparty::RedisQueue do
66
73
  early_job = test_job
67
74
  @q.push(late_job)
68
75
  @q.push(early_job)
69
- chill(3)
70
- complete.size.should eq(1)
71
- complete[0].execute_at.should be(nil)
76
+ chill(@job_time)
77
+ (jobs = @q.jobs).size.should eq(1)
78
+ jobs[0].execute_at.should_not be(nil)
72
79
  end
73
80
 
74
81
  class ErrorJob
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: afterparty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hank Stoever
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-05-14 00:00:00.000000000 Z
11
+ date: 2013-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ! '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: iconv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -68,12 +82,21 @@ files:
68
82
  - README.md
69
83
  - Rakefile
70
84
  - afterparty.gemspec
85
+ - app/assets/javascripts/afterparty.js.coffee
86
+ - app/assets/stylesheets/afterparty.css.sass
87
+ - app/controllers/afterparty/dashboard_controller.rb
88
+ - app/views/afterparty/dashboard/index.html.haml
89
+ - config/routes.rb
71
90
  - dump.rdb
72
91
  - lib/afterparty.rb
92
+ - lib/afterparty/engine.rb
93
+ - lib/afterparty/job_container.rb
73
94
  - lib/afterparty/queue_helpers.rb
74
95
  - lib/afterparty/redis_queue.rb
75
96
  - lib/afterparty/threaded_queue_consumer.rb
76
97
  - lib/afterparty/version.rb
98
+ - lib/afterparty/worker.rb
99
+ - lib/tasks/tasks.rake
77
100
  - spec/helpers.rb
78
101
  - spec/redis_queue_spec.rb
79
102
  - spec/spec_helper.rb