base_rails_app 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -0
- data/README +1 -0
- data/lib/app/controllers/application_controller.rb +55 -0
- data/lib/app/controllers/password_resets_controller.rb +45 -0
- data/lib/app/controllers/user_sessions_controller.rb +25 -0
- data/lib/app/controllers/users_controller.rb +37 -0
- data/lib/app/helpers/application_helper.rb +4 -0
- data/lib/app/helpers/user_sessions_helper.rb +2 -0
- data/lib/app/helpers/users_helper.rb +2 -0
- data/lib/app/models/postman.rb +20 -0
- data/lib/app/models/user.rb +12 -0
- data/lib/app/models/user_session.rb +2 -0
- data/lib/app/views/application/404_not_found.html.erb +8 -0
- data/lib/app/views/layouts/application.html.erb +57 -0
- data/lib/app/views/password_resets/_sidebar.html.erb +0 -0
- data/lib/app/views/password_resets/edit.html.erb +33 -0
- data/lib/app/views/password_resets/new.html.erb +26 -0
- data/lib/app/views/postman/password_reset_instructions.html.erb +3 -0
- data/lib/app/views/postman/welcome_email.html.erb +3 -0
- data/lib/app/views/user_sessions/_form.html.erb +19 -0
- data/lib/app/views/user_sessions/_sidebar.html.erb +13 -0
- data/lib/app/views/user_sessions/new.html.erb +15 -0
- data/lib/app/views/users/_form.html.erb +28 -0
- data/lib/app/views/users/_sidebar.html.erb +6 -0
- data/lib/app/views/users/edit.html.erb +19 -0
- data/lib/app/views/users/new.html.erb +15 -0
- data/lib/app/views/users/show.html.erb +46 -0
- data/lib/base_rails_app.rb +5 -0
- data/lib/base_rails_app/boot.rb +2 -0
- data/lib/base_rails_app/tasks.rb +12 -0
- data/lib/config/configatron/cucumber.rb +0 -0
- data/lib/config/configatron/defaults.rb +4 -0
- data/lib/config/configatron/development.rb +1 -0
- data/lib/config/configatron/production.rb +0 -0
- data/lib/config/configatron/test.rb +0 -0
- data/lib/config/database.yml +25 -0
- data/lib/config/deploy.rb +15 -0
- data/lib/config/environments/cucumber.rb +22 -0
- data/lib/config/environments/development.rb +17 -0
- data/lib/config/environments/production.rb +28 -0
- data/lib/config/environments/test.rb +28 -0
- data/lib/config/gemtronics.rb +36 -0
- data/lib/config/initializers/backtrace_silencers.rb +7 -0
- data/lib/config/initializers/configatron.rb +1 -0
- data/lib/config/initializers/hoptoad.rb +3 -0
- data/lib/config/initializers/inflections.rb +10 -0
- data/lib/config/initializers/mailers.rb +1 -0
- data/lib/config/initializers/mime_types.rb +5 -0
- data/lib/config/initializers/new_rails_defaults.rb +21 -0
- data/lib/config/initializers/session_store.rb +17 -0
- data/lib/config/locales/en.yml +5 -0
- data/lib/config/routes.rb +51 -0
- data/lib/config/schedule.rb +20 -0
- data/lib/db/migrate/20090809175903_create_users.rb +30 -0
- data/lib/db/migrate/20090809204840_acts_as_taggable_on_migration.rb +29 -0
- data/lib/db/migrate/20090912171353_create_delayed_jobs.rb +20 -0
- data/lib/db/migrate/20090912171829_add_delayed_job_extras.rb +11 -0
- data/lib/db/migrate/20090929201455_add_more_time_columns_to_dj.rb +11 -0
- data/lib/db/schema.rb +72 -0
- data/lib/db/seeds.rb +7 -0
- data/lib/lib/tasks/cucumber.rake +36 -0
- data/lib/lib/tasks/rcov.rake +27 -0
- data/lib/lib/tasks/rspec.rake +183 -0
- data/lib/vendor/plugins/delayed_job/generators/delayed_job/delayed_job_generator.rb +22 -0
- data/lib/vendor/plugins/delayed_job/generators/delayed_job/templates/migration.rb +20 -0
- data/lib/vendor/plugins/delayed_job/init.rb +2 -0
- data/lib/vendor/plugins/delayed_job/lib/delayed/command.rb +65 -0
- data/lib/vendor/plugins/delayed_job/lib/delayed/job.rb +271 -0
- data/lib/vendor/plugins/delayed_job/lib/delayed/message_sending.rb +18 -0
- data/lib/vendor/plugins/delayed_job/lib/delayed/performable_method.rb +55 -0
- data/lib/vendor/plugins/delayed_job/lib/delayed/tasks.rb +15 -0
- data/lib/vendor/plugins/delayed_job/lib/delayed/worker.rb +54 -0
- data/lib/vendor/plugins/delayed_job/lib/delayed_job.rb +13 -0
- data/lib/vendor/plugins/delayed_job/recipes/delayed_job.rb +26 -0
- data/lib/vendor/plugins/delayed_job/tasks/jobs.rake +1 -0
- data/lib/vendor/plugins/hoptoad_notifier/install.rb +1 -0
- data/lib/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier.rb +418 -0
- data/lib/vendor/plugins/hoptoad_notifier/lib/hoptoad_tasks.rb +26 -0
- data/lib/vendor/plugins/hoptoad_notifier/recipes/hoptoad.rb +22 -0
- data/lib/vendor/plugins/hoptoad_notifier/tasks/hoptoad_notifier_tasks.rake +66 -0
- data/lib/vendor/plugins/inherited_resources/README.rdoc +524 -0
- data/lib/vendor/plugins/inherited_resources/inherited_resources.gemspec +71 -0
- data/lib/vendor/plugins/inherited_resources/init.rb +1 -0
- data/lib/vendor/plugins/inherited_resources/lib/inherited_resources.rb +23 -0
- data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/actions.rb +79 -0
- data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/base.rb +42 -0
- data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/base_helpers.rb +363 -0
- data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/belongs_to_helpers.rb +89 -0
- data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/class_methods.rb +338 -0
- data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/dsl.rb +26 -0
- data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/dumb_responder.rb +20 -0
- data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/has_scope_helpers.rb +83 -0
- data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/legacy/respond_to.rb +156 -0
- data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/legacy/responder.rb +200 -0
- data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/polymorphic_helpers.rb +155 -0
- data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/singleton_helpers.rb +95 -0
- data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/url_helpers.rb +179 -0
- data/lib/vendor/plugins/inherited_resources/test/aliases_test.rb +139 -0
- data/lib/vendor/plugins/inherited_resources/test/association_chain_test.rb +125 -0
- data/lib/vendor/plugins/inherited_resources/test/base_test.rb +225 -0
- data/lib/vendor/plugins/inherited_resources/test/belongs_to_test.rb +87 -0
- data/lib/vendor/plugins/inherited_resources/test/class_methods_test.rb +138 -0
- data/lib/vendor/plugins/inherited_resources/test/customized_base_test.rb +162 -0
- data/lib/vendor/plugins/inherited_resources/test/customized_belongs_to_test.rb +76 -0
- data/lib/vendor/plugins/inherited_resources/test/defaults_test.rb +70 -0
- data/lib/vendor/plugins/inherited_resources/test/flash_test.rb +88 -0
- data/lib/vendor/plugins/inherited_resources/test/has_scope_test.rb +139 -0
- data/lib/vendor/plugins/inherited_resources/test/locales/en.yml +17 -0
- data/lib/vendor/plugins/inherited_resources/test/nested_belongs_to_test.rb +108 -0
- data/lib/vendor/plugins/inherited_resources/test/optional_belongs_to_test.rb +164 -0
- data/lib/vendor/plugins/inherited_resources/test/polymorphic_test.rb +186 -0
- data/lib/vendor/plugins/inherited_resources/test/redirect_to_test.rb +51 -0
- data/lib/vendor/plugins/inherited_resources/test/respond_to_test.rb +155 -0
- data/lib/vendor/plugins/inherited_resources/test/singleton_test.rb +83 -0
- data/lib/vendor/plugins/inherited_resources/test/test_helper.rb +38 -0
- data/lib/vendor/plugins/inherited_resources/test/url_helpers_test.rb +537 -0
- data/lib/vendor/plugins/inherited_resources/test/views/cars/edit.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/cars/index.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/cars/new.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/cars/show.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/cities/edit.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/cities/index.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/cities/new.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/cities/show.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/comments/edit.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/comments/index.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/comments/new.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/comments/show.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/employees/edit.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/employees/index.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/employees/new.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/employees/show.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/managers/edit.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/managers/new.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/managers/show.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/painters/edit.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/painters/index.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/painters/new.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/painters/show.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/pets/edit.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/pets/index.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/pets/new.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/pets/show.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/products/edit.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/products/index.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/products/new.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/products/show.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/professors/edit.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/professors/index.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/professors/new.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/professors/show.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/projects/index.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/projects/index.json.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/projects/respond_to_skip_default_template.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/projects/respond_with_resource.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/students/edit.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/students/new.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/trees/edit.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/trees/index.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/trees/new.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/trees/show.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/users/edit.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/users/index.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/users/new.html.erb +1 -0
- data/lib/vendor/plugins/inherited_resources/test/views/users/show.html.erb +1 -0
- data/lib/vendor/plugins/web-app-theme/README.md +22 -0
- data/lib/vendor/plugins/web-app-theme/images/avatar.png +0 -0
- data/lib/vendor/plugins/web-app-theme/index.html +473 -0
- data/lib/vendor/plugins/web-app-theme/javascripts/jquery-1.3.min.js +19 -0
- data/lib/vendor/plugins/web-app-theme/javascripts/jquery.localscroll.js +104 -0
- data/lib/vendor/plugins/web-app-theme/javascripts/jquery.scrollTo.js +150 -0
- data/lib/vendor/plugins/web-app-theme/rails_generators/theme/templates/view_layout_administration.html.erb +48 -0
- data/lib/vendor/plugins/web-app-theme/rails_generators/theme/templates/view_layout_sign.html.erb +15 -0
- data/lib/vendor/plugins/web-app-theme/rails_generators/theme/theme_generator.rb +38 -0
- data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_edit.html.erb +20 -0
- data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_form.html.erb +10 -0
- data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_new.html.erb +19 -0
- data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_show.html.erb +23 -0
- data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_sidebar.html.erb +13 -0
- data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_signin.html.erb +39 -0
- data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_signup.html.erb +56 -0
- data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_tables.html.erb +50 -0
- data/lib/vendor/plugins/web-app-theme/rails_generators/themed/themed_generator.rb +89 -0
- data/lib/vendor/plugins/web-app-theme/stylesheets/base.css +336 -0
- data/lib/vendor/plugins/web-app-theme/stylesheets/themes/bec-green/style.css +290 -0
- data/lib/vendor/plugins/web-app-theme/stylesheets/themes/bec/style.css +301 -0
- data/lib/vendor/plugins/web-app-theme/stylesheets/themes/blue/style.css +280 -0
- data/lib/vendor/plugins/web-app-theme/stylesheets/themes/default/style.css +267 -0
- data/lib/vendor/plugins/web-app-theme/stylesheets/themes/djime-cerulean/style.css +298 -0
- data/lib/vendor/plugins/web-app-theme/stylesheets/themes/drastic-dark/style.css +373 -0
- data/lib/vendor/plugins/web-app-theme/stylesheets/themes/kathleene/style.css +272 -0
- data/lib/vendor/plugins/web-app-theme/stylesheets/themes/orange/style.css +263 -0
- data/lib/vendor/plugins/web-app-theme/stylesheets/themes/reidb-greenish/style.css +301 -0
- metadata +257 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
class DelayedJobGenerator < Rails::Generator::Base
|
2
|
+
default_options :skip_migration => false
|
3
|
+
|
4
|
+
def manifest
|
5
|
+
record do |m|
|
6
|
+
m.template 'script', 'script/delayed_job', :chmod => 0755
|
7
|
+
unless options[:skip_migration]
|
8
|
+
m.migration_template "migration.rb", 'db/migrate',
|
9
|
+
:migration_file_name => "create_delayed_jobs"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def add_options!(opt)
|
17
|
+
opt.separator ''
|
18
|
+
opt.separator 'Options:'
|
19
|
+
opt.on("--skip-migration", "Don't generate a migration") { |v| options[:skip_migration] = v }
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class CreateDelayedJobs < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :delayed_jobs, :force => true do |table|
|
4
|
+
table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
|
5
|
+
table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
|
6
|
+
table.text :handler # YAML-encoded string of the object that will do work
|
7
|
+
table.text :last_error # reason for last failure (See Note below)
|
8
|
+
table.datetime :run_at # When to run. Could be Time.now for immediately, or sometime in the future.
|
9
|
+
table.datetime :locked_at # Set when a client is working on this object
|
10
|
+
table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
|
11
|
+
table.string :locked_by # Who is working on this object (if locked)
|
12
|
+
table.timestamps
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.down
|
18
|
+
drop_table :delayed_jobs
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'daemons'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
module Delayed
|
6
|
+
class Command
|
7
|
+
attr_accessor :worker_count
|
8
|
+
|
9
|
+
def initialize(args)
|
10
|
+
@options = {:quiet => true}
|
11
|
+
@worker_count = 1
|
12
|
+
|
13
|
+
opts = OptionParser.new do |opts|
|
14
|
+
opts.banner = "Usage: #{File.basename($0)} [options] start|stop|restart|run"
|
15
|
+
|
16
|
+
opts.on('-h', '--help', 'Show this message') do
|
17
|
+
puts opts
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
opts.on('-e', '--environment=NAME', 'Specifies the environment to run this delayed jobs under (test/development/production).') do |e|
|
21
|
+
ENV['RAILS_ENV'] = e
|
22
|
+
end
|
23
|
+
opts.on('--min-priority N', 'Minimum priority of jobs to run.') do |n|
|
24
|
+
@options[:min_priority] = n
|
25
|
+
end
|
26
|
+
opts.on('--max-priority N', 'Maximum priority of jobs to run.') do |n|
|
27
|
+
@options[:max_priority] = n
|
28
|
+
end
|
29
|
+
opts.on('-n', '--number_of_workers=workers', "Number of unique workers to spawn") do |worker_count|
|
30
|
+
@worker_count = worker_count.to_i rescue 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
@args = opts.parse!(args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def daemonize
|
37
|
+
worker_count.times do |worker_index|
|
38
|
+
process_name = worker_count == 1 ? "delayed_job" : "delayed_job.#{worker_index}"
|
39
|
+
Daemons.run_proc(process_name, :dir => "#{RAILS_ROOT}/tmp/pids", :dir_mode => :normal, :ARGV => @args) do |*args|
|
40
|
+
run process_name
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def run(worker_name = nil)
|
46
|
+
Dir.chdir(RAILS_ROOT)
|
47
|
+
|
48
|
+
# Replace the default logger…too bad Rails doesn't make this easier
|
49
|
+
Rails.logger.instance_eval do
|
50
|
+
@log.reopen File.join(RAILS_ROOT, 'log', 'delayed_job.log')
|
51
|
+
end
|
52
|
+
Delayed::Worker.logger = Rails.logger
|
53
|
+
ActiveRecord::Base.connection.reconnect!
|
54
|
+
|
55
|
+
Delayed::Job.worker_name = "#{worker_name} #{Delayed::Job.worker_name}"
|
56
|
+
|
57
|
+
Delayed::Worker.new(@options).start
|
58
|
+
rescue => e
|
59
|
+
logger.fatal e
|
60
|
+
STDERR.puts e.message
|
61
|
+
exit 1
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Delayed
|
4
|
+
|
5
|
+
class DeserializationError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# A job object that is persisted to the database.
|
9
|
+
# Contains the work object as a YAML field.
|
10
|
+
class Job < ActiveRecord::Base
|
11
|
+
MAX_ATTEMPTS = 25
|
12
|
+
MAX_RUN_TIME = 4.hours
|
13
|
+
set_table_name :delayed_jobs
|
14
|
+
|
15
|
+
# By default failed jobs are destroyed after too many attempts.
|
16
|
+
# If you want to keep them around (perhaps to inspect the reason
|
17
|
+
# for the failure), set this to false.
|
18
|
+
cattr_accessor :destroy_failed_jobs
|
19
|
+
self.destroy_failed_jobs = true
|
20
|
+
|
21
|
+
# Every worker has a unique name which by default is the pid of the process.
|
22
|
+
# There are some advantages to overriding this with something which survives worker retarts:
|
23
|
+
# Workers can safely resume working on tasks which are locked by themselves. The worker will assume that it crashed before.
|
24
|
+
cattr_accessor :worker_name
|
25
|
+
self.worker_name = "host:#{Socket.gethostname} pid:#{Process.pid}" rescue "pid:#{Process.pid}"
|
26
|
+
|
27
|
+
NextTaskSQL = '(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR (locked_by = ?)) AND failed_at IS NULL'
|
28
|
+
NextTaskOrder = 'priority DESC, run_at ASC'
|
29
|
+
|
30
|
+
ParseObjectFromYaml = /\!ruby\/\w+\:([^\s]+)/
|
31
|
+
|
32
|
+
cattr_accessor :min_priority, :max_priority
|
33
|
+
self.min_priority = nil
|
34
|
+
self.max_priority = nil
|
35
|
+
|
36
|
+
# When a worker is exiting, make sure we don't have any locked jobs.
|
37
|
+
def self.clear_locks!
|
38
|
+
update_all("locked_by = null, locked_at = null", ["locked_by = ?", worker_name])
|
39
|
+
end
|
40
|
+
|
41
|
+
def failed?
|
42
|
+
failed_at
|
43
|
+
end
|
44
|
+
alias_method :failed, :failed?
|
45
|
+
|
46
|
+
def payload_object
|
47
|
+
@payload_object ||= deserialize(self['handler'])
|
48
|
+
end
|
49
|
+
|
50
|
+
def name
|
51
|
+
@name ||= begin
|
52
|
+
payload = payload_object
|
53
|
+
if payload.respond_to?(:display_name)
|
54
|
+
payload.display_name
|
55
|
+
else
|
56
|
+
payload.class.name
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def payload_object=(object)
|
62
|
+
self['handler'] = object.to_yaml
|
63
|
+
end
|
64
|
+
|
65
|
+
# Reschedule the job in the future (when a job fails).
|
66
|
+
# Uses an exponential scale depending on the number of failed attempts.
|
67
|
+
def reschedule(message, backtrace = [], time = nil)
|
68
|
+
if self.attempts < MAX_ATTEMPTS
|
69
|
+
time ||= Job.db_time_now + (attempts ** 4) + 5
|
70
|
+
|
71
|
+
self.attempts += 1
|
72
|
+
self.run_at = time
|
73
|
+
self.last_error = message + "\n" + backtrace.join("\n")
|
74
|
+
self.unlock
|
75
|
+
save!
|
76
|
+
else
|
77
|
+
logger.info "* [JOB] PERMANENTLY removing #{self.name} because of #{attempts} consequetive failures."
|
78
|
+
destroy_failed_jobs ? destroy : update_attribute(:failed_at, Time.now)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# Try to run one job. Returns true/false (work done/work failed) or nil if job can't be locked.
|
84
|
+
def run_with_lock(max_run_time, worker_name)
|
85
|
+
logger.info "* [JOB] acquiring lock on #{name}"
|
86
|
+
unless lock_exclusively!(max_run_time, worker_name)
|
87
|
+
# We did not get the lock, some other worker process must have
|
88
|
+
logger.warn "* [JOB] failed to acquire exclusive lock for #{name}"
|
89
|
+
return nil # no work done
|
90
|
+
end
|
91
|
+
|
92
|
+
begin
|
93
|
+
runtime = Benchmark.realtime do
|
94
|
+
Timeout.timeout(max_run_time.to_i) { invoke_job }
|
95
|
+
destroy
|
96
|
+
end
|
97
|
+
# TODO: warn if runtime > max_run_time ?
|
98
|
+
logger.info "* [JOB] #{name} completed after %.4f" % runtime
|
99
|
+
return true # did work
|
100
|
+
rescue Exception => e
|
101
|
+
reschedule e.message, e.backtrace
|
102
|
+
log_exception(e)
|
103
|
+
return false # work failed
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Add a job to the queue
|
108
|
+
def self.enqueue(*args, &block)
|
109
|
+
object = block_given? ? EvaledJob.new(&block) : args.shift
|
110
|
+
|
111
|
+
unless object.respond_to?(:perform) || block_given?
|
112
|
+
raise ArgumentError, 'Cannot enqueue items which do not respond to perform'
|
113
|
+
end
|
114
|
+
|
115
|
+
priority = args.first || 0
|
116
|
+
run_at = args[1]
|
117
|
+
|
118
|
+
Job.create(:payload_object => object, :priority => priority.to_i, :run_at => run_at)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Find a few candidate jobs to run (in case some immediately get locked by others).
|
122
|
+
def self.find_available(limit = 5, max_run_time = MAX_RUN_TIME)
|
123
|
+
|
124
|
+
time_now = db_time_now
|
125
|
+
|
126
|
+
sql = NextTaskSQL.dup
|
127
|
+
|
128
|
+
conditions = [time_now, time_now - max_run_time, worker_name]
|
129
|
+
|
130
|
+
if self.min_priority
|
131
|
+
sql << ' AND (priority >= ?)'
|
132
|
+
conditions << min_priority
|
133
|
+
end
|
134
|
+
|
135
|
+
if self.max_priority
|
136
|
+
sql << ' AND (priority <= ?)'
|
137
|
+
conditions << max_priority
|
138
|
+
end
|
139
|
+
|
140
|
+
conditions.unshift(sql)
|
141
|
+
|
142
|
+
ActiveRecord::Base.silence do
|
143
|
+
find(:all, :conditions => conditions, :order => NextTaskOrder, :limit => limit)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Run the next job we can get an exclusive lock on.
|
148
|
+
# If no jobs are left we return nil
|
149
|
+
def self.reserve_and_run_one_job(max_run_time = MAX_RUN_TIME)
|
150
|
+
|
151
|
+
# We get up to 5 jobs from the db. In case we cannot get exclusive access to a job we try the next.
|
152
|
+
# this leads to a more even distribution of jobs across the worker processes
|
153
|
+
find_available(5, max_run_time).each do |job|
|
154
|
+
t = job.run_with_lock(max_run_time, worker_name)
|
155
|
+
return t unless t == nil # return if we did work (good or bad)
|
156
|
+
end
|
157
|
+
|
158
|
+
nil # we didn't do any work, all 5 were not lockable
|
159
|
+
end
|
160
|
+
|
161
|
+
# Lock this job for this worker.
|
162
|
+
# Returns true if we have the lock, false otherwise.
|
163
|
+
def lock_exclusively!(max_run_time, worker = worker_name)
|
164
|
+
now = self.class.db_time_now
|
165
|
+
affected_rows = if locked_by != worker
|
166
|
+
# We don't own this job so we will update the locked_by name and the locked_at
|
167
|
+
self.class.update_all(["locked_at = ?, locked_by = ?", now, worker], ["id = ? and (locked_at is null or locked_at < ?) and (run_at <= ?)", id, (now - max_run_time.to_i), now])
|
168
|
+
else
|
169
|
+
# We already own this job, this may happen if the job queue crashes.
|
170
|
+
# Simply resume and update the locked_at
|
171
|
+
self.class.update_all(["locked_at = ?", now], ["id = ? and locked_by = ?", id, worker])
|
172
|
+
end
|
173
|
+
if affected_rows == 1
|
174
|
+
self.locked_at = now
|
175
|
+
self.locked_by = worker
|
176
|
+
return true
|
177
|
+
else
|
178
|
+
return false
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Unlock this job (note: not saved to DB)
|
183
|
+
def unlock
|
184
|
+
self.locked_at = nil
|
185
|
+
self.locked_by = nil
|
186
|
+
end
|
187
|
+
|
188
|
+
# This is a good hook if you need to report job processing errors in additional or different ways
|
189
|
+
def log_exception(error)
|
190
|
+
logger.error "* [JOB] #{name} failed with #{error.class.name}: #{error.message} - #{attempts} failed attempts"
|
191
|
+
logger.error(error)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Do num jobs and return stats on success/failure.
|
195
|
+
# Exit early if interrupted.
|
196
|
+
def self.work_off(num = 100)
|
197
|
+
success, failure = 0, 0
|
198
|
+
|
199
|
+
num.times do
|
200
|
+
case self.reserve_and_run_one_job
|
201
|
+
when true
|
202
|
+
success += 1
|
203
|
+
when false
|
204
|
+
failure += 1
|
205
|
+
else
|
206
|
+
break # leave if no work could be done
|
207
|
+
end
|
208
|
+
break if $exit # leave if we're exiting
|
209
|
+
end
|
210
|
+
|
211
|
+
return [success, failure]
|
212
|
+
end
|
213
|
+
|
214
|
+
# Moved into its own method so that new_relic can trace it.
|
215
|
+
def invoke_job
|
216
|
+
payload_object.perform
|
217
|
+
end
|
218
|
+
|
219
|
+
private
|
220
|
+
|
221
|
+
def deserialize(source)
|
222
|
+
handler = YAML.load(source) rescue nil
|
223
|
+
|
224
|
+
unless handler.respond_to?(:perform)
|
225
|
+
if handler.nil? && source =~ ParseObjectFromYaml
|
226
|
+
handler_class = $1
|
227
|
+
end
|
228
|
+
attempt_to_load(handler_class || handler.class)
|
229
|
+
handler = YAML.load(source)
|
230
|
+
end
|
231
|
+
|
232
|
+
return handler if handler.respond_to?(:perform)
|
233
|
+
|
234
|
+
raise DeserializationError,
|
235
|
+
'Job failed to load: Unknown handler. Try to manually require the appropriate file.'
|
236
|
+
rescue TypeError, LoadError, NameError => e
|
237
|
+
raise DeserializationError,
|
238
|
+
"Job failed to load: #{e.message}. Try to manually require the required file."
|
239
|
+
end
|
240
|
+
|
241
|
+
# Constantize the object so that ActiveSupport can attempt
|
242
|
+
# its auto loading magic. Will raise LoadError if not successful.
|
243
|
+
def attempt_to_load(klass)
|
244
|
+
klass.constantize
|
245
|
+
end
|
246
|
+
|
247
|
+
# Get the current time (GMT or local depending on DB)
|
248
|
+
# Note: This does not ping the DB to get the time, so all your clients
|
249
|
+
# must have syncronized clocks.
|
250
|
+
def self.db_time_now
|
251
|
+
(ActiveRecord::Base.default_timezone == :utc) ? Time.now.utc : Time.now
|
252
|
+
end
|
253
|
+
|
254
|
+
protected
|
255
|
+
|
256
|
+
def before_save
|
257
|
+
self.run_at ||= self.class.db_time_now
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
261
|
+
|
262
|
+
class EvaledJob
|
263
|
+
def initialize
|
264
|
+
@job = yield
|
265
|
+
end
|
266
|
+
|
267
|
+
def perform
|
268
|
+
eval(@job)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Delayed
|
2
|
+
module MessageSending
|
3
|
+
def send_later(method, *args)
|
4
|
+
Delayed::Job.enqueue Delayed::PerformableMethod.new(self, method.to_sym, args)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def handle_asynchronously(method)
|
9
|
+
aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1
|
10
|
+
with_method, without_method = "#{aliased_method}_with_send_later#{punctuation}", "#{aliased_method}_without_send_later#{punctuation}"
|
11
|
+
define_method(with_method) do |*args|
|
12
|
+
send_later(without_method, *args)
|
13
|
+
end
|
14
|
+
alias_method_chain method, :send_later
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Delayed
|
2
|
+
class PerformableMethod < Struct.new(:object, :method, :args)
|
3
|
+
CLASS_STRING_FORMAT = /^CLASS\:([A-Z][\w\:]+)$/
|
4
|
+
AR_STRING_FORMAT = /^AR\:([A-Z][\w\:]+)\:(\d+)$/
|
5
|
+
|
6
|
+
def initialize(object, method, args)
|
7
|
+
raise NoMethodError, "undefined method `#{method}' for #{self.inspect}" unless object.respond_to?(method)
|
8
|
+
|
9
|
+
self.object = dump(object)
|
10
|
+
self.args = args.map { |a| dump(a) }
|
11
|
+
self.method = method.to_sym
|
12
|
+
end
|
13
|
+
|
14
|
+
def display_name
|
15
|
+
case self.object
|
16
|
+
when CLASS_STRING_FORMAT then "#{$1}.#{method}"
|
17
|
+
when AR_STRING_FORMAT then "#{$1}##{method}"
|
18
|
+
else "Unknown##{method}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def perform
|
23
|
+
load(object).send(method, *args.map{|a| load(a)})
|
24
|
+
rescue ActiveRecord::RecordNotFound
|
25
|
+
# We cannot do anything about objects which were deleted in the meantime
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def load(arg)
|
32
|
+
case arg
|
33
|
+
when CLASS_STRING_FORMAT then $1.constantize
|
34
|
+
when AR_STRING_FORMAT then $1.constantize.find($2)
|
35
|
+
else arg
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def dump(arg)
|
40
|
+
case arg
|
41
|
+
when Class then class_to_string(arg)
|
42
|
+
when ActiveRecord::Base then ar_to_string(arg)
|
43
|
+
else arg
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def ar_to_string(obj)
|
48
|
+
"AR:#{obj.class}:#{obj.id}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def class_to_string(obj)
|
52
|
+
"CLASS:#{obj.name}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|