From 50199f9bfcaca1c23ae7373efe781c1862dcc0cb Mon Sep 17 00:00:00 2001 From: Andrew Atkinson Date: Sun, 17 Dec 2023 18:14:57 -0600 Subject: [PATCH] Active Record and Active Job name formatting (#1182) * Active Record and Active Job name formatting Initial exploration of some formatting changes for consistency with Rails Guides and official sources: https://guides.rubyonrails.org/index.html - Active Job over ActiveJob - Active Record over ActiveRecord This requires close inspection though because ActiveJob and ActiveRecord can be valid parts of a class name or constant in the GoodJob source code. * Update "Active Job", "Active Record", and "Action Mailer" in Readme --------- Co-authored-by: Ben Sheldon [he/him] --- README.md | 53 +++++++++++---------- app/controllers/good_job/jobs_controller.rb | 2 +- app/models/good_job/base_execution.rb | 2 +- app/models/good_job/execution.rb | 8 ++-- app/models/good_job/job.rb | 8 ++-- 5 files changed, 37 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 09d32e8b4..5a77cd5de 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,12 @@ [![Test Status](https://github.com/bensheldon/good_job/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/bensheldon/good_job/actions/workflows/test.yml?query=branch%3Amain) [![Ruby Toolbox](https://img.shields.io/badge/dynamic/json?color=blue&label=Ruby%20Toolbox&query=%24.projects%5B0%5D.score&url=https%3A%2F%2Fwww.ruby-toolbox.com%2Fapi%2Fprojects%2Fcompare%2Fgood_job&logo=data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZm9jdXNhYmxlPSJmYWxzZSIgZGF0YS1wcmVmaXg9ImZhcyIgZGF0YS1pY29uPSJmbGFzayIgY2xhc3M9InN2Zy1pbmxpbmUtLWZhIGZhLWZsYXNrIGZhLXctMTQiIHJvbGU9ImltZyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgNDQ4IDUxMiI+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik00MzcuMiA0MDMuNUwzMjAgMjE1VjY0aDhjMTMuMyAwIDI0LTEwLjcgMjQtMjRWMjRjMC0xMy4zLTEwLjctMjQtMjQtMjRIMTIwYy0xMy4zIDAtMjQgMTAuNy0yNCAyNHYxNmMwIDEzLjMgMTAuNyAyNCAyNCAyNGg4djE1MUwxMC44IDQwMy41Qy0xOC41IDQ1MC42IDE1LjMgNTEyIDcwLjkgNTEyaDMwNi4yYzU1LjcgMCA4OS40LTYxLjUgNjAuMS0xMDguNXpNMTM3LjkgMzIwbDQ4LjItNzcuNmMzLjctNS4yIDUuOC0xMS42IDUuOC0xOC40VjY0aDY0djE2MGMwIDYuOSAyLjIgMTMuMiA1LjggMTguNGw0OC4yIDc3LjZoLTE3MnoiPjwvcGF0aD48L3N2Zz4=)](https://www.ruby-toolbox.com/projects/good_job) -GoodJob is a multithreaded, Postgres-based, ActiveJob backend for Ruby on Rails. +GoodJob is a multithreaded, Postgres-based, Active Job backend for Ruby on Rails. +GoodJob is a multithreaded, Postgres-based, Active Job backend for Ruby on Rails. -**Inspired by [Delayed::Job](https://github.com/collectiveidea/delayed_job) and [Que](https://github.com/que-rb/que), GoodJob is designed for maximum compatibility with Ruby on Rails, ActiveJob, and Postgres to be simple and performant for most workloads.** +**Inspired by [Delayed::Job](https://github.com/collectiveidea/delayed_job) and [Que](https://github.com/que-rb/que), GoodJob is designed for maximum compatibility with Ruby on Rails, Active Job, and Postgres to be simple and performant for most workloads.** -- **Designed for ActiveJob.** Complete support for [async, queues, delays, priorities, timeouts, and retries](https://edgeguides.rubyonrails.org/active_job_basics.html) with near-zero configuration. +- **Designed for Active Job.** Complete support for [async, queues, delays, priorities, timeouts, and retries](https://edgeguides.rubyonrails.org/active_job_basics.html) with near-zero configuration. - **Built for Rails.** Fully adopts Ruby on Rails [threading and code execution guidelines](https://guides.rubyonrails.org/threading_and_code_execution.html) with [Concurrent::Ruby](https://github.com/ruby-concurrency/concurrent-ruby). - **Backed by Postgres.** Relies upon Postgres integrity, session-level Advisory Locks to provide run-once safety and stay within the limits of `schema.rb`, and LISTEN/NOTIFY to reduce queuing latency. - **For most workloads.** Targets full-stack teams, economy-minded solo developers, and applications that enqueue 1-million jobs/day and more. @@ -55,7 +56,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla - [Exceptions, retries, and reliability](#exceptions-retries-and-reliability) - [Exceptions](#exceptions) - [Retries](#retries) - - [ActionMailer retries](#actionmailer-retries) + - [Action Mailer retries](#action-mailer-retries) - [Interrupts](#interrupts) - [Timeouts](#timeouts) - [Optimize queues, threads, and processes](#optimize-queues-threads-and-processes) @@ -63,7 +64,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla - [Production setup](#production-setup) - [Queue performance with Queue Select Limit](#queue-performance-with-queue-select-limit) - [Execute jobs async / in-process](#execute-jobs-async--in-process) - - [Migrate to GoodJob from a different ActiveJob backend](#migrate-to-goodjob-from-a-different-activejob-backend) + - [Migrate to GoodJob from a different Active Job backend](#migrate-to-goodjob-from-a-different-active-job-backend) - [Monitor and preserve worked jobs](#monitor-and-preserve-worked-jobs) - [Write tests](#write-tests) - [PgBouncer compatibility](#pgbouncer-compatibility) @@ -103,7 +104,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla bin/rails db:migrate:animals ``` -1. Configure the ActiveJob adapter: +1. Configure the Active Job adapter: ```ruby # config/application.rb or config/environments/{RAILS_ENV}.rb @@ -116,7 +117,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla YourJob.perform_later ``` - GoodJob supports all ActiveJob features: + GoodJob supports all Active Job features: ```ruby YourJob.set(queue: :some_queue, wait: 5.minutes, priority: 10).perform_later @@ -222,7 +223,7 @@ and this command is not required to be used. ### Configuration options -ActiveJob configuration depends on where the code is placed: +Active Job configuration depends on where the code is placed: - `config.active_job.queue_adapter = :good_job` within `config/application.rb` or `config/environments/*.rb`. - `ActiveJob::Base.queue_adapter = :good_job` within an initializer (e.g. `config/initializers/active_job.rb`). @@ -321,7 +322,7 @@ config.good_job.execution_mode = :external Good Job’s general behavior can also be configured via attributes directly on the `GoodJob` module: -- **`GoodJob.configure_active_record { ... }`** Inject Active Record configuration into GoodJob's base model, for example, when using [multiple databases with ActiveRecord](https://guides.rubyonrails.org/active_record_multiple_databases.html) or when other custom configuration is necessary for the ActiveRecord model to connect to the Postgres database. Example: +- **`GoodJob.configure_active_record { ... }`** Inject Active Record configuration into GoodJob's base model, for example, when using [multiple databases with Active Record](https://guides.rubyonrails.org/active_record_multiple_databases.html) or when other custom configuration is necessary for the Active Record model to connect to the Postgres database. Example: ```ruby # config/initializers/good_job.rb @@ -331,7 +332,7 @@ Good Job’s general behavior can also be configured via attributes directly on end ``` -- **`GoodJob.active_record_parent_class`** (string) Alternatively, modify the ActiveRecord parent class inherited by GoodJob's Active Record model `GoodJob::Job` (defaults to `"ActiveRecord::Base"`). Configure this _The value must be a String to avoid premature initialization of ActiveRecord._ +- **`GoodJob.active_record_parent_class`** (string) Alternatively, modify the Active Record parent class inherited by GoodJob's Active Record model `GoodJob::Job` (defaults to `"ActiveRecord::Base"`). Configure this _The value must be a String to avoid premature initialization of Active Record._ You’ll generally want to configure these in `config/initializers/good_job.rb`, like so: @@ -465,7 +466,7 @@ Labels can be used to search jobs in the Dashboard. For example, to find all job ### Concurrency controls -GoodJob can extend ActiveJob to provide limits on concurrently running jobs, either at time of _enqueue_ or at _perform_. Limiting concurrency can help prevent duplicate, double or unnecessary jobs from being enqueued, or race conditions when performing, for example when interacting with 3rd-party APIs. +GoodJob can extend Active Job to provide limits on concurrently running jobs, either at time of _enqueue_ or at _perform_. Limiting concurrency can help prevent duplicate, double or unnecessary jobs from being enqueued, or race conditions when performing, for example when interacting with 3rd-party APIs. ```ruby class MyJob < ApplicationJob @@ -494,7 +495,7 @@ class MyJob < ApplicationJob # A unique key to be globally locked against. # Can be String or Lambda/Proc that is invoked in the context of the job. - # Note: Arguments passed to #perform_later can be accessed through ActiveJob's `arguments` method + # Note: Arguments passed to #perform_later can be accessed through Active Job's `arguments` method # which is an array containing positional arguments and, optionally, a kwarg hash. key: -> { "MyJob-#{arguments.first}-#{arguments.last[:version]}" } # MyJob.perform_later("Alice", version: 'v2') => "MyJob-Alice-v2" ) @@ -574,11 +575,11 @@ active_jobs = GoodJob::Bulk.enqueue do # If an exception is raised within this block, no jobs will be inserted. end -# All ActiveJob instances are returned from GoodJob::Bulk.enqueue. +# All Active Job instances are returned from GoodJob::Bulk.enqueue. # Jobs that have been successfully enqueued have a `provider_job_id` set. active_jobs.all?(&:provider_job_id) -# Bulk enqueue ActiveJob instances directly without using `.perform_later`: +# Bulk enqueue Active Job instances directly without using `.perform_later`: GoodJob::Bulk.enqueue(MyJob.new, AnotherJob.new) ``` @@ -816,7 +817,7 @@ Notable changes: - Renames `:async_server` execution mode to `:async`; renames prior `:async` execution mode to `:async_all`. - Sets default Development environment's execution mode to `:async` with disabled polling. - Excludes performing jobs from `enqueue_limit`'s count in `GoodJob::ActiveJobExtensions::Concurrency`. -- Triggers `GoodJob.on_thread_error` for unhandled ActiveJob exceptions. +- Triggers `GoodJob.on_thread_error` for unhandled Active Job exceptions. - Renames `GoodJob.reperform_jobs_on_standard_error` accessor to `GoodJob.retry_on_unhandled_error`. - Renames `GoodJob::Adapter.shutdown(wait:)` argument to `GoodJob::Adapter.shutdown(timeout:)`. - Changes Advisory Lock key format from `good_jobs[ROW_ID]` to `good_jobs-[ACTIVE_JOB_ID]`. @@ -826,11 +827,11 @@ Notable changes: ### Exceptions, retries, and reliability -GoodJob guarantees that a completely-performed job will run once and only once. GoodJob fully supports ActiveJob's built-in functionality for error handling, retries and timeouts. +GoodJob guarantees that a completely-performed job will run once and only once. GoodJob fully supports Active Job's built-in functionality for error handling, retries and timeouts. #### Exceptions -ActiveJob provides [tools for rescuing and retrying exceptions](https://guides.rubyonrails.org/active_job_basics.html#exceptions), including `retry_on`, `discard_on`, `rescue_from` that will rescue exceptions before they get to GoodJob. +Active Job provides [tools for rescuing and retrying exceptions](https://guides.rubyonrails.org/active_job_basics.html#exceptions), including `retry_on`, `discard_on`, `rescue_from` that will rescue exceptions before they get to GoodJob. If errors do reach GoodJob, you can assign a callable to `GoodJob.on_thread_error` to be notified. For example, to log errors to an exception monitoring service like Sentry (or Bugsnag, Airbrake, Honeybadger, etc.): @@ -841,9 +842,9 @@ GoodJob.on_thread_error = -> (exception) { Rails.error.report(exception) } #### Retries -By default, GoodJob relies on ActiveJob's retry functionality. +By default, GoodJob relies on Active Job's retry functionality. -ActiveJob can be configured to retry an infinite number of times, with a polynomial backoff. Using ActiveJob's `retry_on` prevents exceptions from reaching GoodJob: +Active Job can be configured to retry an infinite number of times, with a polynomial backoff. Using Active Job's `retry_on` prevents exceptions from reaching GoodJob: ```ruby class ApplicationJob < ActiveJob::Base @@ -885,7 +886,7 @@ end By default, jobs will not be retried unless `retry_on` is configured. This can be overridden by setting `GoodJob.retry_on_unhandled_error` to `true`; GoodJob will then retry the failing job immediately and infinitely, potentially causing high load. -#### ActionMailer retries +#### Action Mailer retries Any configuration in `ApplicationJob` will have to be duplicated on `ActionMailer::MailDeliveryJob` because ActionMailer uses that custom class which inherits from `ActiveJob::Base`, rather than your application's `ApplicationJob`. @@ -911,7 +912,7 @@ might also be configured to use (deprecated now) `ActionMailer::DeliveryJob`. Jobs will be automatically retried if the process is interrupted while performing a job, for example as the result of a `SIGKILL` or power failure. -If you need more control over interrupt-caused retries, include the `GoodJob::ActiveJobExtensions::InterruptErrors` extension in your job class. When an interrupted job is retried, the extension will raise a `GoodJob::InterruptError` exception within the job, which allows you to use ActiveJob's `retry_on` and `discard_on` to control the behavior of the job. +If you need more control over interrupt-caused retries, include the `GoodJob::ActiveJobExtensions::InterruptErrors` extension in your job class. When an interrupted job is retried, the extension will raise a `GoodJob::InterruptError` exception within the job, which allows you to use Active Job's `retry_on` and `discard_on` to control the behavior of the job. ```ruby class MyJob < ApplicationJob @@ -1201,9 +1202,9 @@ Depending on your application configuration, you may need to take additional ste If you are using cron-style jobs, you might also want to look at your Passenger configuration, especially at [`passenger_pool_idle_time`](https://www.phusionpassenger.com/library/config/nginx/reference/#passenger_pool_idle_time) and [`passenger_min_instances`](https://www.phusionpassenger.com/library/config/nginx/reference/#passenger_min_instances) to make sure there's always at least once process running that can execute cron-style scheduled jobs. See also [Passenger's optimization guide](https://www.phusionpassenger.com/library/config/nginx/optimization/#minimizing-process-spawning) for more information. -### Migrate to GoodJob from a different ActiveJob backend +### Migrate to GoodJob from a different Active Job backend -If your application is already using an ActiveJob backend, you will need to install GoodJob to enqueue and perform newly created jobs _and_ finish performing pre-existing jobs on the previous backend. +If your application is already using an Active Job backend, you will need to install GoodJob to enqueue and perform newly created jobs _and_ finish performing pre-existing jobs on the previous backend. 1. Enqueue newly created jobs on GoodJob either entirely by setting `ActiveJob::Base.queue_adapter = :good_job` or progressively via individual job classes: @@ -1223,7 +1224,7 @@ If your application is already using an ActiveJob backend, you will need to inst worker: bundle exec que ./config/environment.rb & bundle exec good_job & wait -n ``` -1. Once you are confident that no unperformed jobs remain in the previous ActiveJob backend, code and configuration for that backend can be completely removed. +1. Once you are confident that no unperformed jobs remain in the previous Active Job backend, code and configuration for that backend can be completely removed. ### Monitor and preserve worked jobs @@ -1296,7 +1297,7 @@ A workaround to this limitation is to make a direct database connection availabl url: postgres://database_host/my_database ``` -1. Create a new ActiveRecord base class that uses the direct database connection +1. Create a new Active Record base class that uses the direct database connection ```ruby # app/models/application_direct_record.rb @@ -1307,7 +1308,7 @@ A workaround to this limitation is to make a direct database connection availabl end ``` -1. Configure GoodJob to use the newly created ActiveRecord base class: +1. Configure GoodJob to use the newly created Active Record base class: ```ruby # config/initializers/good_job.rb diff --git a/app/controllers/good_job/jobs_controller.rb b/app/controllers/good_job/jobs_controller.rb index c69642117..7dcce0e18 100644 --- a/app/controllers/good_job/jobs_controller.rb +++ b/app/controllers/good_job/jobs_controller.rb @@ -96,7 +96,7 @@ def destroy def redirect_on_error(exception) alert = case exception when GoodJob::Job::AdapterNotGoodJobError - "ActiveJob Queue Adapter must be GoodJob." + "Active Job Queue Adapter must be GoodJob." when GoodJob::Job::ActionForStateMismatchError "Job is not in an appropriate state for this action." else diff --git a/app/models/good_job/base_execution.rb b/app/models/good_job/base_execution.rb index 1e2c783a1..086202517 100644 --- a/app/models/good_job/base_execution.rb +++ b/app/models/good_job/base_execution.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module GoodJob - # ActiveRecord model to share behavior between {Job} and {Execution} models + # Active Record model to share behavior between {Job} and {Execution} models # which both read out of the same table. class BaseExecution < BaseRecord include AdvisoryLockable diff --git a/app/models/good_job/execution.rb b/app/models/good_job/execution.rb index 895fb9863..3dc068404 100644 --- a/app/models/good_job/execution.rb +++ b/app/models/good_job/execution.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module GoodJob - # ActiveRecord model that represents an +ActiveJob+ job. + # Active Record model that represents an +ActiveJob+ job. class Execution < BaseExecution # Raised if something attempts to execute a previously completed Execution again. PreviouslyPerformedError = Class.new(StandardError) @@ -150,9 +150,9 @@ def self.queue_parser(string) # @return [ActiveRecord::Relation] scope :schedule_ordered, -> { order(coalesce_scheduled_at_created_at.asc) } - # Get Jobs were completed before the given timestamp. If no timestamp is - # provided, get all jobs that have been completed. By default, GoodJob - # destroys jobs after they are completed and this will find no jobs. + # Get completed jobs before the given timestamp. If no timestamp is + # provided, get *all* completed jobs. By default, GoodJob + # destroys jobs after they're completed, meaning this returns no jobs. # However, if you have changed {GoodJob.preserve_job_records}, this may # find completed Jobs. # @!method finished(timestamp = nil) diff --git a/app/models/good_job/job.rb b/app/models/good_job/job.rb index 4ce32bf75..4e58e2d73 100644 --- a/app/models/good_job/job.rb +++ b/app/models/good_job/job.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module GoodJob - # ActiveRecord model that represents an +ActiveJob+ job. + # Active Record model that represents an +ActiveJob+ job. # There is not a table in the database whose discrete rows represents "Jobs". # The +good_jobs+ table is a table of individual {GoodJob::Execution}s that share the same +active_job_id+. # A single row from the +good_jobs+ table of executions is fetched to represent a Job. @@ -9,7 +9,7 @@ module GoodJob class Job < BaseExecution # Raised when an inappropriate action is applied to a Job based on its state. ActionForStateMismatchError = Class.new(StandardError) - # Raised when an action requires GoodJob to be the ActiveJob Queue Adapter but GoodJob is not. + # Raised when GoodJob is not configured as the Active Job Queue Adapter AdapterNotGoodJobError = Class.new(StandardError) # Attached to a Job's Execution when the Job is discarded. DiscardJobError = Class.new(StandardError) @@ -60,7 +60,7 @@ def table_name=(_value) scope :unfinished_undiscrete, -> { where(finished_at: nil, retried_good_job_id: nil, is_discrete: [nil, false]) } - # The job's ActiveJob UUID + # The job's Active Job UUID # @return [String] def id active_job_id @@ -101,7 +101,7 @@ def tail_execution executions.first end - # The number of times this job has been executed, according to ActiveJob's serialized state. + # The number of times this job has been executed, according to Active Job's serialized state. # @return [Numeric] def executions_count aj_count = serialized_params.fetch('executions', 0)