diff --git a/.rubocop.yml b/.rubocop.yml index 34ad98f94..13bf5fea8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -17,13 +17,16 @@ AllCops: RedundantBlockCall: Enabled: false +BlockLength: + Enabled: false + # Avoid more than `Max` levels of nesting. BlockNesting: Max: 2 # Indentation of when/else CaseIndentation: - IndentWhenRelativeTo: end + EnforcedStyle: end IndentOneStep: false ClassLength: @@ -49,6 +52,10 @@ DotPosition: DoubleNegation: Enabled: false +# Detects any duplication as issue including our conditional requires +DuplicatedGem: + Enabled: false + EmptyLinesAroundAccessModifier: Enabled: true @@ -58,7 +65,10 @@ Encoding: # Align ends correctly EndAlignment: - AlignWith: variable + EnforcedStyleAlignWith: variable + +Style/FrozenStringLiteralComment: + Enabled: false # Enforce Ruby 1.8-compatible hash syntax HashSyntax: @@ -77,6 +87,9 @@ MethodLength: MultilineOperationIndentation: EnforcedStyle: indented +Style/NumericPredicate: + Enabled: false + # Avoid long parameter lists ParameterLists: Max: 4 @@ -103,6 +116,9 @@ RegexpLiteral: RescueModifier: Enabled: false +Style/SafeNavigation: + Enabled: false + SignalException: EnforcedStyle: only_raise @@ -110,6 +126,9 @@ SignalException: SpaceInsideHashLiteralBraces: EnforcedStyle: no_space +Style/SymbolArray: + Enabled: false + SymbolProc: Enabled: false @@ -119,5 +138,8 @@ TrailingCommaInLiteral: TrailingCommaInArguments: Enabled: false +YAMLLoad: + Enabled: false + ZeroLengthPredicate: Enabled: false diff --git a/.travis.yml b/.travis.yml index 2890e151f..d911f7a4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,16 +3,15 @@ env: global: - JRUBY_OPTS="$JRUBY_OPTS --debug" matrix: - - RAILS_VERSION="~> 4.0.0" - - RAILS_VERSION="~> 4.1.0" - RAILS_VERSION="~> 4.2.0" - - RAILS_VERSION="~> 5.0.0.beta1" + - RAILS_VERSION="~> 5.0.0" + - RAILS_VERSION="~> 5.1.0.rc1" - RAILS_VERSION="edge" rvm: - - 2.0.0 - 2.1 - - 2.2.5 - - 2.3.1 + - 2.2.6 + - 2.3.3 + - 2.4.1 - jruby-9.0.5.0 - jruby-head - rbx-2 @@ -27,6 +26,8 @@ matrix: - rvm: rbx-2 - rvm: ruby-head - env: RAILS_VERSION="edge" + - rvm: jruby-9.0.5.0 + env: RAILS_VERSION="~> 5.1.0.rc1" fast_finish: true # legacy testing # things still run and we don't have a good reason to break it @@ -38,11 +39,17 @@ matrix: env: RAILS_VERSION="~> 3.1.0" - rvm: 2.1 env: RAILS_VERSION="~> 3.2.0" + - rvm: 2.1 + env: RAILS_VERSION="~> 4.0.0" + - rvm: 2.3.3 + env: RAILS_VERSION="~> 4.1.0" exclude: - - rvm: 2.0.0 - env: RAILS_VERSION="~> 5.0.0.beta1" - rvm: 2.1 - env: RAILS_VERSION="~> 5.0.0.beta1" + env: RAILS_VERSION="~> 5.0.0" + - rvm: 2.1 + env: RAILS_VERSION="~> 5.1.0.rc1" + - rvm: 2.1 + env: RAILS_VERSION="edge" sudo: false diff --git a/Gemfile b/Gemfile index 9a781192f..593c62460 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,7 @@ source 'https://rubygems.org' +rails_version = ENV['RAILS_VERSION'] || '' + gem 'rake' platforms :ruby do @@ -8,7 +10,13 @@ end platforms :jruby do gem 'jruby-openssl' - gem 'activerecord-jdbcsqlite3-adapter' + if rails_version == 'edge' || rails_version.match(/5\.\d+\.\d+/) + gem 'activerecord-jdbcsqlite3-adapter', + :git => 'https://github.com/jruby/activerecord-jdbc-adapter.git', + :branch => 'rails-5' + else + gem 'activerecord-jdbcsqlite3-adapter' + end gem 'mime-types', ['~> 2.6', '< 2.99'] end @@ -18,16 +26,16 @@ end group :test do if ENV['RAILS_VERSION'] == 'edge' - gem 'activerecord', :github => 'rails/rails' gem 'actionmailer', :github => 'rails/rails' + gem 'activerecord', :github => 'rails/rails' else - gem 'activerecord', (ENV['RAILS_VERSION'] || ['>= 3.0', '< 5.1']) gem 'actionmailer', (ENV['RAILS_VERSION'] || ['>= 3.0', '< 5.1']) + gem 'activerecord', (ENV['RAILS_VERSION'] || ['>= 3.0', '< 5.1']) end gem 'coveralls', :require => false gem 'rspec', '>= 3' - gem 'rubocop', '>= 0.25' + gem 'rubocop', '>= 0.25', '< 0.49' gem 'simplecov', '>= 0.9' end diff --git a/README.md b/README.md index d38ef3856..a6e4bfa7f 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,7 @@ multitude of core tasks. Amongst those tasks are: Installation ============ -delayed_job 3.0.0 only supports Rails 3.0+. See the [2.0 -branch](https://github.com/collectiveidea/delayed_job/tree/v2.0) for Rails 2. +delayed_job 3.0.0 only supports Rails 3.0+. delayed_job supports multiple backends for storing the job queue. [See the wiki for other backends](https://github.com/collectiveidea/delayed_job/wiki/Backends). @@ -220,6 +219,12 @@ Configured queue priorities can be overriden by passing priority to the delay me object.delay(:queue => 'high_priority', priority: 0).method ``` +You can start processes to only work certain queues with the `queue` and `queues` +options defined below. Processes started without specifying a queue will run jobs +from **any** queue. To effectively have a process that runs jobs where a queue is not +specified, set a default queue name with `Delayed::Worker.default_queue_name` and +have the processes run that queue. + Running Jobs ============ `script/delayed_job` can be used to manage a background process which will diff --git a/lib/delayed/backend/base.rb b/lib/delayed/backend/base.rb index cd885b9b9..4f18fb39d 100644 --- a/lib/delayed/backend/base.rb +++ b/lib/delayed/backend/base.rb @@ -30,16 +30,13 @@ def reserve(worker, max_run_time = Worker.max_run_time) end # Allow the backend to attempt recovery from reserve errors - def recover_from(_error) - end + def recover_from(_error); end # Hook method that is called before a new worker is forked - def before_fork - end + def before_fork; end # Hook method that is called after a new worker is forked - def after_fork - end + def after_fork; end def work_off(num = 100) warn '[DEPRECATION] `Delayed::Job.work_off` is deprecated. Use `Delayed::Worker.new.work_off instead.' @@ -101,7 +98,7 @@ def unlock def hook(name, *args) if payload_object.respond_to?(name) method = payload_object.method(name) - method.arity == 0 ? method.call : method.call(self, *args) + method.arity.zero? ? method.call : method.call(self, *args) end rescue DeserializationError # rubocop:disable HandleExceptions end diff --git a/lib/delayed/backend/job_preparer.rb b/lib/delayed/backend/job_preparer.rb index 52d0d32c8..f545eda0e 100644 --- a/lib/delayed/backend/job_preparer.rb +++ b/lib/delayed/backend/job_preparer.rb @@ -4,7 +4,7 @@ class JobPreparer attr_reader :options, :args def initialize(*args) - @options = args.extract_options! + @options = args.extract_options!.dup @args = args end @@ -42,9 +42,11 @@ def handle_deprecation options[:run_at] = args[1] end + # rubocop:disable GuardClause unless options[:payload_object].respond_to?(:perform) raise ArgumentError, 'Cannot enqueue items which do not respond to perform' end + # rubocop:enabled GuardClause end end end diff --git a/lib/delayed/backend/shared_spec.rb b/lib/delayed/backend/shared_spec.rb index df465a9a2..771e0cd9c 100644 --- a/lib/delayed/backend/shared_spec.rb +++ b/lib/delayed/backend/shared_spec.rb @@ -113,6 +113,12 @@ def create_job(opts = {}) job = described_class.enqueue M::ModuleJob.new expect { job.invoke_job }.to change { M::ModuleJob.runs }.from(0).to(1) end + + it 'does not mutate the options hash' do + options = {:priority => 1} + described_class.enqueue SimpleJob.new, options + expect(options).to eq(:priority => 1) + end end context 'with delay_jobs = false' do diff --git a/lib/delayed/command.rb b/lib/delayed/command.rb index 58a2d91c9..281078242 100644 --- a/lib/delayed/command.rb +++ b/lib/delayed/command.rb @@ -91,11 +91,13 @@ def daemonize # rubocop:disable PerceivedComplexity if worker_pools setup_pools elsif @options[:identifier] + # rubocop:disable GuardClause if worker_count > 1 raise ArgumentError, 'Cannot specify both --number-of-workers and --identifier' else run_process("delayed_job.#{@options[:identifier]}", @options) end + # rubocop:enable GuardClause else worker_count.times do |worker_index| process_name = worker_count == 1 ? 'delayed_job' : "delayed_job.#{worker_index}" diff --git a/lib/delayed/exceptions.rb b/lib/delayed/exceptions.rb index 542361dea..11f132276 100644 --- a/lib/delayed/exceptions.rb +++ b/lib/delayed/exceptions.rb @@ -8,5 +8,5 @@ def message end end - class FatalBackendError < Exception; end + class FatalBackendError < RuntimeError; end end diff --git a/lib/delayed/lifecycle.rb b/lib/delayed/lifecycle.rb index 63ab1b14f..57dbf98a9 100644 --- a/lib/delayed/lifecycle.rb +++ b/lib/delayed/lifecycle.rb @@ -1,5 +1,5 @@ module Delayed - class InvalidCallback < Exception; end + class InvalidCallback < RuntimeError; end class Lifecycle EVENTS = { diff --git a/lib/delayed/message_sending.rb b/lib/delayed/message_sending.rb index 5bf8b6dad..a2808fd39 100644 --- a/lib/delayed/message_sending.rb +++ b/lib/delayed/message_sending.rb @@ -6,9 +6,11 @@ def initialize(payload_class, target, options) @options = options end + # rubocop:disable MethodMissing def method_missing(method, *args) Job.enqueue({:payload_object => @payload_class.new(@target, method.to_sym, args)}.merge(@options)) end + # rubocop:enable MethodMissing end module MessageSending @@ -26,36 +28,36 @@ def send_at(time, method, *args) warn '[DEPRECATION] `object.send_at(time, :method)` is deprecated. Use `object.delay(:run_at => time).method' __delay__(:run_at => time).__send__(method, *args) end + end - module ClassMethods - def handle_asynchronously(method, opts = {}) # rubocop:disable PerceivedComplexity - aliased_method = method.to_s.sub(/([?!=])$/, '') - punctuation = $1 # rubocop:disable PerlBackrefs - with_method = "#{aliased_method}_with_delay#{punctuation}" - without_method = "#{aliased_method}_without_delay#{punctuation}" - define_method(with_method) do |*args| - curr_opts = opts.clone - curr_opts.each_key do |key| - next unless (val = curr_opts[key]).is_a?(Proc) - curr_opts[key] = if val.arity == 1 - val.call(self) - else - val.call - end + module MessageSendingClassMethods + def handle_asynchronously(method, opts = {}) # rubocop:disable PerceivedComplexity + aliased_method = method.to_s.sub(/([?!=])$/, '') + punctuation = $1 # rubocop:disable PerlBackrefs + with_method = "#{aliased_method}_with_delay#{punctuation}" + without_method = "#{aliased_method}_without_delay#{punctuation}" + define_method(with_method) do |*args| + curr_opts = opts.clone + curr_opts.each_key do |key| + next unless (val = curr_opts[key]).is_a?(Proc) + curr_opts[key] = if val.arity == 1 + val.call(self) + else + val.call end - delay(curr_opts).__send__(without_method, *args) end + delay(curr_opts).__send__(without_method, *args) + end - alias_method without_method, method - alias_method method, with_method + alias_method without_method, method + alias_method method, with_method - if public_method_defined?(without_method) - public method - elsif protected_method_defined?(without_method) - protected method - elsif private_method_defined?(without_method) - private method - end + if public_method_defined?(without_method) + public method + elsif protected_method_defined?(without_method) + protected method + elsif private_method_defined?(without_method) + private method end end end diff --git a/lib/delayed/performable_method.rb b/lib/delayed/performable_method.rb index 0bd1fea49..96a28b056 100644 --- a/lib/delayed/performable_method.rb +++ b/lib/delayed/performable_method.rb @@ -30,9 +30,11 @@ def method(sym) object.method(sym) end + # rubocop:disable MethodMissing def method_missing(symbol, *args) object.send(symbol, *args) end + # rubocop:enable MethodMissing def respond_to?(symbol, include_private = false) super || object.respond_to?(symbol, include_private) diff --git a/lib/delayed/psych_ext.rb b/lib/delayed/psych_ext.rb index 2c535237e..5b12df0c9 100644 --- a/lib/delayed/psych_ext.rb +++ b/lib/delayed/psych_ext.rb @@ -37,7 +37,7 @@ def visit_Psych_Nodes_Mapping(object) # rubocop:disable CyclomaticComplexity, Me klass = result.class id = result[klass.primary_key] begin - klass.find(id) + klass.unscoped.find(id) rescue ActiveRecord::RecordNotFound => error # rubocop:disable BlockNesting raise Delayed::DeserializationError, "ActiveRecord::RecordNotFound, class: #{klass}, primary key: #{id} (#{error.message})" end diff --git a/lib/delayed/worker.rb b/lib/delayed/worker.rb index 2c3081f05..c02f0523e 100644 --- a/lib/delayed/worker.rb +++ b/lib/delayed/worker.rb @@ -116,7 +116,7 @@ def self.setup_lifecycle end def self.reload_app? - defined?(ActionDispatch::Reloader) && Rails.application.config.cache_classes == false + Rails.application.reloaders.any?(&:updated?) && Rails.application.config.cache_classes == false end def self.delay_job?(job) @@ -233,6 +233,8 @@ def run(job) job_say job, format('COMPLETED after %.4f', runtime) return true # did work rescue DeserializationError => error + job_say job, "FAILED permanently with #{error.class.name}: #{error.message}", 'error' + job.error = error failed(job) rescue Exception => error # rubocop:disable RescueException @@ -268,7 +270,7 @@ def failed(job) end def job_say(job, text, level = default_log_level) - text = "Job #{job.name} (id=#{job.id}) #{text}" + text = "Job #{job.name} (id=#{job.id})#{say_queue(job.queue)} #{text}" say text, level end @@ -293,6 +295,10 @@ def max_run_time(job) protected + def say_queue(queue) + " (queue=#{queue})" if queue + end + def handle_failed_job(job, error) job.error = error job_say job, "FAILED (#{job.attempts} prior attempts) with #{error.class.name}: #{error.message}", 'error' @@ -320,8 +326,12 @@ def reserve_job def reload! return unless self.class.reload_app? - ActionDispatch::Reloader.cleanup! - ActionDispatch::Reloader.prepare! + if defined?(ActiveSupport::Reloader) + Rails.application.reloader.reload! + else + ActionDispatch::Reloader.cleanup! + ActionDispatch::Reloader.prepare! + end end end end diff --git a/lib/delayed_job.rb b/lib/delayed_job.rb index 808831595..439dd8587 100644 --- a/lib/delayed_job.rb +++ b/lib/delayed_job.rb @@ -1,3 +1,4 @@ +require 'active_support' require 'delayed/compatibility' require 'delayed/exceptions' require 'delayed/message_sending' @@ -19,4 +20,4 @@ require 'delayed/railtie' if defined?(Rails::Railtie) Object.send(:include, Delayed::MessageSending) -Module.send(:include, Delayed::MessageSending::ClassMethods) +Module.send(:include, Delayed::MessageSendingClassMethods) diff --git a/lib/generators/delayed_job/delayed_job_generator.rb b/lib/generators/delayed_job/delayed_job_generator.rb index cba43c161..60b473055 100644 --- a/lib/generators/delayed_job/delayed_job_generator.rb +++ b/lib/generators/delayed_job/delayed_job_generator.rb @@ -6,6 +6,6 @@ class DelayedJobGenerator < Rails::Generators::Base def create_executable_file template 'script', "#{Delayed::Compatibility.executable_prefix}/delayed_job" - chmod "#{Delayed::Compatibility.executable_prefix}/delayed_job", 0755 + chmod "#{Delayed::Compatibility.executable_prefix}/delayed_job", 0o755 end end diff --git a/spec/autoloaded/clazz.rb b/spec/autoloaded/clazz.rb index 5c5756d8b..daa9e9213 100644 --- a/spec/autoloaded/clazz.rb +++ b/spec/autoloaded/clazz.rb @@ -1,7 +1,6 @@ # Make sure this file does not get required manually module Autoloaded class Clazz - def perform - end + def perform; end end end diff --git a/spec/autoloaded/instance_clazz.rb b/spec/autoloaded/instance_clazz.rb index c6e4d4a30..1e72c2bff 100644 --- a/spec/autoloaded/instance_clazz.rb +++ b/spec/autoloaded/instance_clazz.rb @@ -1,6 +1,5 @@ module Autoloaded class InstanceClazz - def perform - end + def perform; end end end diff --git a/spec/autoloaded/instance_struct.rb b/spec/autoloaded/instance_struct.rb index 68a9b9485..80d3daffc 100644 --- a/spec/autoloaded/instance_struct.rb +++ b/spec/autoloaded/instance_struct.rb @@ -1,7 +1,6 @@ module Autoloaded InstanceStruct = ::Struct.new(nil) class InstanceStruct - def perform - end + def perform; end end end diff --git a/spec/autoloaded/struct.rb b/spec/autoloaded/struct.rb index 4e20bd7b5..97cba2cc9 100644 --- a/spec/autoloaded/struct.rb +++ b/spec/autoloaded/struct.rb @@ -2,7 +2,6 @@ module Autoloaded Struct = ::Struct.new(nil) class Struct - def perform - end + def perform; end end end diff --git a/spec/message_sending_spec.rb b/spec/message_sending_spec.rb index 7b4b98d17..ca58edcc1 100644 --- a/spec/message_sending_spec.rb +++ b/spec/message_sending_spec.rb @@ -1,6 +1,11 @@ require 'helper' describe Delayed::MessageSending do + it 'does not include ClassMethods along with MessageSending' do + expect { ClassMethods }.to raise_error(NameError) + expect(defined?(String::ClassMethods)).to eq(nil) + end + describe 'handle_asynchronously' do class Story def tell!(_arg); end @@ -19,7 +24,7 @@ def tell!(_arg); end expect(job.payload_object.class).to eq(Delayed::PerformableMethod) expect(job.payload_object.method_name).to eq(:tell_without_delay!) expect(job.payload_object.args).to eq([1]) - end.to change { Delayed::Job.count } + end.to(change { Delayed::Job.count }) end describe 'with options' do @@ -42,8 +47,7 @@ def tell; end describe 'using a proc with parameters' do class Yarn attr_accessor :importance - def spin - end + def spin; end handle_asynchronously :spin, :priority => proc { |y| y.importance } end @@ -107,7 +111,7 @@ def tell expect do fairy_tail.delay.tell end.to change(fairy_tail, :happy_ending).from(nil).to(true) - end.not_to change { Delayed::Job.count } + end.not_to(change { Delayed::Job.count }) end it 'does delay the job when delay_jobs is true' do @@ -137,7 +141,7 @@ def tell expect do fairy_tail.delay.tell end.to change(fairy_tail, :happy_ending).from(nil).to(true) - end.not_to change { Delayed::Job.count } + end.not_to(change { Delayed::Job.count }) end end end diff --git a/spec/performable_method_spec.rb b/spec/performable_method_spec.rb index 751340618..a4d700322 100644 --- a/spec/performable_method_spec.rb +++ b/spec/performable_method_spec.rb @@ -30,8 +30,7 @@ it 'does not raise NoMethodError if target method is private' do clazz = Class.new do - def private_method - end + def private_method; end private :private_method end expect { Delayed::PerformableMethod.new(clazz.new, :private_method, []) }.not_to raise_error diff --git a/spec/worker_spec.rb b/spec/worker_spec.rb index 2f0054234..1ce08ce99 100644 --- a/spec/worker_spec.rb +++ b/spec/worker_spec.rb @@ -24,15 +24,23 @@ describe 'job_say' do before do @worker = Delayed::Worker.new - @job = double('job', :id => 123, :name => 'ExampleJob') + @job = double('job', :id => 123, :name => 'ExampleJob', :queue => nil) end it 'logs with job name and id' do + expect(@job).to receive(:queue) expect(@worker).to receive(:say). with('Job ExampleJob (id=123) message', Delayed::Worker.default_log_level) @worker.job_say(@job, 'message') end + it 'logs with job name, queue and id' do + expect(@job).to receive(:queue).and_return('test') + expect(@worker).to receive(:say). + with('Job ExampleJob (id=123) (queue=test) message', Delayed::Worker.default_log_level) + @worker.job_say(@job, 'message') + end + it 'has a configurable default log level' do Delayed::Worker.default_log_level = 'error'