afterparty 0.0.21 → 0.1.0

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: 1746d9cd2e5dee4f459c02cf8a0bef335de1a05d
4
- data.tar.gz: 86b2eb38f0c9113b8515fbe18ad8d3635a9dc0e9
3
+ metadata.gz: 5d17a9d413c53f3fbac4701c2c6b3019141b94f3
4
+ data.tar.gz: de82e598bc016faa80d7fa4879ca0084f372588c
5
5
  !binary "U0hBNTEy":
6
- metadata.gz: 431465cc60c4fb3abbfc63f8f4294367baa94507c4ce04b9181d3afbd5ed4546d3b3d9a79a0178a37814fcbfe468def7fd8c36633baf47d7d55ce384c6929828
7
- data.tar.gz: a8bcde980355c3a56a668450e274452d0ef10c0274e15bcc609a1a8ae3eb9d596c854ff60616e8b9e0648388c7f19974fdcfc6bb64dd8d806f0e37424355f093
6
+ metadata.gz: abb7fba4469a92003e3a439ae9514088e5f9312547216a75386856af95cd3e6ecda7d7e0b4259556fb778bc67a4d3db0b10618a3c01a13dcd15545901ac49472
7
+ data.tar.gz: 7d475a34daa110341d5401cb08a6f1186add9a584aaffe4f0551e5337d8633e4431aca2c9b66d2c91d45e87827975d9e6f9712aa7c2ba64038925f2fbfd5b45a
data/.rspec CHANGED
@@ -1,2 +1 @@
1
1
  --color
2
- --format documentation
@@ -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/Gemfile CHANGED
@@ -7,4 +7,6 @@ gem 'rails', github: "rails/rails", branch: "jobs", tag: "v4.0.0.rc1"
7
7
  gem 'awesome_print'
8
8
  gem 'guard-rspec'
9
9
  gem 'ruby_gntp'
10
- gem 'redis'
10
+ gem 'redis'
11
+ gem 'sqlite3'
12
+ gem 'genspec'
data/README.md CHANGED
@@ -1,10 +1,10 @@
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
4
 
5
- ## Installation
5
+ A Rails 3 & 4 compatible queue with support for executing jobs in the future and persistence with ActiveRecord.
6
6
 
7
- Make sure you've installed [redis](http://redis.io) on your machine.
7
+ ## Installation
8
8
 
9
9
  Add this line to your application's Gemfile:
10
10
 
@@ -15,16 +15,11 @@ gem 'afterparty'
15
15
  And then execute:
16
16
 
17
17
  $ bundle
18
+ $ rails g afterparty
19
+ $ rake db:migrate
18
20
 
19
- Or install it yourself as:
20
-
21
- $ gem install afterparty
22
-
23
- In your desired application environment, like `application.rb`:
24
-
25
- ~~~Ruby
26
- config.queue = Afterparty::RedisQueue.new
27
- ~~~
21
+ This will create an initializer in `config/initializers/afterparty.rb`. It initializes a queue at
22
+ `Rails.configuration.queue` for you to pass jobs to.
28
23
 
29
24
  ## Usage
30
25
 
@@ -41,11 +36,60 @@ end
41
36
  Then add it to the queue at any time.
42
37
 
43
38
  ~~~Ruby
44
- Rails.queue << Job.new
39
+ Rails.configuration.queue << Job.new
45
40
  ~~~
46
41
 
47
42
  If your job responds to an `execute_at` method, the queue will wait to process that job until the specified time.
48
43
 
44
+ ### Running jobs
45
+
46
+ You can start a worker in a separate process for executing jobs by calling `rake jobs:work`.
47
+
48
+ ### Helper jobs
49
+
50
+ Afterparty provides helper job wrappers for executing arbitrary methods or mailers.
51
+
52
+ ~~~Ruby
53
+ # pass an object, method, and arguments
54
+
55
+ mailer_job = Afterparty::MailerJob.new UserMailer, :welcome, @user
56
+ mailer_job.execute_at = Time.now + 20.minutes
57
+ Rails.configuration.queue << mailer_job
58
+
59
+ job = Afterparty::BasicJob.new @user, :reset_password
60
+ Rails.configuration.queue << mailer_job
61
+ ~~~
62
+
63
+ ### Dashboard
64
+
65
+ This gem provides a handy dashboard for inspecting, debugging, and re-running jobs.
66
+
67
+ Visit [http://localhost:3000/afterparty/](http://localhost:3000/afterparty/) and login with
68
+ `admin` and `password`. You can change the authentication strategy in `config/initializers/afterparty.rb` to something like this:
69
+
70
+ ~~~Ruby
71
+ Rails.configuration.queue.config_login do |username, password|
72
+ user = User.authenticate(username, password)
73
+ !user.nil? && user.is_admin?
74
+ end
75
+ ~~~
76
+
77
+ ### Unicorn configuration
78
+
79
+ If you're using Unicorn as your application server, you can run a worker thread asynchronously by adding a few lines to your `unicorn.rb`:
80
+
81
+ ~~~Ruby
82
+
83
+ @jobs_pid = nil
84
+
85
+ before_fork do |server, worker|
86
+ @jobs_pid ||= spawn("bundle exec rake jobs:work")
87
+
88
+ # ... the rest of your configuration
89
+ ~~~
90
+
91
+ This has the advantage of, for example, staying within Heroku's free tier by not running a worker dyno.
92
+
49
93
  ## Contributing
50
94
 
51
95
  1. Fork it
@@ -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"
Binary file
@@ -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,91 @@
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
+ .error-description
79
+ :color lighten(red, 10%)
80
+
81
+ .job-action
82
+ :text-decoration underline
83
+ :font-size 12px
84
+ :margin 0 2px
85
+
86
+ .job_id
87
+ :color #bbb
88
+ :font-weight 700
89
+
90
+ .notice
91
+ :color lighten(green, 10%)
@@ -0,0 +1,44 @@
1
+ module Afterparty
2
+ class DashboardController < ApplicationController
3
+ before_filter :authenticate
4
+ layout false
5
+ before_filter :find_job, only: [:run, :destroy, :run_again]
6
+
7
+ def index
8
+ @queues = Afterparty.queues
9
+ if params[:completed]
10
+ @jobs = AfterpartyJob.completed.limit(20)
11
+ else
12
+ @jobs = queue.jobs
13
+ end
14
+ end
15
+
16
+ def run
17
+ queue.run @job
18
+ flash[:notice] = "You successfully completed job ##{@job.id}."
19
+ redirect_to afterparty_engine.dashboard_path(completed: true)
20
+ end
21
+
22
+ def destroy
23
+ @job.destroy
24
+ flash[:notice] = "You have successfully destroyed job ##{@job.id}."
25
+ redirect_to afterparty_engine.dashboard_path
26
+ end
27
+
28
+ private
29
+
30
+ def queue
31
+ Rails.configuration.queue
32
+ end
33
+
34
+ def find_job
35
+ @job = AfterpartyJob.find(params[:id])
36
+ end
37
+
38
+ def authenticate
39
+ authenticate_or_request_with_http_basic do |username, password|
40
+ queue.authenticate(username, password)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,85 @@
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
+ - if notice
24
+ %p.notice= notice
25
+ %table.job-table
26
+ %thead
27
+ %tr
28
+ %th Execute At
29
+ %tbody
30
+ - if @jobs.empty?
31
+ %tr
32
+ %td{colspan: 3}
33
+ %em No jobs to show...
34
+ - else
35
+ - @jobs.each do |job_container|
36
+ - job = job_container.reify
37
+ %tr.job-row
38
+ %td
39
+ %h3
40
+ - if job
41
+ = link_to job.class.to_s, "#", class: 'debug'
42
+ - if job.respond_to? :description
43
+ %span.description= job.description.html_safe
44
+ - elsif job_container.has_error
45
+ %sp.error-description= job_container.error_message
46
+ - else
47
+ %em Error marshaling job
48
+ - if job_container.completed_at
49
+ %span.distance-future= "Completed #{time_ago_in_words(job_container.completed_at)} ago"
50
+ - else
51
+ - distance = time_ago_in_words(job_container.execute_at)
52
+ - if job_container.execute_at > Time.now
53
+ %span.distance-future= "Scheduled to execute in #{distance}"
54
+ - else
55
+ %span.distance-past= "Scheduled to execute #{distance} ago"
56
+ %span.job_id== ##{job_container.id} #{job_container.queue}
57
+ - if params[:completed]
58
+ = link_to "run again", afterparty_engine.run_job_path(id: job_container.id), class: 'job-action'
59
+ - else
60
+ = link_to "run", afterparty_engine.run_job_path(id: job_container.id), class: 'job-action'
61
+ = link_to "delete", afterparty_engine.destroy_job_path(id: job_container.id), class: 'job-action'
62
+ %tr.debug-row
63
+ %td
64
+ - if job
65
+ = debug job
66
+ - else
67
+ %p
68
+ %strong YAML dump:
69
+ = job_container.job_dump
70
+ - if job_container.has_error
71
+ %p
72
+ %strong Error Message:
73
+ = job_container.error_message
74
+ - if job_container.error_backtrace
75
+ %p
76
+ %strong Error Backtrace:
77
+ = job_container.error_backtrace.gsub("\n","<br>")
78
+ - if job_container.completed_at
79
+ %p
80
+ %strong Completed At:
81
+ = job_container.completed_at.strftime("%B %d, %Y at %l:%I %P")
82
+
83
+ %p
84
+ Current Time:
85
+ = 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#destroy", as: :destroy_job
5
+ end
6
+
7
+ Rails.application.routes.draw do
8
+ mount Afterparty::Engine, at: "afterparty", as: "afterparty_engine"
9
+ end
@@ -1,17 +1,61 @@
1
1
  require 'logger'
2
2
  require 'afterparty/queue_helpers'
3
- require 'afterparty/redis_queue'
4
- require 'redis'
3
+ require 'yaml'
5
4
  Dir[File.expand_path('../afterparty/*', __FILE__)].each { |f| require f }
6
5
 
7
6
 
8
7
  module Afterparty
9
- @@redis = Redis.new
8
+ def self.clear namespace=:default
9
+ redis_call namespace, :del
10
+ end
11
+
12
+ def self.redis_call namespace, command, *args
13
+ @@redis.send(command, redis_queue_name(namespace), *args)
14
+ end
15
+
16
+ def self.redis_queue_name namespace=:default
17
+ "afterparty_#{namespace}_queue"
18
+ end
10
19
 
11
- def self.redis
12
- @@redis
20
+ def self.queues
21
+ # @@redis.smembers "afterparty_queues"
13
22
  end
14
- def self.redis=(redis)
15
- @@redis = redis
23
+
24
+ # return timestamp of :execute_at or current time
25
+ def self.queue_time job
26
+ time = job_valid?(job) ? job.execute_at : DateTime.now
27
+ end
28
+
29
+ # returns true if job has an :execute_at value
30
+ def self.job_valid? job
31
+ job.respond_to?(:execute_at) && !job.execute_at.nil?
16
32
  end
33
+
34
+ def self.load(raw)
35
+ begin
36
+ # postgres converts it to utf-8
37
+ # raw.encode!("ascii")
38
+ begin
39
+ # job = Marshal.load(raw)
40
+ # job = Marshal.load(job) if String === job
41
+ return YAML.load(raw)
42
+ rescue ArgumentError => e
43
+ # lots of yaml load errors are because something that hasn't been
44
+ # required. recursively require on these errors
45
+ # Invoke the autoloader and try again if object's class is undefined
46
+ if e.message =~ /undefined class\/module (.*)$/
47
+ # puts "autoloading #{$1}"
48
+ $1.constantize rescue return nil
49
+ end
50
+ return load(raw)
51
+ end
52
+ rescue Exception => e
53
+ puts e
54
+ puts "Exception while unmarshaling a job:"
55
+ puts e.message
56
+ puts e.backtrace
57
+ return nil
58
+ end
59
+ end
60
+
17
61
  end