diff --git a/.rubocop.yml b/.rubocop.yml index d7063c9f..197797bc 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -14,5 +14,3 @@ Style/DoubleNegation: Enabled: false Metrics/PerceivedComplexity: Enabled: false -Metrics/ClassLength: - Max: 110 diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8b6db2da..3e87e70b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,37 +1,36 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2016-06-26 12:36:38 -0400 using RuboCop version 0.40.0. +# on 2021-07-10 20:06:41 +0000 using RuboCop version 0.40.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 -# Configuration parameters: AllowSafeAssignment. -Lint/AssignmentInCondition: - Exclude: - - 'lib/resque/scheduler/env.rb' - # Offense count: 2 Lint/UselessAccessModifier: Exclude: - 'lib/resque/scheduler.rb' -# Offense count: 17 +# Offense count: 18 Metrics/AbcSize: - Max: 36 + Max: 41 -# Offense count: 3 +# Offense count: 1 +# Configuration parameters: CountComments. +Metrics/ClassLength: + Max: 112 + +# Offense count: 5 Metrics/CyclomaticComplexity: Max: 12 -# Offense count: 6 +# Offense count: 11 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes. # URISchemes: http, https Metrics/LineLength: Max: 96 -# Offense count: 20 +# Offense count: 23 # Configuration parameters: CountComments. Metrics/MethodLength: Max: 34 @@ -39,7 +38,7 @@ Metrics/MethodLength: # Offense count: 2 # Configuration parameters: CountComments. Metrics/ModuleLength: - Max: 331 + Max: 350 # Offense count: 1 Style/CaseEquality: diff --git a/AUTHORS.md b/AUTHORS.md index d4aa3c35..413c516c 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -4,6 +4,7 @@ Resque Scheduler authors - Aaron Suggs - Alexander Simonov - Andrea Campolonghi +- Andrea Lorenzetti - Ben VandenBos - Bernerd Schaefer - Bogdan Gusiev diff --git a/README.md b/README.md index a92a8820..f3bbd7f2 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ Resque.schedule = YAML.load_file('your_resque_schedule.yml') If a static schedule is not set `resque-scheduler` will issue a "Schedule empty!" warning on startup, but despite that warning setting a static schedule is totally optional. It is possible -to use only dynamic schedules (see below). +to use only dynamic schedules or auto load (see below). The schedule file is a list of Resque job classes with arguments and a schedule frequency (in crontab syntax). The schedule is just a hash, but @@ -405,6 +405,50 @@ config[:every] = '1d' Resque.set_schedule(name, config) ``` +#### Auto load + +With auto load you specify a path from which jobs will be loaded and scheduled without the needs of static scheduling or dynamic scheduling. + +Auto load are not enabled by default. To be able to auto load set schedules, you must pass the following to resque-scheduler initialization (see Installation above for a more complete example): + +```ruby +Resque::Scheduler.auto_load = 'path/to/*_job.rb' +``` + +Auto load enables a job to declare it's scheduling. In order to do that file must follow `snake_case` convention for filename and `CamelCase` for class name. It also must include `Resque::Scheduler::Job` and declares it's schedule: + +```ruby + cron '*/2 * * * *' + queue 'default' +``` + +All options available: + +```ruby +cron '* */3 * * *' # use cron or every option, don't use both +every '3d' # use every or cron option, don't use both +queue 'default' +args 'custom arg' +description 'Nice description' +``` + +Job's example: + +```ruby +# my_great_job.rb +require 'resque/scheduler/job' + +class MyGreatJob + include Resque::Scheduler::Job + + cron '*/2 * * * *' + queue 'default' + args 'args' + description 'description' +end + +``` + #### Time zones Note that if you use the cron syntax, this will be interpreted as in the server time zone diff --git a/lib/resque/scheduler.rb b/lib/resque/scheduler.rb index 523b1840..c9b842d3 100644 --- a/lib/resque/scheduler.rb +++ b/lib/resque/scheduler.rb @@ -107,6 +107,26 @@ def load_schedule! Resque.schedule.each do |name, config| load_schedule_job(name, config) end + + Dir[auto_load.to_s].each do |file| + require File.absolute_path(file) + name = File.basename(file, '.rb') + begin + klass = Resque::Scheduler::Util.constantize(name) + rescue NameError + log! "Can't load file #{file}" + end + load_schedule_job( + name, + 'class' => klass.name, + 'cron' => klass.respond_to?(:cron) ? klass.cron : nil, + 'every' => klass.respond_to?(:every) ? klass.every : nil, + 'queue' => klass.respond_to?(:queue) ? klass.queue : nil, + 'args' => klass.respond_to?(:args) ? klass.args : nil, + 'description' => klass.respond_to?(:description) ? klass.description : nil + ) if klass + end + Resque.redis.del(:schedules_changed) if am_master && dynamic procline 'Schedules Loaded' end diff --git a/lib/resque/scheduler/cli.rb b/lib/resque/scheduler/cli.rb index c0d7fea9..ce82fc1c 100644 --- a/lib/resque/scheduler/cli.rb +++ b/lib/resque/scheduler/cli.rb @@ -8,6 +8,7 @@ module Scheduler app_name: 'APP_NAME', background: 'BACKGROUND', dynamic: 'DYNAMIC_SCHEDULE', + auto_load: 'AUTO_LOAD', env: 'RAILS_ENV', initializer_path: 'INITIALIZER_PATH', logfile: 'LOGFILE', @@ -31,6 +32,10 @@ class Cli 'Application name for procline'], callback: ->(options) { ->(n) { options[:app_name] = n } } }, + { + args: ['-A', '--auto-load [AUTO_LOAD]', 'Enable jobs auto load'], + callback: ->(options) { ->(a) { options[:auto_load] = a } } + }, { args: ['-B', '--background', 'Run in the background [BACKGROUND]'], callback: ->(options) { ->(b) { options[:background] = b } } diff --git a/lib/resque/scheduler/configuration.rb b/lib/resque/scheduler/configuration.rb index 9398ac3f..e10b52fa 100644 --- a/lib/resque/scheduler/configuration.rb +++ b/lib/resque/scheduler/configuration.rb @@ -53,6 +53,13 @@ def dynamic @dynamic ||= !!ENV['DYNAMIC_SCHEDULE'] end + # If set, will try to automatically load those jobs + attr_writer :auto_load + + def auto_load + @auto_load ||= !!ENV['AUTO_LOAD'] + end + # If set, will append the app name to procline attr_writer :app_name diff --git a/lib/resque/scheduler/env.rb b/lib/resque/scheduler/env.rb index e26d0aa4..558e882d 100644 --- a/lib/resque/scheduler/env.rb +++ b/lib/resque/scheduler/env.rb @@ -64,6 +64,8 @@ def setup_scheduler_configuration c.dynamic = !!options[:dynamic] if options.key?(:dynamic) + c.auto_load = options[:auto_load] if options.key?(:auto_load) + c.env = options[:env] if options.key?(:env) c.logfile = options[:logfile] if options.key?(:logfile) diff --git a/lib/resque/scheduler/job.rb b/lib/resque/scheduler/job.rb new file mode 100644 index 00000000..15f90c49 --- /dev/null +++ b/lib/resque/scheduler/job.rb @@ -0,0 +1,40 @@ +# vim:fileencoding=utf-8 + +module Resque + module Scheduler + module Job + class << self + def included(base) + base.extend ClassMethods + end + end + + module ClassMethods + def cron(value = nil) + return @cron ||= nil if value.nil? + @cron = value + end + + def every(value = nil) + return @every ||= nil if value.nil? + @every = value + end + + def queue(value = nil) + return @queue ||= nil if value.nil? + @queue = value + end + + def args(value = nil) + return @args ||= nil if value.nil? + @args = value + end + + def description(value = nil) + return @description ||= nil if value.nil? + @description = value + end + end + end + end +end diff --git a/lib/resque/scheduler/util.rb b/lib/resque/scheduler/util.rb index 6bc65ada..a6cf30e2 100644 --- a/lib/resque/scheduler/util.rb +++ b/lib/resque/scheduler/util.rb @@ -8,10 +8,12 @@ class Util # Scheduler. refer to: # https://github.com/resque/resque-scheduler/pull/273 + CLASSIFY_DELIMETERS = %w(- _).freeze + def self.constantize(camel_cased_word) camel_cased_word = camel_cased_word.to_s - if camel_cased_word.include?('-') + unless (camel_cased_word.chars & CLASSIFY_DELIMETERS).empty? camel_cased_word = classify(camel_cased_word) end @@ -32,7 +34,12 @@ def self.constantize(camel_cased_word) end def self.classify(dashed_word) - dashed_word.split('-').map(&:capitalize).join + CLASSIFY_DELIMETERS.each do |delimiter| + dashed_word = dashed_word.split(delimiter) + .map { |w| w[0].capitalize + w[1..-1] } + .join + end + dashed_word end end end diff --git a/test/cli_test.rb b/test/cli_test.rb index 18fd2b5e..d0f2da51 100644 --- a/test/cli_test.rb +++ b/test/cli_test.rb @@ -88,6 +88,10 @@ def new_cli(argv = [], env = {}) assert_equal(nil, new_cli.send(:options)[:dynamic]) end + test 'defaults to nil auto_load' do + assert_equal(nil, new_cli.send(:options)[:auto_load]) + end + test 'initializes env from the env' do cli = new_cli([], 'RAILS_ENV' => 'flurb') assert_equal('flurb', cli.send(:options)[:env]) @@ -224,6 +228,18 @@ def new_cli(argv = [], env = {}) assert_equal('flimsy', cli.send(:options)[:app_name]) end + test 'accepts auto_load via -A' do + cli = new_cli(%w(-A /some/path_*_job.rb)) + cli.parse_options + assert_equal('/some/path_*_job.rb', cli.send(:options)[:auto_load]) + end + + test 'accepts auto_load via --auto-load' do + cli = new_cli(%w(--auto-load /some/path_*_job.rb)) + cli.parse_options + assert_equal('/some/path_*_job.rb', cli.send(:options)[:auto_load]) + end + test 'runs Resque::Scheduler' do Resque::Scheduler.expects(:run) Resque::Scheduler::Cli.run!([], {}) diff --git a/test/fixtures/error_job.rb b/test/fixtures/error_job.rb new file mode 100644 index 00000000..f8f22f3d --- /dev/null +++ b/test/fixtures/error_job.rb @@ -0,0 +1,7 @@ +# vim:fileencoding=utf-8 + +require 'resque/scheduler/job' + +class ErrorJob + include Resque::Scheduler::Job +end diff --git a/test/fixtures/valid_cron_job.rb b/test/fixtures/valid_cron_job.rb new file mode 100644 index 00000000..60e90784 --- /dev/null +++ b/test/fixtures/valid_cron_job.rb @@ -0,0 +1,12 @@ +# vim:fileencoding=utf-8 + +require 'resque/scheduler/job' + +class ValidCronJob + include Resque::Scheduler::Job + + cron '*/2 * * * *' + queue 'default' + args 'args' + description 'description' +end diff --git a/test/fixtures/valid_every_job.rb b/test/fixtures/valid_every_job.rb new file mode 100644 index 00000000..74489a64 --- /dev/null +++ b/test/fixtures/valid_every_job.rb @@ -0,0 +1,12 @@ +# vim:fileencoding=utf-8 + +require 'resque/scheduler/job' + +class ValidEveryJob + include Resque::Scheduler::Job + + every '1d' + queue 'default' + args 'args' + description 'description' +end diff --git a/test/job_test.rb b/test/job_test.rb new file mode 100644 index 00000000..a2835544 --- /dev/null +++ b/test/job_test.rb @@ -0,0 +1,33 @@ +# vim:fileencoding=utf-8 + +require 'resque/scheduler/job' + +context 'Job' do + test 'has nil default parameters' do + class EmptyJob + include Resque::Scheduler::Job + end + + %i(cron every queue args description).each do |p| + assert_nil EmptyJob.send(p) + end + end + + test 'saves values' do + class JobWithValues + include Resque::Scheduler::Job + + cron '* */3 * * *' + every '3d' + queue 'default' + args 'some arg' + description 'nice description' + end + + assert_equal '* */3 * * *', JobWithValues.cron + assert_equal '3d', JobWithValues.every + assert_equal 'default', JobWithValues.queue + assert_equal 'some arg', JobWithValues.args + assert_equal 'nice description', JobWithValues.description + end +end diff --git a/test/scheduler_test.rb b/test/scheduler_test.rb index d759edd1..223cbfcf 100644 --- a/test/scheduler_test.rb +++ b/test/scheduler_test.rb @@ -8,7 +8,9 @@ c.quiet = true c.env = nil c.app_name = nil + c.auto_load = nil end + Resque.schedule.clear Resque.data_store.redis.flushall Resque::Scheduler.clear_schedule! Resque::Scheduler.send(:instance_variable_set, :@scheduled_jobs, {}) @@ -104,6 +106,33 @@ assert Resque::Scheduler.scheduled_jobs.include?('some_ivar_job2') end + test 'can load jobs with auto_load' do + Resque::Scheduler.auto_load = 'test/fixtures/valid*job.rb' + + Resque::Scheduler.reload_schedule! + + assert_equal(2, Resque::Scheduler.rufus_scheduler.jobs.size) + assert_equal(2, Resque::Scheduler.scheduled_jobs.size) + %w(valid_cron_job valid_every_job).each do |job_name| + assert Resque::Scheduler.scheduled_jobs.keys.include?(job_name) + end + + cron_job = Resque::Scheduler.scheduled_jobs['valid_cron_job'] + assert_equal('*/2 * * * *', cron_job.original) + + every_job = Resque::Scheduler.scheduled_jobs['valid_every_job'] + assert_equal('1d', every_job.original) + end + + test 'do not load error job with auto_load' do + Resque::Scheduler.auto_load = 'test/fixtures/error_job.rb' + + Resque::Scheduler.reload_schedule! + + assert_equal(0, Resque::Scheduler.rufus_scheduler.jobs.size) + assert_equal(0, Resque::Scheduler.scheduled_jobs.size) + end + test 'load_schedule_job loads a schedule' do Resque::Scheduler.load_schedule_job( 'some_ivar_job', diff --git a/test/util_test.rb b/test/util_test.rb index 3606156c..96baae9a 100644 --- a/test/util_test.rb +++ b/test/util_test.rb @@ -14,4 +14,8 @@ module ReSchedulIzer; end test 'constantizing with a dash' do assert util.constantize('re-schedul-izer') == ReSchedulIzer end + + test 'constantizing with an underscore' do + assert util.constantize('re_schedul_izer') == ReSchedulIzer + end end