base_rails_app 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (194) hide show
  1. data/LICENSE +1 -0
  2. data/README +1 -0
  3. data/lib/app/controllers/application_controller.rb +55 -0
  4. data/lib/app/controllers/password_resets_controller.rb +45 -0
  5. data/lib/app/controllers/user_sessions_controller.rb +25 -0
  6. data/lib/app/controllers/users_controller.rb +37 -0
  7. data/lib/app/helpers/application_helper.rb +4 -0
  8. data/lib/app/helpers/user_sessions_helper.rb +2 -0
  9. data/lib/app/helpers/users_helper.rb +2 -0
  10. data/lib/app/models/postman.rb +20 -0
  11. data/lib/app/models/user.rb +12 -0
  12. data/lib/app/models/user_session.rb +2 -0
  13. data/lib/app/views/application/404_not_found.html.erb +8 -0
  14. data/lib/app/views/layouts/application.html.erb +57 -0
  15. data/lib/app/views/password_resets/_sidebar.html.erb +0 -0
  16. data/lib/app/views/password_resets/edit.html.erb +33 -0
  17. data/lib/app/views/password_resets/new.html.erb +26 -0
  18. data/lib/app/views/postman/password_reset_instructions.html.erb +3 -0
  19. data/lib/app/views/postman/welcome_email.html.erb +3 -0
  20. data/lib/app/views/user_sessions/_form.html.erb +19 -0
  21. data/lib/app/views/user_sessions/_sidebar.html.erb +13 -0
  22. data/lib/app/views/user_sessions/new.html.erb +15 -0
  23. data/lib/app/views/users/_form.html.erb +28 -0
  24. data/lib/app/views/users/_sidebar.html.erb +6 -0
  25. data/lib/app/views/users/edit.html.erb +19 -0
  26. data/lib/app/views/users/new.html.erb +15 -0
  27. data/lib/app/views/users/show.html.erb +46 -0
  28. data/lib/base_rails_app.rb +5 -0
  29. data/lib/base_rails_app/boot.rb +2 -0
  30. data/lib/base_rails_app/tasks.rb +12 -0
  31. data/lib/config/configatron/cucumber.rb +0 -0
  32. data/lib/config/configatron/defaults.rb +4 -0
  33. data/lib/config/configatron/development.rb +1 -0
  34. data/lib/config/configatron/production.rb +0 -0
  35. data/lib/config/configatron/test.rb +0 -0
  36. data/lib/config/database.yml +25 -0
  37. data/lib/config/deploy.rb +15 -0
  38. data/lib/config/environments/cucumber.rb +22 -0
  39. data/lib/config/environments/development.rb +17 -0
  40. data/lib/config/environments/production.rb +28 -0
  41. data/lib/config/environments/test.rb +28 -0
  42. data/lib/config/gemtronics.rb +36 -0
  43. data/lib/config/initializers/backtrace_silencers.rb +7 -0
  44. data/lib/config/initializers/configatron.rb +1 -0
  45. data/lib/config/initializers/hoptoad.rb +3 -0
  46. data/lib/config/initializers/inflections.rb +10 -0
  47. data/lib/config/initializers/mailers.rb +1 -0
  48. data/lib/config/initializers/mime_types.rb +5 -0
  49. data/lib/config/initializers/new_rails_defaults.rb +21 -0
  50. data/lib/config/initializers/session_store.rb +17 -0
  51. data/lib/config/locales/en.yml +5 -0
  52. data/lib/config/routes.rb +51 -0
  53. data/lib/config/schedule.rb +20 -0
  54. data/lib/db/migrate/20090809175903_create_users.rb +30 -0
  55. data/lib/db/migrate/20090809204840_acts_as_taggable_on_migration.rb +29 -0
  56. data/lib/db/migrate/20090912171353_create_delayed_jobs.rb +20 -0
  57. data/lib/db/migrate/20090912171829_add_delayed_job_extras.rb +11 -0
  58. data/lib/db/migrate/20090929201455_add_more_time_columns_to_dj.rb +11 -0
  59. data/lib/db/schema.rb +72 -0
  60. data/lib/db/seeds.rb +7 -0
  61. data/lib/lib/tasks/cucumber.rake +36 -0
  62. data/lib/lib/tasks/rcov.rake +27 -0
  63. data/lib/lib/tasks/rspec.rake +183 -0
  64. data/lib/vendor/plugins/delayed_job/generators/delayed_job/delayed_job_generator.rb +22 -0
  65. data/lib/vendor/plugins/delayed_job/generators/delayed_job/templates/migration.rb +20 -0
  66. data/lib/vendor/plugins/delayed_job/init.rb +2 -0
  67. data/lib/vendor/plugins/delayed_job/lib/delayed/command.rb +65 -0
  68. data/lib/vendor/plugins/delayed_job/lib/delayed/job.rb +271 -0
  69. data/lib/vendor/plugins/delayed_job/lib/delayed/message_sending.rb +18 -0
  70. data/lib/vendor/plugins/delayed_job/lib/delayed/performable_method.rb +55 -0
  71. data/lib/vendor/plugins/delayed_job/lib/delayed/tasks.rb +15 -0
  72. data/lib/vendor/plugins/delayed_job/lib/delayed/worker.rb +54 -0
  73. data/lib/vendor/plugins/delayed_job/lib/delayed_job.rb +13 -0
  74. data/lib/vendor/plugins/delayed_job/recipes/delayed_job.rb +26 -0
  75. data/lib/vendor/plugins/delayed_job/tasks/jobs.rake +1 -0
  76. data/lib/vendor/plugins/hoptoad_notifier/install.rb +1 -0
  77. data/lib/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier.rb +418 -0
  78. data/lib/vendor/plugins/hoptoad_notifier/lib/hoptoad_tasks.rb +26 -0
  79. data/lib/vendor/plugins/hoptoad_notifier/recipes/hoptoad.rb +22 -0
  80. data/lib/vendor/plugins/hoptoad_notifier/tasks/hoptoad_notifier_tasks.rake +66 -0
  81. data/lib/vendor/plugins/inherited_resources/README.rdoc +524 -0
  82. data/lib/vendor/plugins/inherited_resources/inherited_resources.gemspec +71 -0
  83. data/lib/vendor/plugins/inherited_resources/init.rb +1 -0
  84. data/lib/vendor/plugins/inherited_resources/lib/inherited_resources.rb +23 -0
  85. data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/actions.rb +79 -0
  86. data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/base.rb +42 -0
  87. data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/base_helpers.rb +363 -0
  88. data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/belongs_to_helpers.rb +89 -0
  89. data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/class_methods.rb +338 -0
  90. data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/dsl.rb +26 -0
  91. data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/dumb_responder.rb +20 -0
  92. data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/has_scope_helpers.rb +83 -0
  93. data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/legacy/respond_to.rb +156 -0
  94. data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/legacy/responder.rb +200 -0
  95. data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/polymorphic_helpers.rb +155 -0
  96. data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/singleton_helpers.rb +95 -0
  97. data/lib/vendor/plugins/inherited_resources/lib/inherited_resources/url_helpers.rb +179 -0
  98. data/lib/vendor/plugins/inherited_resources/test/aliases_test.rb +139 -0
  99. data/lib/vendor/plugins/inherited_resources/test/association_chain_test.rb +125 -0
  100. data/lib/vendor/plugins/inherited_resources/test/base_test.rb +225 -0
  101. data/lib/vendor/plugins/inherited_resources/test/belongs_to_test.rb +87 -0
  102. data/lib/vendor/plugins/inherited_resources/test/class_methods_test.rb +138 -0
  103. data/lib/vendor/plugins/inherited_resources/test/customized_base_test.rb +162 -0
  104. data/lib/vendor/plugins/inherited_resources/test/customized_belongs_to_test.rb +76 -0
  105. data/lib/vendor/plugins/inherited_resources/test/defaults_test.rb +70 -0
  106. data/lib/vendor/plugins/inherited_resources/test/flash_test.rb +88 -0
  107. data/lib/vendor/plugins/inherited_resources/test/has_scope_test.rb +139 -0
  108. data/lib/vendor/plugins/inherited_resources/test/locales/en.yml +17 -0
  109. data/lib/vendor/plugins/inherited_resources/test/nested_belongs_to_test.rb +108 -0
  110. data/lib/vendor/plugins/inherited_resources/test/optional_belongs_to_test.rb +164 -0
  111. data/lib/vendor/plugins/inherited_resources/test/polymorphic_test.rb +186 -0
  112. data/lib/vendor/plugins/inherited_resources/test/redirect_to_test.rb +51 -0
  113. data/lib/vendor/plugins/inherited_resources/test/respond_to_test.rb +155 -0
  114. data/lib/vendor/plugins/inherited_resources/test/singleton_test.rb +83 -0
  115. data/lib/vendor/plugins/inherited_resources/test/test_helper.rb +38 -0
  116. data/lib/vendor/plugins/inherited_resources/test/url_helpers_test.rb +537 -0
  117. data/lib/vendor/plugins/inherited_resources/test/views/cars/edit.html.erb +1 -0
  118. data/lib/vendor/plugins/inherited_resources/test/views/cars/index.html.erb +1 -0
  119. data/lib/vendor/plugins/inherited_resources/test/views/cars/new.html.erb +1 -0
  120. data/lib/vendor/plugins/inherited_resources/test/views/cars/show.html.erb +1 -0
  121. data/lib/vendor/plugins/inherited_resources/test/views/cities/edit.html.erb +1 -0
  122. data/lib/vendor/plugins/inherited_resources/test/views/cities/index.html.erb +1 -0
  123. data/lib/vendor/plugins/inherited_resources/test/views/cities/new.html.erb +1 -0
  124. data/lib/vendor/plugins/inherited_resources/test/views/cities/show.html.erb +1 -0
  125. data/lib/vendor/plugins/inherited_resources/test/views/comments/edit.html.erb +1 -0
  126. data/lib/vendor/plugins/inherited_resources/test/views/comments/index.html.erb +1 -0
  127. data/lib/vendor/plugins/inherited_resources/test/views/comments/new.html.erb +1 -0
  128. data/lib/vendor/plugins/inherited_resources/test/views/comments/show.html.erb +1 -0
  129. data/lib/vendor/plugins/inherited_resources/test/views/employees/edit.html.erb +1 -0
  130. data/lib/vendor/plugins/inherited_resources/test/views/employees/index.html.erb +1 -0
  131. data/lib/vendor/plugins/inherited_resources/test/views/employees/new.html.erb +1 -0
  132. data/lib/vendor/plugins/inherited_resources/test/views/employees/show.html.erb +1 -0
  133. data/lib/vendor/plugins/inherited_resources/test/views/managers/edit.html.erb +1 -0
  134. data/lib/vendor/plugins/inherited_resources/test/views/managers/new.html.erb +1 -0
  135. data/lib/vendor/plugins/inherited_resources/test/views/managers/show.html.erb +1 -0
  136. data/lib/vendor/plugins/inherited_resources/test/views/painters/edit.html.erb +1 -0
  137. data/lib/vendor/plugins/inherited_resources/test/views/painters/index.html.erb +1 -0
  138. data/lib/vendor/plugins/inherited_resources/test/views/painters/new.html.erb +1 -0
  139. data/lib/vendor/plugins/inherited_resources/test/views/painters/show.html.erb +1 -0
  140. data/lib/vendor/plugins/inherited_resources/test/views/pets/edit.html.erb +1 -0
  141. data/lib/vendor/plugins/inherited_resources/test/views/pets/index.html.erb +1 -0
  142. data/lib/vendor/plugins/inherited_resources/test/views/pets/new.html.erb +1 -0
  143. data/lib/vendor/plugins/inherited_resources/test/views/pets/show.html.erb +1 -0
  144. data/lib/vendor/plugins/inherited_resources/test/views/products/edit.html.erb +1 -0
  145. data/lib/vendor/plugins/inherited_resources/test/views/products/index.html.erb +1 -0
  146. data/lib/vendor/plugins/inherited_resources/test/views/products/new.html.erb +1 -0
  147. data/lib/vendor/plugins/inherited_resources/test/views/products/show.html.erb +1 -0
  148. data/lib/vendor/plugins/inherited_resources/test/views/professors/edit.html.erb +1 -0
  149. data/lib/vendor/plugins/inherited_resources/test/views/professors/index.html.erb +1 -0
  150. data/lib/vendor/plugins/inherited_resources/test/views/professors/new.html.erb +1 -0
  151. data/lib/vendor/plugins/inherited_resources/test/views/professors/show.html.erb +1 -0
  152. data/lib/vendor/plugins/inherited_resources/test/views/projects/index.html.erb +1 -0
  153. data/lib/vendor/plugins/inherited_resources/test/views/projects/index.json.erb +1 -0
  154. data/lib/vendor/plugins/inherited_resources/test/views/projects/respond_to_skip_default_template.html.erb +1 -0
  155. data/lib/vendor/plugins/inherited_resources/test/views/projects/respond_with_resource.html.erb +1 -0
  156. data/lib/vendor/plugins/inherited_resources/test/views/students/edit.html.erb +1 -0
  157. data/lib/vendor/plugins/inherited_resources/test/views/students/new.html.erb +1 -0
  158. data/lib/vendor/plugins/inherited_resources/test/views/trees/edit.html.erb +1 -0
  159. data/lib/vendor/plugins/inherited_resources/test/views/trees/index.html.erb +1 -0
  160. data/lib/vendor/plugins/inherited_resources/test/views/trees/new.html.erb +1 -0
  161. data/lib/vendor/plugins/inherited_resources/test/views/trees/show.html.erb +1 -0
  162. data/lib/vendor/plugins/inherited_resources/test/views/users/edit.html.erb +1 -0
  163. data/lib/vendor/plugins/inherited_resources/test/views/users/index.html.erb +1 -0
  164. data/lib/vendor/plugins/inherited_resources/test/views/users/new.html.erb +1 -0
  165. data/lib/vendor/plugins/inherited_resources/test/views/users/show.html.erb +1 -0
  166. data/lib/vendor/plugins/web-app-theme/README.md +22 -0
  167. data/lib/vendor/plugins/web-app-theme/images/avatar.png +0 -0
  168. data/lib/vendor/plugins/web-app-theme/index.html +473 -0
  169. data/lib/vendor/plugins/web-app-theme/javascripts/jquery-1.3.min.js +19 -0
  170. data/lib/vendor/plugins/web-app-theme/javascripts/jquery.localscroll.js +104 -0
  171. data/lib/vendor/plugins/web-app-theme/javascripts/jquery.scrollTo.js +150 -0
  172. data/lib/vendor/plugins/web-app-theme/rails_generators/theme/templates/view_layout_administration.html.erb +48 -0
  173. data/lib/vendor/plugins/web-app-theme/rails_generators/theme/templates/view_layout_sign.html.erb +15 -0
  174. data/lib/vendor/plugins/web-app-theme/rails_generators/theme/theme_generator.rb +38 -0
  175. data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_edit.html.erb +20 -0
  176. data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_form.html.erb +10 -0
  177. data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_new.html.erb +19 -0
  178. data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_show.html.erb +23 -0
  179. data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_sidebar.html.erb +13 -0
  180. data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_signin.html.erb +39 -0
  181. data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_signup.html.erb +56 -0
  182. data/lib/vendor/plugins/web-app-theme/rails_generators/themed/templates/view_tables.html.erb +50 -0
  183. data/lib/vendor/plugins/web-app-theme/rails_generators/themed/themed_generator.rb +89 -0
  184. data/lib/vendor/plugins/web-app-theme/stylesheets/base.css +336 -0
  185. data/lib/vendor/plugins/web-app-theme/stylesheets/themes/bec-green/style.css +290 -0
  186. data/lib/vendor/plugins/web-app-theme/stylesheets/themes/bec/style.css +301 -0
  187. data/lib/vendor/plugins/web-app-theme/stylesheets/themes/blue/style.css +280 -0
  188. data/lib/vendor/plugins/web-app-theme/stylesheets/themes/default/style.css +267 -0
  189. data/lib/vendor/plugins/web-app-theme/stylesheets/themes/djime-cerulean/style.css +298 -0
  190. data/lib/vendor/plugins/web-app-theme/stylesheets/themes/drastic-dark/style.css +373 -0
  191. data/lib/vendor/plugins/web-app-theme/stylesheets/themes/kathleene/style.css +272 -0
  192. data/lib/vendor/plugins/web-app-theme/stylesheets/themes/orange/style.css +263 -0
  193. data/lib/vendor/plugins/web-app-theme/stylesheets/themes/reidb-greenish/style.css +301 -0
  194. 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,2 @@
1
+ require File.dirname(__FILE__) + '/lib/delayed_job'
2
+ Gemtronics.find_and_require_gem('delayed_job_extras')
@@ -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