afterparty 0.0.21 → 0.1.0

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.
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