From fe3f92e53888ce25bec97710c66188e05e056ef1 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 18:59:44 +0900 Subject: [PATCH 01/16] activejob: Copy hand-written RBS and test from 6.0 to 8.1 Co-Authored-By: Claude Opus 5.5 (1M context) --- gems/activejob/8.1/_test/activejob.rb | 25 ++++++++ gems/activejob/8.1/_test/metadata.yaml | 3 + gems/activejob/8.1/_test/send_message_job.rbs | 7 +++ gems/activejob/8.1/activejob.rbs | 62 +++++++++++++++++++ gems/activejob/8.1/patch.rbs | 29 +++++++++ 5 files changed, 126 insertions(+) create mode 100644 gems/activejob/8.1/_test/activejob.rb create mode 100644 gems/activejob/8.1/_test/metadata.yaml create mode 100644 gems/activejob/8.1/_test/send_message_job.rbs create mode 100644 gems/activejob/8.1/activejob.rbs create mode 100644 gems/activejob/8.1/patch.rbs diff --git a/gems/activejob/8.1/_test/activejob.rb b/gems/activejob/8.1/_test/activejob.rb new file mode 100644 index 000000000..d0521bf37 --- /dev/null +++ b/gems/activejob/8.1/_test/activejob.rb @@ -0,0 +1,25 @@ +class SendMessageJob < ActiveJob::Base + queue_as :default + self.queue_adapter = :sidekiq + + before_enqueue do |job| + # before enqueued + end + + around_perform :around_task + + def perform(*args) + # Do something later + end + + private + + def around_task + # Do something before + # yield + # Do something after + end +end + +SendMessageJob.perform_now +SendMessageJob.set(queue: :another_queue).perform_later(flag: false) diff --git a/gems/activejob/8.1/_test/metadata.yaml b/gems/activejob/8.1/_test/metadata.yaml new file mode 100644 index 000000000..45c31bed5 --- /dev/null +++ b/gems/activejob/8.1/_test/metadata.yaml @@ -0,0 +1,3 @@ +additional_gems: + - railties + - sidekiq \ No newline at end of file diff --git a/gems/activejob/8.1/_test/send_message_job.rbs b/gems/activejob/8.1/_test/send_message_job.rbs new file mode 100644 index 000000000..70b2b929b --- /dev/null +++ b/gems/activejob/8.1/_test/send_message_job.rbs @@ -0,0 +1,7 @@ +class SendMessageJob < ActiveJob::Base + def perform: (*untyped) -> void + + private + + def around_task: () -> void +end diff --git a/gems/activejob/8.1/activejob.rbs b/gems/activejob/8.1/activejob.rbs new file mode 100644 index 000000000..e5447b4a9 --- /dev/null +++ b/gems/activejob/8.1/activejob.rbs @@ -0,0 +1,62 @@ +module ActiveJob + class Base + extend ActiveJob::Callbacks::ClassMethods + extend ActiveJob::Core::ClassMethods + extend ActiveJob::Enqueuing::ClassMethods + extend ActiveJob::Exceptions::ClassMethods + extend ActiveJob::Execution::ClassMethods + extend ActiveJob::QueueAdapter::ClassMethods + extend ActiveJob::QueueName::ClassMethods + extend ActiveJob::QueuePriority::ClassMethods + + def self.queue_as: (*untyped) -> void + | ... + def self.queue_with_priority: (?untyped? priority) -> void + | ... + def self.before_perform: (*untyped filters) -> void + | ... + def self.after_perform: (*untyped filters) -> void + | ... + def self.around_perform: (*untyped filters) -> void + | ... + def self.before_enqueue: (*untyped filters) -> void + | ... + def self.after_enqueue: (*untyped filters) -> void + | ... + def self.around_enqueue: (*untyped filters) -> void + | ... + def self.retry_on: (*untyped) -> void + | ... + def self.discard_on: (*untyped) -> void + | ... + end +end + +module ActiveJob + module QueueAdapters + # == Sidekiq adapter for Active Job + # + # Simple, efficient background processing for Ruby. Sidekiq uses threads to + # handle many jobs at the same time in the same process. It does not + # require Rails but will integrate tightly with it to make background + # processing dead simple. + # + # Read more about Sidekiq {here}[http://sidekiq.org]. + # + # To use Sidekiq set the queue_adapter config to +:sidekiq+. + # + # Rails.application.config.active_job.queue_adapter = :sidekiq + class SidekiqAdapter + def enqueue: (untyped job) -> untyped + + def enqueue_at: (untyped job, untyped timestamp) -> untyped + + class JobWrapper + # nodoc: + include Sidekiq::Job + + def perform: (untyped job_data) -> untyped + end + end + end +end \ No newline at end of file diff --git a/gems/activejob/8.1/patch.rbs b/gems/activejob/8.1/patch.rbs new file mode 100644 index 000000000..0752327fd --- /dev/null +++ b/gems/activejob/8.1/patch.rbs @@ -0,0 +1,29 @@ +# Remove the fake type if the real type is available +module Que + class Job + end +end + +# Remove the fake type if the real type is available +class QC + class Queue + end +end + +# Glue code that could be made more detailed if Sidekiq is available. +module Sidekiq + module Job + end +end + +# Remove the fake type if the real type is available +module Sneakers + module Worker + end +end + +# Remove the fake type if the real type is available +module SuckerPunch + module Job + end +end From e49adbb94142f762ec459df3c1e944b9760acaab Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 19:03:18 +0900 Subject: [PATCH 02/16] activejob: Generate RBS from activejob 8.1.4 Generated with `rbs prototype rb` from the activejob 8.1.4 source and post-processed the same way as the other Rails 8.1 gems: drop members already defined elsewhere, fill missing type arguments, add accessors defined with class_attribute, mattr_accessor, and cattr_accessor, add methods defined with delegate, and carry over typed signatures from 6.0 where the method still has the same parameters. The job generator in lib/rails is left out, as its superclass comes from railties, which activejob doesn't depend on. Co-Authored-By: Claude Opus 5.5 (1M context) --- gems/activejob/8.1/activejob-generated.rbs | 2730 ++++++++++++++++++++ 1 file changed, 2730 insertions(+) create mode 100644 gems/activejob/8.1/activejob-generated.rbs diff --git a/gems/activejob/8.1/activejob-generated.rbs b/gems/activejob/8.1/activejob-generated.rbs new file mode 100644 index 000000000..ac0eef7a2 --- /dev/null +++ b/gems/activejob/8.1/activejob-generated.rbs @@ -0,0 +1,2730 @@ +# The generated code is based on Ruby on Rails source code +# You can find the license of Ruby on Rails from following. + +#Copyright (c) David Heinemeier Hansson +# +#Permission is hereby granted, free of charge, to any person obtaining +#a copy of this software and associated documentation files (the +#"Software"), to deal in the Software without restriction, including +#without limitation the rights to use, copy, modify, merge, publish, +#distribute, sublicense, and/or sell copies of the Software, and to +#permit persons to whom the Software is furnished to do so, subject to +#the following conditions: +# +#The above copyright notice and this permission notice shall be +#included in all copies or substantial portions of the Software. +# +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +#EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +#MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +#NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +#LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +#OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +#WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +# :markup: markdown +# :include: ../README.md +module ActiveJob + def self.verbose_enqueue_logs: () -> untyped + + def self.verbose_enqueue_logs=: (untyped value) -> untyped + + extend ::ActiveSupport::Autoload + + # Push many jobs onto the queue at once without running enqueue callbacks. + # Queue adapters may communicate the enqueue status of each job by setting + # successfully_enqueued and/or enqueue_error on the passed-in job instances. + def self.perform_all_later: (*untyped jobs) -> nil +end + +module ActiveJob + # Raised when an exception is raised during job arguments deserialization. + # + # Wraps the original exception raised as +cause+. + class DeserializationError < ::StandardError + def initialize: () -> void + end + + # Raised when an unsupported argument type is set as a job argument. We + # currently support String, Integer, Float, NilClass, TrueClass, FalseClass, + # BigDecimal, Symbol, Date, Time, DateTime, ActiveSupport::TimeWithZone, + # ActiveSupport::Duration, Hash, ActiveSupport::HashWithIndifferentAccess, + # Array, Range, or GlobalID::Identification instances, although this can be + # extended by adding custom serializers. + # Raised if you set the key for a Hash something else than a string or + # a symbol. Also raised when trying to serialize an object which can't be + # identified with a GlobalID - such as an unpersisted Active Record model. + class SerializationError < ::ArgumentError + end + + module Arguments + extend ::ActiveJob::Arguments + + # Serializes a set of arguments. Intrinsic types that can safely be + # serialized without mutation are returned as-is. Arrays/Hashes are + # serialized element by element. All other types are serialized using + # GlobalID. + def serialize: (untyped arguments) -> untyped + + def serialize_argument: (untyped argument) -> untyped + + # Deserializes a set of arguments. Intrinsic types that can safely be + # deserialized without mutation are returned as-is. Arrays/Hashes are + # deserialized element by element. All other types are deserialized using + # GlobalID. + def deserialize: (untyped arguments) -> untyped + + private + + # :nodoc: + GLOBALID_KEY: "_aj_globalid" + + # :nodoc: + SYMBOL_KEYS_KEY: "_aj_symbol_keys" + + # :nodoc: + RUBY2_KEYWORDS_KEY: "_aj_ruby2_keywords" + + # :nodoc: + WITH_INDIFFERENT_ACCESS_KEY: "_aj_hash_with_indifferent_access" + + # :nodoc: + RESERVED_KEYS: untyped + + def deserialize_argument: (untyped argument) -> untyped + + def serialized_global_id?: (untyped hash) -> untyped + + def deserialize_global_id: (untyped hash) -> untyped + + def custom_serialized?: (untyped hash) -> untyped + + def serialize_hash: (untyped argument) -> untyped + + def deserialize_hash: (untyped serialized_hash) -> untyped + + def serialize_hash_key: (untyped key) -> untyped + + def serialize_indifferent_hash: (untyped indifferent_hash) -> untyped + + def transform_symbol_keys: (untyped hash, untyped symbol_keys) -> untyped + + def convert_to_global_id_hash: (untyped argument) -> untyped + end +end + +module ActiveJob + # = Active Job \Base + # + # Active Job objects can be configured to work with different backend + # queuing frameworks. To specify a queue adapter to use: + # + # ActiveJob::Base.queue_adapter = :inline + # + # A list of supported adapters can be found in QueueAdapters. + # + # Active Job objects can be defined by creating a class that inherits + # from the ActiveJob::Base class. The only necessary method to + # implement is the "perform" method. + # + # To define an Active Job object: + # + # class ProcessPhotoJob < ActiveJob::Base + # def perform(photo) + # photo.watermark!('Rails') + # photo.rotate!(90.degrees) + # photo.resize_to_fit!(300, 300) + # photo.upload! + # end + # end + # + # Records that are passed in are serialized/deserialized using Global + # ID. More information can be found in ActiveJob::Arguments. + # + # To enqueue a job to be performed as soon as the queuing system is free: + # + # ProcessPhotoJob.perform_later(photo) + # + # To enqueue a job to be processed at some point in the future: + # + # ProcessPhotoJob.set(wait_until: Date.tomorrow.noon).perform_later(photo) + # + # More information can be found in ActiveJob::Core::ClassMethods#set. + # + # A job can also be processed immediately without sending to the queue: + # + # ProcessPhotoJob.perform_now(photo) + # + # == Exceptions + # + # * DeserializationError - Error class for deserialization errors. + # * SerializationError - Error class for serialization errors. + class Base + include ::ActiveJob::Core + + include ::ActiveJob::QueueAdapter + + include ::ActiveJob::QueueName + + include ::ActiveJob::QueuePriority + + include ::ActiveJob::Enqueuing + + include ::ActiveJob::Execution + + include ::ActiveJob::Callbacks + + include ::ActiveJob::Exceptions + + include ::ActiveJob::Instrumentation + + include ::ActiveJob::Logging + + include ::ActiveJob::ExecutionState + end +end + +module ActiveJob + # = Active Job \Callbacks + # + # Active Job provides hooks during the life cycle of a job. Callbacks allow you + # to trigger logic during this cycle. Available callbacks are: + # + # * before_enqueue + # * around_enqueue + # * after_enqueue + # * before_perform + # * around_perform + # * after_perform + module Callbacks + extend ::ActiveSupport::Concern + + include ::ActiveSupport::Callbacks + + extend ::ActiveSupport::Callbacks + + # These methods will be included into any Active Job object, adding + # callbacks for +perform+ and +enqueue+ methods. + module ClassMethods + # Defines a callback that will get called right before the + # job's perform method is executed. + # + # class VideoProcessJob < ActiveJob::Base + # queue_as :default + # + # before_perform do |job| + # UserMailer.notify_video_started_processing(job.arguments.first) + # end + # + # def perform(video_id) + # Video.find(video_id).process + # end + # end + # + def before_perform: (*untyped filters) { (?) -> untyped } -> untyped + + # Defines a callback that will get called right after the + # job's perform method has finished. + # + # class VideoProcessJob < ActiveJob::Base + # queue_as :default + # + # after_perform do |job| + # UserMailer.notify_video_processed(job.arguments.first) + # end + # + # def perform(video_id) + # Video.find(video_id).process + # end + # end + # + def after_perform: (*untyped filters) { (?) -> untyped } -> untyped + + # Defines a callback that will get called around the job's perform method. + # + # class VideoProcessJob < ActiveJob::Base + # queue_as :default + # + # around_perform do |job, block| + # UserMailer.notify_video_started_processing(job.arguments.first) + # block.call + # UserMailer.notify_video_processed(job.arguments.first) + # end + # + # def perform(video_id) + # Video.find(video_id).process + # end + # end + # + # You can access the return value of the job only if the execution wasn't halted. + # + # class VideoProcessJob < ActiveJob::Base + # around_perform do |job, block| + # value = block.call + # puts value # => "Hello World!" + # end + # + # def perform + # "Hello World!" + # end + # end + # + def around_perform: (*untyped filters) { (?) -> untyped } -> untyped + + # Defines a callback that will get called right before the + # job is enqueued. + # + # class VideoProcessJob < ActiveJob::Base + # queue_as :default + # + # before_enqueue do |job| + # $statsd.increment "enqueue-video-job.try" + # end + # + # def perform(video_id) + # Video.find(video_id).process + # end + # end + # + def before_enqueue: (*untyped filters) { (?) -> untyped } -> untyped + + # Defines a callback that will get called right after the + # job is enqueued. + # + # class VideoProcessJob < ActiveJob::Base + # queue_as :default + # + # after_enqueue do |job| + # result = job.successfully_enqueued? ? "success" : "failure" + # $statsd.increment "enqueue-video-job.#{result}" + # end + # + # def perform(video_id) + # Video.find(video_id).process + # end + # end + # + def after_enqueue: (*untyped filters) { (?) -> untyped } -> untyped + + # Defines a callback that will get called around the enqueuing + # of the job. + # + # class VideoProcessJob < ActiveJob::Base + # queue_as :default + # + # around_enqueue do |job, block| + # $statsd.time "video-job.process" do + # block.call + # end + # end + # + # def perform(video_id) + # Video.find(video_id).process + # end + # end + # + def around_enqueue: (*untyped filters) { (?) -> untyped } -> untyped + end + end +end + +module ActiveJob + class ConfiguredJob + def initialize: (untyped job_class, ?::Hash[untyped, untyped] options) -> void + + def perform_now: (*untyped, **untyped) ?{ (?) -> untyped } -> untyped + + def perform_later: (*untyped, **untyped) ?{ (untyped) -> untyped } -> untyped + end +end + +module ActiveJob + # = Active Job Continuable + # + # The Continuable module provides the ability to track the progress of your + # jobs, and continue from where they left off if interrupted. + # + # Mix ActiveJob::Continuable into your job to enable continuations. + # + # See {ActiveJob::Continuation}[rdoc-ref:ActiveJob::Continuation] for usage. + # + module Continuable + def max_resumptions: () -> untyped + + def max_resumptions?: () -> bool + + def resume_options: () -> untyped + + def resume_options?: () -> bool + + def resume_errors_after_advancing: () -> untyped + + def resume_errors_after_advancing?: () -> bool + + extend ::ActiveSupport::Concern + + # The number of times the job has been resumed. + attr_accessor resumptions: untyped + + attr_accessor continuation: untyped + + # Start a new continuation step + def step: (untyped step_name, ?start: untyped?, ?isolated: bool) ?{ (?) -> untyped } -> untyped + + def serialize: () -> untyped + + def deserialize: (untyped job_data) -> untyped + + def checkpoint!: () -> (untyped | nil) + + def interrupt!: (reason: untyped) -> untyped + + private + + def continue: () { (?) -> untyped } -> untyped + + def resume_job: (untyped exception) -> untyped + end +end + +module ActiveJob + # = Active Job \Continuation + # + # Continuations provide a mechanism for interrupting and resuming jobs. This allows + # long-running jobs to make progress across application restarts. + # + # Jobs should include the ActiveJob::Continuable module to enable continuations. + # \Continuable jobs are automatically retried when interrupted. + # + # Use the +step+ method to define the steps in your job. Steps can use an optional + # cursor to track progress in the step. + # + # Steps are executed as soon as they are encountered. If a job is interrupted, previously + # completed steps will be skipped. If a step is in progress, it will be resumed + # with the last recorded cursor. + # + # Code that is not part of a step will be executed on each job run. + # + # You can pass a block or a method name to the step method. The block will be called with + # the step object as an argument. Methods can either take no arguments or a single argument + # for the step object. + # + # class ProcessImportJob < ApplicationJob + # include ActiveJob::Continuable + # + # def perform(import_id) + # # This always runs, even if the job is resumed. + # @import = Import.find(import_id) + # + # step :validate do + # @import.validate! + # end + # + # step(:process_records) do |step| + # @import.records.find_each(start: step.cursor) do |record| + # record.process + # step.advance! from: record.id + # end + # end + # + # step :reprocess_records + # step :finalize + # end + # + # def reprocess_records(step) + # @import.records.find_each(start: step.cursor) do |record| + # record.reprocess + # step.advance! from: record.id + # end + # end + # + # def finalize + # @import.finalize! + # end + # end + # + # === Cursors + # + # Cursors are used to track progress within a step. The cursor can be any object that is + # serializable as an argument to +ActiveJob::Base.serialize+. It defaults to +nil+. + # + # When a step is resumed, the last cursor value is restored. The code in the step is responsible + # for using the cursor to continue from the right point. + # + # +set!+ sets the cursor to a specific value. + # + # step :iterate_items do |step| + # items[step.cursor..].each do |item| + # process(item) + # step.set! (step.cursor || 0) + 1 + # end + # end + # + # An starting value for the cursor can be set when defining the step: + # + # step :iterate_items, start: 0 do |step| + # items[step.cursor..].each do |item| + # process(item) + # step.set! step.cursor + 1 + # end + # end + # + # The cursor can be advanced with +advance!+. This calls +succ+ on the current cursor value. + # It raises an ActiveJob::Continuation::UnadvanceableCursorError if the cursor does not implement +succ+. + # + # step :iterate_items, start: 0 do |step| + # items[step.cursor..].each do |item| + # process(item) + # step.advance! + # end + # end + # + # You can optionally pass a +from+ argument to +advance!+. This is useful when iterating + # over a collection of records where IDs may not be contiguous. + # + # step :process_records do |step| + # import.records.find_each(start: step.cursor) do |record| + # record.process + # step.advance! from: record.id + # end + # end + # + # You can use an array to iterate over nested records: + # + # step :process_nested_records, start: [ 0, 0 ] do |step| + # Account.find_each(start: step.cursor[0]) do |account| + # account.records.find_each(start: step.cursor[1]) do |record| + # record.process + # step.set! [ account.id, record.id + 1 ] + # end + # step.set! [ account.id + 1, 0 ] + # end + # end + # + # Setting or advancing the cursor creates a checkpoint. You can also create a checkpoint + # manually by calling the +checkpoint!+ method on the step. This is useful if you want to + # allow interruptions, but don't need to update the cursor. + # + # step :destroy_records do |step| + # import.records.find_each do |record| + # record.destroy! + # step.checkpoint! + # end + # end + # + # === Checkpoints + # + # A checkpoint is where a job can be interrupted. At a checkpoint the job will call + # +queue_adapter.stopping?+. If it returns true, the job will raise an + # ActiveJob::Continuation::Interrupt exception. + # + # There is an automatic checkpoint before the start of each step except for the first for + # each job execution. Within a step one is created when calling +set!+, +advance!+ or +checkpoint!+. + # + # Jobs are not automatically interrupted when the queue adapter is marked as stopping - they + # will continue to run either until the next checkpoint, or when the process is stopped. + # + # This is to allow jobs to be interrupted at a safe point, but it also means that the jobs + # should checkpoint more frequently than the shutdown timeout to ensure a graceful restart. + # + # When interrupted, the job will automatically retry with the progress serialized + # in the job data under the +continuation+ key. + # + # The serialized progress contains: + # - a list of the completed steps + # - the current step and its cursor value (if one is in progress) + # + # === Isolated Steps + # + # Steps run sequentially in a single job execution, unless the job is interrupted. + # + # You can specify that a step should always run in its own execution by passing the +isolated: true+ option. + # + # This is useful for long-running steps where it may not be possible to checkpoint within + # the job grace period - it ensures that progress is serialized back into the job data before + # the step starts. + # + # step :quick_step1 + # step :slow_step, isolated: true + # step :quick_step2 + # step :quick_step3 + # + # === Errors + # + # If a job raises an error and is not retried via Active Job, it will be passed back to the underlying + # queue backend and any progress in this execution will be lost. + # + # To mitigate this, the job will be automatically retried if it raises an error after it has made progress. + # Making progress is defined as having completed a step or advanced the cursor within the current step. + # + # === Configuration + # + # Continuable jobs have several configuration options: + # * :max_resumptions - The maximum number of times a job can be resumed. Defaults to +nil+ which means + # unlimited resumptions. + # * :resume_options - Options to pass to +retry_job+ when resuming the job. + # Defaults to { wait: 5.seconds }. + # See {ActiveJob::Exceptions#retry_job}[rdoc-ref:ActiveJob::Exceptions#retry_job] for available options. + # * :resume_errors_after_advancing - Whether to resume errors after advancing the continuation. + # Defaults to +true+. + class Continuation + extend ::ActiveSupport::Autoload + + # Raised when a job is interrupted, allowing Active Job to requeue it. + # This inherits from +Exception+ rather than +StandardError+, so it's not + # caught by normal exception handling. + class Interrupt < ::Exception + end + + # Base class for all Continuation errors. + class Error < ::StandardError + end + + # Raised when a step is invalid. + class InvalidStepError < ::ActiveJob::Continuation::Error + end + + # Raised when there is an error with a checkpoint, such as open database transactions. + class CheckpointError < ::ActiveJob::Continuation::Error + end + + # Raised when attempting to advance a cursor that doesn't implement `succ`. + class UnadvanceableCursorError < ::ActiveJob::Continuation::Error + end + + # Raised when a job has reached its limit of the number of resumes. + # The limit is defined by the +max_resumptions+ class attribute. + class ResumeLimitError < ::ActiveJob::Continuation::Error + end + + include ::ActiveJob::Continuation::Validation + + def initialize: (untyped job, untyped serialized_progress) -> void + + def step: (untyped name, **untyped options) { (?) -> untyped } -> untyped + + def to_h: () -> untyped + + def description: () -> (untyped | ::String | "not started") + + def started?: () -> untyped + + def advanced?: () -> untyped + + def instrumentation: () -> { description: untyped, completed_steps: untyped, current_step: untyped } + + private + + attr_reader job: untyped + + attr_reader encountered: untyped + + attr_reader completed: untyped + + attr_reader current: untyped + + def running_step?: () -> untyped + + def isolating?: () -> untyped + + def completed?: (untyped name) -> untyped + + def new_step: (*untyped args, **untyped options) -> untyped + + def skip_step: (untyped name) -> untyped + + def run_step: (untyped name, start: untyped, isolated: untyped) { (?) -> untyped } -> untyped + + def run_step_inline: (untyped name, start: untyped, **untyped options) { (?) -> untyped } -> untyped + + def instrumenting_step: (untyped step) { (?) -> untyped } -> untyped + + def instrument: (*untyped, **untyped) ?{ (?) -> untyped } -> untyped + end +end + +module ActiveJob + class Continuation + # = Active Job Continuation Step + # + # Represents a step within a continuable job. + # + # When a step is completed, it is recorded in the job's continuation state. + # If the job is interrupted, it will be resumed from after the last completed step. + # + # Steps also have an optional cursor that can be used to track progress within the step. + # If a job is interrupted during a step, the cursor will be saved and passed back when + # the job is resumed. + # + # It is the responsibility of the code in the step to use the cursor correctly to resume + # from where it left off. + class Step + # The name of the step. + attr_reader name: untyped + + # The cursor for the step. + attr_reader cursor: untyped + + def initialize: (untyped name, untyped cursor, job: untyped, resumed: untyped) -> void + + # Check if the job should be interrupted, and if so raise an Interrupt exception. + # The job will be requeued for retry. + def checkpoint!: () -> untyped + + # Set the cursor and interrupt the job if necessary. + def set!: (untyped cursor) -> untyped + + # Advance the cursor from the current or supplied value + # + # The cursor will be advanced by calling the +succ+ method on the cursor. + # An UnadvanceableCursorError error will be raised if the cursor does not implement +succ+. + def advance!: (?from: untyped?) -> untyped + + # Has this step been resumed from a previous job execution? + def resumed?: () -> untyped + + # Has the cursor been advanced during this job execution? + def advanced?: () -> untyped + + def to_a: () -> ::Array[untyped] + + def description: () -> ::String + + private + + attr_reader initial_cursor: untyped + + attr_reader job: untyped + end + end +end + +module ActiveJob + class Continuation + # Test helper for ActiveJob::Continuable jobs. + # + module TestHelper + include ::ActiveJob::TestHelper + + # Interrupt a job during a step. + # + # class MyJob < ApplicationJob + # include ActiveJob::Continuable + # + # cattr_accessor :items, default: [] + # def perform + # step :my_step, start: 1 do |step| + # (step.cursor..10).each do |i| + # items << i + # step.advance! + # end + # end + # end + # end + # + # test "interrupt job during step" do + # MyJob.perform_later + # interrupt_job_during_step(MyJob, :my_step, cursor: 6) { perform_enqueued_jobs } + # assert_equal [1, 2, 3, 4, 5], MyJob.items + # perform_enqueued_jobs + # assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], MyJob.items + # end + def interrupt_job_during_step: (untyped job, untyped step, ?cursor: untyped?) { (?) -> untyped } -> untyped + + # Interrupt a job after a step. + # + # Note that there's no checkpoint after the final step so it won't be interrupted. + # + # class MyJob < ApplicationJob + # include ActiveJob::Continuable + # + # cattr_accessor :items, default: [] + # + # def perform + # step(:step_one) { items << 1 } + # step(:step_two) { items << 2 } + # step(:step_three) { items << 3 } + # step(:step_four) { items << 4 } + # end + # end + # + # test "interrupt job after step" do + # MyJob.perform_later + # interrupt_job_after_step(MyJob, :step_two) { perform_enqueued_jobs } + # assert_equal [1, 2], MyJob.items + # perform_enqueued_jobs + # assert_equal [1, 2, 3, 4], MyJob.items + # end + def interrupt_job_after_step: (untyped job, untyped step) { (?) -> untyped } -> untyped + + private + + def continuation_for: (untyped klass) -> untyped + + def during_step?: (untyped job, untyped step, ?cursor: untyped?) -> (untyped | nil) + + def after_step?: (untyped job, untyped step) -> (untyped | nil) + end + end +end + +module ActiveJob + class Continuation + module Validation + private + + def validate_step!: (untyped name) -> untyped + + def validate_step_symbol!: (untyped name) -> (untyped | nil) + + def validate_step_not_encountered!: (untyped name) -> (untyped | nil) + + def validate_step_not_nested!: (untyped name) -> (untyped | nil) + + def validate_step_resume_expected!: (untyped name) -> (untyped | nil) + + def validate_step_expected_order!: (untyped name) -> (untyped | nil) + + def raise_step_error!: (untyped message) -> untyped + end + end +end + +module ActiveJob + # Raised during job payload deserialization when it references an uninitialized job class. + class UnknownJobClassError < ::NameError[untyped] + def initialize: (untyped job_class_name) -> void + end + + # = Active Job \Core + # + # Provides general behavior that will be included into every Active Job + # object that inherits from ActiveJob::Base. + module Core + extend ::ActiveSupport::Concern + + # Job arguments + attr_accessor arguments: untyped + + attr_writer serialized_arguments: untyped + + # Time when the job should be performed + attr_accessor scheduled_at: untyped + + # Job Identifier + attr_accessor job_id: untyped + + # Queue in which the job will reside. + attr_writer queue_name: untyped + + # Priority that the job will have (lower is more priority). + attr_writer priority: untyped + + # ID optionally provided by adapter + attr_accessor provider_job_id: untyped + + # Number of times this job has been executed (which increments on every retry, like after an exception). + attr_accessor executions: untyped + + # Hash that contains the number of times this job handled errors for each specific retry_on declaration. + # Keys are the string representation of the exceptions listed in the retry_on declaration, + # while its associated value holds the number of executions where the corresponding retry_on + # declaration handled one of its listed exceptions. + attr_accessor exception_executions: untyped + + # I18n.locale to be used during the job. + attr_accessor locale: untyped + + # Timezone to be used during the job. + attr_accessor timezone: untyped + + # Track when a job was enqueued + attr_accessor enqueued_at: untyped + + # Track whether the adapter received the job successfully. + attr_writer successfully_enqueued: untyped + + def successfully_enqueued?: () -> untyped + + # Track any exceptions raised by the backend so callers can inspect the errors. + attr_accessor enqueue_error: untyped + + # These methods will be included into any Active Job object, adding + # helpers for de/serialization and creation of job instances. + module ClassMethods + # Creates a new job instance from a hash created with +serialize+ + def deserialize: (untyped job_data) -> untyped + + # Creates a job preconfigured with the given options. You can call + # perform_later with the job arguments to enqueue the job with the + # preconfigured options + # + # ==== Options + # * :wait - Enqueues the job with the specified delay + # * :wait_until - Enqueues the job at the time specified + # * :queue - Enqueues the job on the specified queue + # * :priority - Enqueues the job with the specified priority + # + # ==== Examples + # + # VideoJob.set(queue: :some_queue).perform_later(Video.last) + # VideoJob.set(wait: 5.minutes).perform_later(Video.last) + # VideoJob.set(wait_until: Time.now.tomorrow).perform_later(Video.last) + # VideoJob.set(queue: :some_queue, wait: 5.minutes).perform_later(Video.last) + # VideoJob.set(queue: :some_queue, wait_until: Time.now.tomorrow).perform_later(Video.last) + # VideoJob.set(queue: :some_queue, wait: 5.minutes, priority: 10).perform_later(Video.last) + def set: (?::Hash[untyped, untyped] options) -> ConfiguredJob + end + + # Creates a new job instance. Takes the arguments that will be + # passed to the perform method. + def initialize: (*untyped arguments) -> void + + # Returns a hash with the job data that can safely be passed to the + # queuing adapter. + def serialize: () -> ::Hash[::String, untyped] + + # Attaches the stored job data to the current instance. Receives a hash + # returned from +serialize+ + # + # ==== Examples + # + # class DeliverWebhookJob < ActiveJob::Base + # attr_writer :attempt_number + # + # def attempt_number + # @attempt_number ||= 0 + # end + # + # def serialize + # super.merge('attempt_number' => attempt_number + 1) + # end + # + # def deserialize(job_data) + # super + # self.attempt_number = job_data['attempt_number'] + # end + # + # rescue_from(Timeout::Error) do |exception| + # raise exception if attempt_number > 5 + # retry_job(wait: 10) + # end + # end + def deserialize: (untyped job_data) -> untyped + + # Configures the job with the given options. + def set: (?::Hash[untyped, untyped] options) -> self + + private + + def serialize_arguments_if_needed: (untyped arguments) -> untyped + + def deserialize_arguments_if_needed: () -> (untyped | nil) + + def serialize_arguments: (untyped arguments) -> untyped + + def deserialize_arguments: (untyped serialized_args) -> untyped + + def arguments_serialized?: () -> untyped + + def deserialize_time: (untyped time) -> untyped + end +end + +module ActiveJob + def self.deprecator: () -> untyped +end + +module ActiveJob + module EnqueueAfterTransactionCommit + def self.included: (untyped base) -> untyped + + module ActiveJobMethods + # Ensures perform_all_later respects each job's enqueue_after_transaction_commit configuration. + # Jobs with enqueue_after_transaction_commit set to true are deferred and enqueued only after the transaction commits; + # other jobs are enqueued immediately. This ensures enqueuing timing matches the per-job setting. + def perform_all_later: (*untyped jobs) -> nil + end + + private + + def raw_enqueue: () -> (self | untyped) + end +end + +module ActiveJob + # Can be raised by adapters if they wish to communicate to the caller a reason + # why the adapter was unexpectedly unable to enqueue a job. + class EnqueueError < ::StandardError + end + + module Enqueuing + extend ::ActiveSupport::Concern + + # Includes the +perform_later+ method for job initialization. + module ClassMethods + def enqueue_after_transaction_commit: () -> untyped + + def enqueue_after_transaction_commit=: (untyped value) -> untyped + + # Push a job onto the queue. By default the arguments must be either String, + # Integer, Float, NilClass, TrueClass, FalseClass, BigDecimal, Symbol, Date, + # Time, DateTime, ActiveSupport::TimeWithZone, ActiveSupport::Duration, + # Hash, ActiveSupport::HashWithIndifferentAccess, Array, Range, or + # GlobalID::Identification instances, although this can be extended by adding + # custom serializers. + # + # Returns an instance of the job class queued with arguments available in + # Job#arguments or +false+ if the enqueue did not succeed. + # + # After the attempted enqueue, the job will be yielded to an optional block. + # + # If Active Job is used conjointly with Active Record, and #perform_later is called + # inside an Active Record transaction, then the enqueue is implicitly deferred to after + # the transaction is committed, or dropped if it's rolled back. In such case #perform_later + # will return the job instance like if it was successfully enqueued, but will still return + # +false+ if a callback prevented the job from being enqueued. + # + # This behavior can be changed on a per job basis: + # + # class NotificationJob < ApplicationJob + # self.enqueue_after_transaction_commit = false + # end + def perform_later: (*untyped, **untyped) ?{ (untyped) -> untyped } -> untyped + + private + + def job_or_instantiate: (*untyped args) { (?) -> untyped } -> untyped + end + + # Enqueues the job to be performed by the queue adapter. + # + # ==== Options + # * :wait - Enqueues the job with the specified delay + # * :wait_until - Enqueues the job at the time specified + # * :queue - Enqueues the job on the specified queue + # * :priority - Enqueues the job with the specified priority + # + # ==== Examples + # + # my_job_instance.enqueue + # my_job_instance.enqueue wait: 5.minutes + # my_job_instance.enqueue queue: :important + # my_job_instance.enqueue wait_until: Date.tomorrow.midnight + # my_job_instance.enqueue priority: 10 + def enqueue: (?::Hash[untyped, untyped] options) -> untyped + + private + + def raw_enqueue: () -> untyped + + def _raw_enqueue: () -> untyped + end +end + +module ActiveJob + # Provides behavior for retrying and discarding jobs on exceptions. + module Exceptions + def after_discard_procs: () -> untyped + + def after_discard_procs?: () -> bool + + def after_discard_procs=: (untyped value) -> untyped + + extend ::ActiveSupport::Concern + + module ClassMethods + def retry_jitter: () -> untyped + + def retry_jitter=: (untyped value) -> untyped + + def after_discard_procs: () -> untyped + + def after_discard_procs=: (untyped value) -> untyped + + def after_discard_procs?: () -> bool + + # Catch the exception and reschedule job for re-execution after so many seconds, for a specific number of attempts. + # If the exception keeps getting raised beyond the specified number of attempts, the exception is allowed to + # bubble up to the underlying queuing system, which may have its own retry mechanism or place it in a + # holding queue for inspection. + # + # You can also pass a block that'll be invoked if the retry attempts fail for custom logic rather than letting + # the exception bubble up. This block is yielded with the job instance as the first and the error instance as the second parameter. + # + # +retry_on+ and +discard_on+ handlers are searched from bottom to top, and up the class hierarchy. The handler of the first class for + # which exception.is_a?(klass) holds true is the one invoked, if any. + # + # ==== Options + # * :wait - Re-enqueues the job with a delay specified either in seconds (default: 3 seconds), + # as a computing proc that takes the number of executions so far as an argument, or as a symbol reference of + # :polynomially_longer, which applies the wait algorithm of ((executions**4) + (Kernel.rand * (executions**4) * jitter)) + 2 + # (first wait ~3s, then ~18s, then ~83s, etc) + # * :attempts - Enqueues the job the specified number of times (default: 5 attempts) or a symbol reference of :unlimited + # to retry the job until it succeeds. The number of attempts includes the original job execution. + # * :queue - Re-enqueues the job on a different queue + # * :priority - Re-enqueues the job with a different priority + # * :jitter - A random delay of wait time used when calculating backoff. The default is 15% (0.15) which represents the upper bound of possible wait time (expressed as a percentage) + # * :report - Errors will be reported to the Rails.error reporter before being retried + # + # ==== Examples + # + # class RemoteServiceJob < ActiveJob::Base + # retry_on CustomAppException # defaults to ~3s wait, 5 attempts + # retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 } + # retry_on CustomInfrastructureException, wait: 5.minutes, attempts: :unlimited + # + # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3 + # retry_on Net::OpenTimeout, Timeout::Error, wait: :polynomially_longer, attempts: 10 # retries at most 10 times for Net::OpenTimeout and Timeout::Error combined + # # To retry at most 10 times for each individual exception: + # # retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 10 + # # retry_on Net::ReadTimeout, wait: 5.seconds, jitter: 0.30, attempts: 10 + # # retry_on Timeout::Error, wait: :polynomially_longer, attempts: 10 + # + # retry_on YetAnotherCustomAppException, report: true + # retry_on EvenWorseCustomAppException do |job, error| + # CustomErrorHandlingCode.handle(job, error) + # end + # + # def perform(*args) + # # Might raise CustomAppException, AnotherCustomAppException, or YetAnotherCustomAppException for something domain specific + # # Might raise ActiveRecord::Deadlocked when a local db deadlock is detected + # # Might raise Net::OpenTimeout or Timeout::Error when the remote service is down + # end + # end + def retry_on: (*untyped exceptions, ?wait: untyped, ?attempts: ::Integer, ?queue: untyped?, ?priority: untyped?, ?jitter: untyped, ?report: bool) ?{ (untyped, untyped) -> untyped } -> untyped + + # Discard the job with no attempts to retry, if the exception is raised. This is useful when the subject of the job, + # like an Active Record, is no longer available, and the job is thus no longer relevant. + # + # Passing the :report option reports the error through the error reporter before discarding the job. + # + # You can also pass a block that'll be invoked. This block is yielded with the job instance as the first and the error instance as the second parameter. + # + # +retry_on+ and +discard_on+ handlers are searched from bottom to top, and up the class hierarchy. The handler of the first class for + # which exception.is_a?(klass) holds true is the one invoked, if any. + # + # ==== Example + # + # class SearchIndexingJob < ActiveJob::Base + # discard_on ActiveJob::DeserializationError + # discard_on CustomAppException, report: true + # discard_on(AnotherCustomAppException) do |job, error| + # CustomErrorHandlingCode.handle(job, error) + # end + # + # def perform(record) + # # Will raise ActiveJob::DeserializationError if the record can't be deserialized + # # Might raise CustomAppException for something domain specific + # end + # end + def discard_on: (*untyped exceptions, ?report: bool) ?{ (untyped, untyped) -> untyped } -> untyped + + # A block to run when a job is about to be discarded for any reason. + # + # ==== Example + # + # class WorkJob < ActiveJob::Base + # after_discard do |job, exception| + # ExceptionNotifier.report(exception) + # end + # + # ... + # + # end + def after_discard: () { (?) -> untyped } -> untyped + end + + # Reschedules the job to be re-executed. This is useful in combination with + # {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from]. + # When you rescue an exception from your job you can ask Active Job to retry + # performing your job. + # + # ==== Options + # * :wait - Enqueues the job with the specified delay in seconds + # * :wait_until - Enqueues the job at the time specified + # * :queue - Enqueues the job on the specified queue + # * :priority - Enqueues the job with the specified priority + # + # ==== Examples + # + # class SiteScraperJob < ActiveJob::Base + # rescue_from(ErrorLoadingSite) do + # retry_job queue: :low_priority + # end + # + # def perform(*args) + # # raise ErrorLoadingSite if cannot scrape + # end + # end + def retry_job: (?::Hash[untyped, untyped] options) -> untyped + + private + + JITTER_DEFAULT: untyped + + def determine_delay: (seconds_or_duration_or_algorithm: untyped, executions: untyped, ?jitter: untyped) -> untyped + + def determine_jitter_for_delay: (untyped delay, untyped jitter) -> (::Float | untyped) + + def executions_for: (untyped exceptions) -> untyped + + def run_after_discard_procs: (untyped exception) -> untyped + end +end + +module ActiveJob + module ExecutionState + def perform_now: () -> untyped + end +end + +module ActiveJob + # = Active Job \Execution + # + # Provides methods to execute jobs immediately, and wraps job execution so + # that exceptions configured with + # {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from] + # are handled. + module Execution + extend ::ActiveSupport::Concern + + include ::ActiveSupport::Rescuable + + # Includes methods for executing and performing jobs instantly. + module ClassMethods + # Performs the job immediately. + # + # MyJob.perform_now("mike") + # + def perform_now: (*untyped, **untyped) ?{ (?) -> untyped } -> untyped + + def execute: (untyped job_data) -> untyped + end + + # Performs the job immediately. The job is not sent to the queuing adapter + # but directly executed by blocking the execution of others until it's finished. + # +perform_now+ returns the value of your job's +perform+ method. + # + # class MyJob < ActiveJob::Base + # def perform + # "Hello World!" + # end + # end + # + # puts MyJob.new(*args).perform_now # => "Hello World!" + def perform_now: () -> untyped + + def perform: (*untyped) -> untyped + + private + + def _perform_job: () -> untyped + end +end + +module ActiveJob + # Returns the currently loaded version of Active Job as a +Gem::Version+. + def self.gem_version: () -> Gem::Version + + module VERSION + MAJOR: 8 + + MINOR: 1 + + TINY: 4 + + PRE: nil + + STRING: untyped + end +end + +module ActiveJob + def self.instrument_enqueue_all: (untyped queue_adapter, untyped jobs) { (untyped) -> untyped } -> untyped + + module Instrumentation + extend ::ActiveSupport::Concern + + def perform_now: () -> untyped + + def instrument: (untyped operation, ?::Hash[untyped, untyped] payload) { (?) -> untyped } -> untyped + + private + + def _perform_job: () -> untyped + + def halted_callback_hook: (*untyped) -> untyped + end +end + +module ActiveJob + class LogSubscriber < ::ActiveSupport::LogSubscriber + def self.backtrace_cleaner: () -> untyped + def self.backtrace_cleaner=: (untyped value) -> untyped + def self.backtrace_cleaner?: () -> bool + def backtrace_cleaner: () -> untyped + def backtrace_cleaner?: () -> bool + def backtrace_cleaner=: (untyped value) -> untyped + + def enqueue: (untyped event) -> untyped + + def enqueue_at: (untyped event) -> untyped + + def enqueue_all: (untyped event) -> untyped + + def perform_start: (untyped event) -> untyped + + def perform: (untyped event) -> untyped + + def enqueue_retry: (untyped event) -> untyped + + def retry_stopped: (untyped event) -> untyped + + def discard: (untyped event) -> untyped + + def interrupt: (untyped event) -> untyped + + def resume: (untyped event) -> untyped + + def step_skipped: (untyped event) -> untyped + + def step_started: (untyped event) -> untyped + + def step: (untyped event) -> untyped + + private + + def queue_name: (untyped event) -> untyped + + def args_info: (untyped job) -> (untyped | "") + + def format: (untyped arg) -> untyped + + def scheduled_at: (untyped event) -> untyped + + def logger: () -> untyped + + def info: (?untyped? progname) { (?) -> untyped } -> (nil | untyped) + + def error: (?untyped? progname) { (?) -> untyped } -> (nil | untyped) + + def log_enqueue_source: () -> untyped + + def enqueue_source_location: () -> untyped + + def enqueued_jobs_message: (untyped adapter, untyped enqueued_jobs) -> ::String + end +end + +module ActiveJob + module Logging + def logger: () -> untyped + + def logger=: (untyped value) -> untyped + + extend ::ActiveSupport::Concern + + def perform_now: () -> untyped + + private + + def tag_logger: (*untyped tags) { () -> untyped } -> untyped + + def logger_tagged_by_active_job?: () -> untyped + end +end + +module ActiveJob + def self.adapter_name: (untyped adapter) -> (untyped | ::String) + + # = Active Job Queue adapter + # + # The +ActiveJob::QueueAdapter+ module is used to load the + # correct adapter. The default queue adapter is +:async+, + # which loads the ActiveJob::QueueAdapters::AsyncAdapter. + module QueueAdapter + def queue_adapter: (*untyped, **untyped) ?{ (*untyped) -> untyped } -> untyped + + extend ::ActiveSupport::Concern + + # Includes the setter method for changing the active queue adapter. + module ClassMethods + def _queue_adapter_name: () -> untyped + + def _queue_adapter_name=: (untyped value) -> untyped + + def _queue_adapter: () -> untyped + + def _queue_adapter=: (untyped value) -> untyped + + # Returns the backend queue provider. The default queue adapter + # is +:async+. See QueueAdapters for more information. + def queue_adapter: () -> untyped + + # Returns string denoting the name of the configured queue adapter. + # By default returns "async". + def queue_adapter_name: () -> untyped + + # Specify the backend queue provider. The default queue adapter + # is the +:async+ queue. See QueueAdapters for more + # information. + def queue_adapter=: (untyped name_or_adapter) -> untyped + + private + + def assign_adapter: (untyped adapter_name, untyped queue_adapter) -> untyped + + QUEUE_ADAPTER_METHODS: ::Array[:enqueue | :enqueue_at] + + def queue_adapter?: (untyped object) -> untyped + end + end +end + +module ActiveJob + # = Active Job adapters + # + # Active Job has adapters for the following queuing backends: + # + # * {Backburner}[https://github.com/nesquena/backburner] + # * {Delayed Job}[https://github.com/collectiveidea/delayed_job] + # * {Que}[https://github.com/chanks/que] + # * {queue_classic}[https://github.com/QueueClassic/queue_classic] + # * {Resque}[https://github.com/resque/resque] + # * {Sidekiq}[https://sidekiq.org] + # * {Sneakers}[https://github.com/jondot/sneakers] + # * Please Note: We are not accepting pull requests for new adapters. See the {README}[link:files/activejob/README_md.html] for more details. + # + # For testing and development Active Job has three built-in adapters: + # + # * {Active Job Async}[https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html] + # * {Active Job Inline}[https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/InlineAdapter.html] + # * {Active Job Test}[https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/TestAdapter.html] + # + # === Backends Features + # + # | | Async | Queues | Delayed | Priorities | Timeout | Retries | + # |-------------------|-------|--------|------------|------------|---------|---------| + # | Backburner | Yes | Yes | Yes | Yes | Job | Global | + # | Delayed Job | Yes | Yes | Yes | Job | Global | Global | + # | Que | Yes | Yes | Yes | Job | No | Job | + # | queue_classic | Yes | Yes | Yes* | No | No | No | + # | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes | + # | Sidekiq | Yes | Yes | Yes | Queue | No | Job | + # | Sneakers | Yes | Yes | No | Queue | Queue | No | + # | Active Job Async | Yes | Yes | Yes | No | No | No | + # | Active Job Inline | No | Yes | N/A | N/A | N/A | N/A | + # | Active Job Test | No | Yes | N/A | N/A | N/A | N/A | + # + # ==== Async + # + # Yes: The Queue Adapter has the ability to run the job in a non-blocking manner. + # It either runs on a separate or forked process, or on a different thread. + # + # No: The job is run in the same process. + # + # ==== Queues + # + # Yes: Jobs may set which queue they are run in with queue_as or by using the set + # method. + # + # ==== Delayed + # + # Yes: The adapter will run the job in the future through perform_later. + # + # (Gem): An additional gem is required to use perform_later with this adapter. + # + # No: The adapter will run jobs at the next opportunity and cannot use perform_later. + # + # N/A: The adapter does not support queuing. + # + # NOTE: + # queue_classic supports job scheduling since version 3.1. + # For older versions you can use the queue_classic-later gem. + # + # ==== Priorities + # + # The order in which jobs are processed can be configured differently depending + # on the adapter. + # + # Job: Any class inheriting from the adapter may set the priority on the job + # object relative to other jobs. + # + # Queue: The adapter can set the priority for job queues, when setting a queue + # with Active Job this will be respected. + # + # Yes: Allows the priority to be set on the job object, at the queue level or + # as default configuration option. + # + # No: The adapter does not allow the priority of jobs to be configured. + # + # N/A: The adapter does not support queuing, and therefore sorting them. + # + # ==== Timeout + # + # When a job will stop after the allotted time. + # + # Job: The timeout can be set for each instance of the job class. + # + # Queue: The timeout is set for all jobs on the queue. + # + # Global: The adapter is configured that all jobs have a maximum run time. + # + # No: The adapter does not allow the timeout of jobs to be configured. + # + # N/A: This adapter does not run in a separate process, and therefore timeout + # is unsupported. + # + # ==== Retries + # + # Job: The number of retries can be set per instance of the job class. + # + # Yes: The Number of retries can be configured globally, for each instance or + # on the queue. This adapter may also present failed instances of the job class + # that can be restarted. + # + # Global: The adapter has a global number of retries. + # + # No: The adapter does not allow the number of retries to be configured. + # + # N/A: The adapter does not run in a separate process, and therefore doesn't + # support retries. + # + module QueueAdapters + extend ::ActiveSupport::Autoload + + ADAPTER: "Adapter" + + # Returns adapter for specified name. + # + # ActiveJob::QueueAdapters.lookup(:sidekiq) + # # => ActiveJob::QueueAdapters::SidekiqAdapter + def self.lookup: (untyped name) -> untyped + end +end + +module ActiveJob + module QueueAdapters + # = Active Job Abstract Adapter + # + # Active Job supports multiple job queue systems. ActiveJob::QueueAdapters::AbstractAdapter + # forms the abstraction layer which makes this possible. + class AbstractAdapter + attr_accessor stopping: untyped + + def enqueue: (untyped job) -> untyped + + def enqueue_at: (untyped job, untyped timestamp) -> untyped + + def stopping?: () -> untyped + end + end +end + +module ActiveJob + module QueueAdapters + # = Active Job Async adapter + # + # The Async adapter runs jobs with an in-process thread pool. + # + # This is the default queue adapter. It's well-suited for dev/test since + # it doesn't need an external infrastructure, but it's a poor fit for + # production since it drops pending jobs on restart. + # + # To use this adapter, set queue adapter to +:async+: + # + # config.active_job.queue_adapter = :async + # + # To configure the adapter's thread pool, instantiate the adapter and + # pass your own config: + # + # config.active_job.queue_adapter = ActiveJob::QueueAdapters::AsyncAdapter.new \ + # min_threads: 1, + # max_threads: 2 * Concurrent.processor_count, + # idletime: 600.seconds + # + # The adapter uses a {Concurrent Ruby}[https://github.com/ruby-concurrency/concurrent-ruby] thread pool to schedule and execute + # jobs. Since jobs share a single thread pool, long-running jobs will block + # short-lived jobs. Fine for dev/test; bad for production. + class AsyncAdapter < ::ActiveJob::QueueAdapters::AbstractAdapter + # See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ThreadPoolExecutor.html] for executor options. + def initialize: (**untyped executor_options) -> void + + def enqueue: (untyped job) -> untyped + + def enqueue_at: (untyped job, untyped timestamp) -> untyped + + # Gracefully stop processing jobs. Finishes in-progress work and handles + # any new jobs following the executor's fallback policy (`caller_runs`). + # Waits for termination by default. Pass `wait: false` to continue. + def shutdown: (?wait: bool) -> untyped + + # Used for our test suite. + def immediate=: (untyped immediate) -> untyped + + # Note that we don't actually need to serialize the jobs since we're + # performing them in-process, but we do so anyway for parity with other + # adapters and deployment environments. Otherwise, serialization bugs + # may creep in undetected. + class JobWrapper + def initialize: (untyped job) -> void + + def perform: () -> untyped + end + + class Scheduler + DEFAULT_EXECUTOR_OPTIONS: { min_threads: 0, max_threads: untyped, auto_terminate: true, idletime: 60, max_queue: 0, fallback_policy: :caller_runs } + + attr_accessor immediate: untyped + + def initialize: (**untyped options) -> void + + def enqueue: (untyped job, queue_name: untyped) -> untyped + + def enqueue_at: (untyped job, untyped timestamp, queue_name: untyped) -> untyped + + def shutdown: (?wait: bool) -> untyped + + def executor: () -> untyped + end + end + end +end + +module ActiveJob + module QueueAdapters + # = Backburner adapter for Active Job + # + # Backburner is a beanstalkd-powered job queue that can handle a very + # high volume of jobs. You create background jobs and place them on + # multiple work queues to be processed later. Read more about + # Backburner {here}[https://github.com/nesquena/backburner]. + # + # To use Backburner set the queue_adapter config to +:backburner+. + # + # Rails.application.config.active_job.queue_adapter = :backburner + class BackburnerAdapter < ::ActiveJob::QueueAdapters::AbstractAdapter + def enqueue: (untyped job) -> untyped + + def enqueue_at: (untyped job, untyped timestamp) -> untyped + + class JobWrapper + def self.perform: (untyped job_data) -> untyped + end + end + end +end + +module ActiveJob + module QueueAdapters + # = Delayed Job adapter for Active Job + # + # Delayed::Job (or DJ) encapsulates the common pattern of asynchronously + # executing longer tasks in the background. Although DJ can have many + # storage backends, one of the most used is based on Active Record. + # Read more about Delayed Job {here}[https://github.com/collectiveidea/delayed_job]. + # + # To use Delayed Job, set the queue_adapter config to +:delayed_job+. + # + # Rails.application.config.active_job.queue_adapter = :delayed_job + class DelayedJobAdapter < ::ActiveJob::QueueAdapters::AbstractAdapter + def enqueue: (untyped job) -> untyped + + def enqueue_at: (untyped job, untyped timestamp) -> untyped + + class JobWrapper + attr_accessor job_data: untyped + + def initialize: (untyped job_data) -> void + + def display_name: () -> ::String + + def perform: () -> untyped + + private + + def log_arguments?: () -> untyped + end + end + end +end + +module ActiveJob + module QueueAdapters + # = Active Job Inline adapter + # + # When enqueuing jobs with the Inline adapter the job will be executed + # immediately. + # + # To use the Inline set the queue_adapter config to +:inline+. + # + # Rails.application.config.active_job.queue_adapter = :inline + class InlineAdapter < ::ActiveJob::QueueAdapters::AbstractAdapter + def enqueue: (untyped job) -> untyped + + def enqueue_at: (*untyped) -> untyped + end + end +end + +module ActiveJob + module QueueAdapters + # = queue_classic adapter for Active Job + # + # queue_classic provides a simple interface to a PostgreSQL-backed message + # queue. queue_classic specializes in concurrent locking and minimizing + # database load while providing a simple, intuitive developer experience. + # queue_classic assumes that you are already using PostgreSQL in your + # production environment and that adding another dependency (e.g. redis, + # beanstalkd, 0mq) is undesirable. + # + # Read more about queue_classic {here}[https://github.com/QueueClassic/queue_classic]. + # + # To use queue_classic set the queue_adapter config to +:queue_classic+. + # + # Rails.application.config.active_job.queue_adapter = :queue_classic + class QueueClassicAdapter < ::ActiveJob::QueueAdapters::AbstractAdapter + def enqueue: (untyped job) -> untyped + + def enqueue_at: (untyped job, untyped timestamp) -> untyped + + # Builds a +QC::Queue+ object to schedule jobs on. + # + # If you have a custom +QC::Queue+ subclass you'll need to subclass + # +ActiveJob::QueueAdapters::QueueClassicAdapter+ and override the + # build_queue method. + def build_queue: (untyped queue_name) -> QC::Queue + + class JobWrapper + def self.perform: (untyped job_data) -> untyped + end + end + end +end + +module ActiveJob + module QueueAdapters + # = Resque adapter for Active Job + # + # Resque (pronounced like "rescue") is a Redis-backed library for creating + # background jobs, placing those jobs on multiple queues, and processing + # them later. + # + # Read more about Resque {here}[https://github.com/resque/resque]. + # + # To use Resque set the queue_adapter config to +:resque+. + # + # Rails.application.config.active_job.queue_adapter = :resque + class ResqueAdapter < ::ActiveJob::QueueAdapters::AbstractAdapter + def enqueue: (untyped job) -> untyped + + def enqueue_at: (untyped job, untyped timestamp) -> untyped + + class JobWrapper + def self.perform: (untyped job_data) -> untyped + end + end + end +end + +module ActiveJob + module QueueAdapters + # = Sidekiq adapter for Active Job + # + # Simple, efficient background processing for Ruby. Sidekiq uses threads to + # handle many jobs at the same time in the same process. It does not + # require \Rails but will integrate tightly with it to make background + # processing dead simple. + # + # Read more about Sidekiq {here}[http://sidekiq.org]. + # + # To use Sidekiq set the queue_adapter config to +:sidekiq+. + # + # Rails.application.config.active_job.queue_adapter = :sidekiq + class SidekiqAdapter < ::ActiveJob::QueueAdapters::AbstractAdapter + def initialize: (*untyped) -> void + + def check_adapter: () -> untyped + + def enqueue_all: (untyped jobs) -> untyped + + class JobWrapper + end + end + end +end + +module ActiveJob + module QueueAdapters + # = Sneakers adapter for Active Job + # + # A high-performance RabbitMQ background processing framework for Ruby. + # Sneakers is being used in production for both I/O and CPU intensive + # workloads, and have achieved the goals of high-performance and + # 0-maintenance, as designed. + # + # Read more about Sneakers {here}[https://github.com/jondot/sneakers]. + # + # To use Sneakers set the queue_adapter config to +:sneakers+. + # + # Rails.application.config.active_job.queue_adapter = :sneakers + class SneakersAdapter < ::ActiveJob::QueueAdapters::AbstractAdapter + def initialize: () -> void + + def enqueue: (untyped job) -> untyped + + def enqueue_at: (untyped job, untyped timestamp) -> untyped + + class JobWrapper + include ::Sneakers::Worker + + def work: (untyped msg) -> untyped + end + end + end +end + +module ActiveJob + module QueueAdapters + # = Test adapter for Active Job + # + # The test adapter should be used only in testing. Along with + # ActiveJob::TestCase and ActiveJob::TestHelper + # it makes a great tool to test your \Rails application. + # + # To use the test adapter set +queue_adapter+ config to +:test+. + # + # Rails.application.config.active_job.queue_adapter = :test + class TestAdapter < ::ActiveJob::QueueAdapters::AbstractAdapter + attr_accessor perform_enqueued_jobs: untyped + + attr_accessor perform_enqueued_at_jobs: untyped + + attr_accessor filter: untyped + + attr_accessor reject: untyped + + attr_accessor queue: untyped + + attr_accessor at: untyped + + attr_accessor stopping: untyped + + attr_writer enqueued_jobs: untyped + + attr_writer performed_jobs: untyped + + # Provides a store of all the enqueued jobs with the TestAdapter so you can check them. + def enqueued_jobs: () -> untyped + + # Provides a store of all the performed jobs with the TestAdapter so you can check them. + def performed_jobs: () -> untyped + + def enqueue: (untyped job) -> untyped + + def enqueue_at: (untyped job, untyped timestamp) -> untyped + + def stopping?: () -> untyped + + private + + def job_to_hash: (untyped job, ?::Hash[untyped, untyped] extras) -> untyped + + def perform_or_enqueue: (untyped perform, untyped job, untyped job_data) -> untyped + + def filtered?: (untyped job) -> untyped + + def filtered_time?: (untyped job) -> (untyped | nil) + + def filtered_queue?: (untyped job) -> (untyped | nil) + + def filtered_job_class?: (untyped job) -> untyped + + def filter_as_proc: (untyped filter) -> untyped + end + end +end + +module ActiveJob + module QueueName + def queue_name_prefix: () -> untyped + + def queue_name_prefix?: () -> bool + + def queue_name_prefix=: (untyped value) -> untyped + + extend ::ActiveSupport::Concern + + # Includes the ability to override the default queue name and prefix. + module ClassMethods + def self.default_queue_name: () -> untyped + + def self.default_queue_name=: (untyped value) -> untyped + + def default_queue_name: () -> untyped + + def default_queue_name=: (untyped value) -> untyped + + def queue_name: () -> untyped + + def queue_name=: (untyped value) -> untyped + + def queue_name?: () -> bool + + def queue_name_delimiter: () -> untyped + + def queue_name_delimiter=: (untyped value) -> untyped + + def queue_name_delimiter?: () -> bool + + def queue_name_prefix: () -> untyped + + def queue_name_prefix=: (untyped value) -> untyped + + def queue_name_prefix?: () -> bool + + # Specifies the name of the queue to process the job on. + # + # class PublishToFeedJob < ActiveJob::Base + # queue_as :feeds + # + # def perform(post) + # post.to_feed! + # end + # end + # + # Can be given a block that will evaluate in the context of the job + # so that a dynamic queue name can be applied: + # + # class PublishToFeedJob < ApplicationJob + # queue_as do + # post = self.arguments.first + # + # if post.paid? + # :paid_feeds + # else + # :feeds + # end + # end + # + # def perform(post) + # post.to_feed! + # end + # end + def queue_as: (?untyped? part_name) ?{ (?) -> untyped } -> untyped + + def queue_name_from_part: (untyped part_name) -> untyped + end + + # Returns the name of the queue the job will be run on. + def queue_name: () -> untyped + end +end + +module ActiveJob + module QueuePriority + extend ::ActiveSupport::Concern + + # Includes the ability to override the default queue priority. + module ClassMethods + def self.default_priority: () -> untyped + + def self.default_priority=: (untyped value) -> untyped + + def default_priority: () -> untyped + + def default_priority=: (untyped value) -> untyped + + def priority: () -> untyped + + def priority=: (untyped value) -> untyped + + def priority?: () -> bool + + # Specifies the priority of the queue to create the job with. + # + # class PublishToFeedJob < ActiveJob::Base + # queue_with_priority 50 + # + # def perform(post) + # post.to_feed! + # end + # end + # + # Can be given a block that will evaluate in the context of the job + # so that a dynamic priority can be applied: + # + # class PublishToFeedJob < ApplicationJob + # queue_with_priority do + # post = self.arguments.first + # + # if post.paid? + # 10 + # else + # 50 + # end + # end + # + # def perform(post) + # post.to_feed! + # end + # end + def queue_with_priority: (?untyped? priority) ?{ (?) -> untyped } -> untyped + end + + # Returns the priority that the job will be created with + def priority: () -> untyped + end +end + +module ActiveJob + # = Active Job Railtie + class Railtie < ::Rails::Railtie + end +end + +module ActiveJob + # = Active Job \Serializers + # + # The +ActiveJob::Serializers+ module is used to store a list of known serializers + # and to add new ones. It also has helpers to serialize/deserialize objects. + module Serializers + extend ::ActiveSupport::Autoload + + # Returns serialized representative of the passed object. + # Will look up through all known serializers. + # Raises ActiveJob::SerializationError if it can't find a proper serializer. + def self.serialize: (untyped argument) -> untyped + + # Returns deserialized object. + # Will look up through all known serializers. + # If no serializer found will raise ArgumentError. + def self.deserialize: (untyped argument) -> untyped + + # Returns list of known serializers. + attr_reader self.serializers: untyped + + def self.serializers=: (untyped serializers) -> untyped + + # Adds new serializers to a list of known serializers. + def self.add_serializers: (*untyped new_serializers) -> untyped + + def self.add_new_serializers: (untyped new_serializers) -> untyped + + def self.index_serializers: (untyped new_serializers) -> untyped + + # :nodoc: + OBJECT_SERIALIZER_KEY: "_aj_serialized" + end +end + +module ActiveJob + module Serializers + class ActionControllerParametersSerializer < ::ActiveJob::Serializers::ObjectSerializer + def serialize: (untyped argument) -> untyped + + def deserialize: (untyped hash) -> untyped + + def serialize?: (untyped argument) -> untyped + + def klass: () -> (untyped | nil) + end + end +end + +module ActiveJob + module Serializers + class BigDecimalSerializer < ::ActiveJob::Serializers::ObjectSerializer + def serialize: (untyped big_decimal) -> untyped + + def deserialize: (untyped hash) -> untyped + + def klass: () -> untyped + end + end +end + +module ActiveJob + module Serializers + class DateSerializer < ::ActiveJob::Serializers::ObjectSerializer + def serialize: (untyped date) -> untyped + + def deserialize: (untyped hash) -> untyped + + def klass: () -> untyped + end + end +end + +module ActiveJob + module Serializers + class DateTimeSerializer < ::ActiveJob::Serializers::TimeObjectSerializer + def deserialize: (untyped hash) -> untyped + + def klass: () -> untyped + end + end +end + +module ActiveJob + module Serializers + class DurationSerializer < ::ActiveJob::Serializers::ObjectSerializer + def serialize: (untyped duration) -> untyped + + def deserialize: (untyped hash) -> untyped + + def klass: () -> untyped + end + end +end + +module ActiveJob + module Serializers + class ModuleSerializer < ::ActiveJob::Serializers::ObjectSerializer + def serialize: (untyped constant) -> untyped + + def deserialize: (untyped hash) -> untyped + + def klass: () -> untyped + end + end +end + +module ActiveJob + module Serializers + # Base class for serializing and deserializing custom objects. + # + # Example: + # + # class MoneySerializer < ActiveJob::Serializers::ObjectSerializer + # def serialize(money) + # super("amount" => money.amount, "currency" => money.currency) + # end + # + # def deserialize(hash) + # Money.new(hash["amount"], hash["currency"]) + # end + # + # def klass + # Money + # end + # end + class ObjectSerializer + def self.serialize?: (*untyped, **untyped) ?{ (*untyped) -> untyped } -> untyped + def self.serialize: (*untyped, **untyped) ?{ (*untyped) -> untyped } -> untyped + def self.deserialize: (*untyped, **untyped) ?{ (*untyped) -> untyped } -> untyped + + include ::Singleton + + def initialize: () -> void + + # Determines if an argument should be serialized by a serializer. + def serialize?: (untyped argument) -> untyped + + # Serializes an argument to a JSON primitive type. + def serialize: (untyped hash) -> untyped + + # Deserializes an argument from a JSON primitive type. + def deserialize: (untyped hash) -> untyped + end + end +end + +module ActiveJob + module Serializers + class RangeSerializer < ::ActiveJob::Serializers::ObjectSerializer + def serialize: (untyped range) -> untyped + + def deserialize: (untyped hash) -> untyped + + def klass: () -> untyped + end + end +end + +module ActiveJob + module Serializers + class SymbolSerializer < ::ActiveJob::Serializers::ObjectSerializer + def serialize: (untyped argument) -> untyped + + def deserialize: (untyped argument) -> untyped + + def klass: () -> untyped + end + end +end + +module ActiveJob + module Serializers + class TimeObjectSerializer < ::ActiveJob::Serializers::ObjectSerializer + NANO_PRECISION: 9 + + def serialize: (untyped time) -> untyped + end + end +end + +module ActiveJob + module Serializers + class TimeSerializer < ::ActiveJob::Serializers::TimeObjectSerializer + def deserialize: (untyped hash) -> untyped + + def klass: () -> untyped + end + end +end + +module ActiveJob + module Serializers + class TimeWithZoneSerializer < ::ActiveJob::Serializers::ObjectSerializer + NANO_PRECISION: 9 + + def serialize: (untyped time_with_zone) -> untyped + + def deserialize: (untyped hash) -> untyped + + def klass: () -> untyped + end + end +end + +module ActiveJob + class StructuredEventSubscriber < ::ActiveSupport::StructuredEventSubscriber + def enqueue: (untyped event) -> untyped + + def enqueue_at: (untyped event) -> untyped + + def enqueue_all: (untyped event) -> untyped + + def perform_start: (untyped event) -> untyped + + def perform: (untyped event) -> untyped + + def enqueue_retry: (untyped event) -> untyped + + def retry_stopped: (untyped event) -> untyped + + def discard: (untyped event) -> untyped + + def interrupt: (untyped event) -> untyped + + def resume: (untyped event) -> untyped + + def step_skipped: (untyped event) -> untyped + + def step_started: (untyped event) -> untyped + + def step: (untyped event) -> untyped + end +end + +module ActiveJob + class TestCase < ::ActiveSupport::TestCase + include ::ActiveJob::TestHelper + end +end + +module ActiveJob + # Provides helper methods for testing Active Job + module TestHelper + def enqueued_jobs: (*untyped, **untyped) ?{ (*untyped) -> untyped } -> untyped + + def enqueued_jobs=: (*untyped, **untyped) ?{ (*untyped) -> untyped } -> untyped + + def performed_jobs: (*untyped, **untyped) ?{ (*untyped) -> untyped } -> untyped + + def performed_jobs=: (*untyped, **untyped) ?{ (*untyped) -> untyped } -> untyped + + include ::ActiveSupport::Testing::Assertions + + module TestQueueAdapter + extend ::ActiveSupport::Concern + + module ClassMethods + def _test_adapter: () -> untyped + + def _test_adapter=: (untyped value) -> untyped + + def queue_adapter: () -> untyped + + def disable_test_adapter: () -> untyped + + def enable_test_adapter: (untyped test_adapter) -> untyped + end + end + + def before_setup: () -> untyped + + def after_teardown: () -> untyped + + # Returns a queue adapter instance to use with all Active Job test helpers. + # By default, returns an instance of ActiveJob::QueueAdapters::TestAdapter. + # Override this method to specify a different adapter. The adapter must + # implement the same interface as ActiveJob::QueueAdapters::TestAdapter. + def queue_adapter_for_test: () -> nil + + # Asserts that the number of enqueued jobs matches the given number. + # + # def test_jobs + # assert_enqueued_jobs 0 + # HelloJob.perform_later('david') + # assert_enqueued_jobs 1 + # HelloJob.perform_later('abdelkader') + # assert_enqueued_jobs 2 + # end + # + # If a block is passed, asserts that the block will cause the specified number of + # jobs to be enqueued. + # + # def test_jobs_again + # assert_enqueued_jobs 1 do + # HelloJob.perform_later('cristian') + # end + # + # assert_enqueued_jobs 2 do + # HelloJob.perform_later('aaron') + # HelloJob.perform_later('rafael') + # end + # end + # + # Asserts the number of times a specific job was enqueued by passing +:only+ option. + # + # def test_logging_job + # assert_enqueued_jobs 1, only: LoggingJob do + # LoggingJob.perform_later + # HelloJob.perform_later('jeremy') + # end + # end + # + # Asserts the number of times a job except specific class was enqueued by passing +:except+ option. + # + # def test_logging_job + # assert_enqueued_jobs 1, except: HelloJob do + # LoggingJob.perform_later + # HelloJob.perform_later('jeremy') + # end + # end + # + # +:only+ and +:except+ options accept Class, Array of Class, or Proc. When passed a Proc, + # a hash containing the job's class and it's argument are passed as argument. + # + # Asserts the number of times a job is enqueued to a specific queue by passing +:queue+ option. + # + # def test_logging_job + # assert_enqueued_jobs 2, queue: 'default' do + # LoggingJob.perform_later + # HelloJob.perform_later('elfassy') + # end + # end + def assert_enqueued_jobs: (untyped number, ?only: untyped?, ?except: untyped?, ?queue: untyped?) ?{ (?) -> untyped } -> untyped + + # Asserts that no jobs have been enqueued. + # + # def test_jobs + # assert_no_enqueued_jobs + # HelloJob.perform_later('jeremy') + # assert_enqueued_jobs 1 + # end + # + # If a block is passed, asserts that the block will not cause any job to be enqueued. + # + # def test_jobs_again + # assert_no_enqueued_jobs do + # # No job should be enqueued from this block + # end + # end + # + # Asserts that no jobs of a specific kind are enqueued by passing +:only+ option. + # + # def test_no_logging + # assert_no_enqueued_jobs only: LoggingJob do + # HelloJob.perform_later('jeremy') + # end + # end + # + # Asserts that no jobs except specific class are enqueued by passing +:except+ option. + # + # def test_no_logging + # assert_no_enqueued_jobs except: HelloJob do + # HelloJob.perform_later('jeremy') + # end + # end + # + # +:only+ and +:except+ options accept Class, Array of Class, or Proc. When passed a Proc, + # a hash containing the job's class and it's argument are passed as argument. + # + # Asserts that no jobs are enqueued to a specific queue by passing +:queue+ option + # + # def test_no_logging + # assert_no_enqueued_jobs queue: 'default' do + # LoggingJob.set(queue: :some_queue).perform_later + # end + # end + # + # Note: This assertion is simply a shortcut for: + # + # assert_enqueued_jobs 0, &block + def assert_no_enqueued_jobs: (?only: untyped?, ?except: untyped?, ?queue: untyped?) { (?) -> untyped } -> untyped + + # Asserts that the number of performed jobs matches the given number. + # If no block is passed, perform_enqueued_jobs + # must be called around or after the job call. + # + # def test_jobs + # assert_performed_jobs 0 + # + # perform_enqueued_jobs do + # HelloJob.perform_later('xavier') + # end + # assert_performed_jobs 1 + # + # HelloJob.perform_later('yves') + # + # perform_enqueued_jobs + # + # assert_performed_jobs 2 + # end + # + # If a block is passed, asserts that the block will cause the specified number of + # jobs to be performed. + # + # def test_jobs_again + # assert_performed_jobs 1 do + # HelloJob.perform_later('robin') + # end + # + # assert_performed_jobs 2 do + # HelloJob.perform_later('carlos') + # HelloJob.perform_later('sean') + # end + # end + # + # This method also supports filtering. If the +:only+ option is specified, + # then only the listed job(s) will be performed. + # + # def test_hello_job + # assert_performed_jobs 1, only: HelloJob do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later + # end + # end + # + # Also if the +:except+ option is specified, + # then the job(s) except specific class will be performed. + # + # def test_hello_job + # assert_performed_jobs 1, except: LoggingJob do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later + # end + # end + # + # An array may also be specified, to support testing multiple jobs. + # + # def test_hello_and_logging_jobs + # assert_nothing_raised do + # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later('stewie') + # RescueJob.perform_later('david') + # end + # end + # end + # + # A proc may also be specified. When passed a Proc, the job's instance will be passed as argument. + # + # def test_hello_and_logging_jobs + # assert_nothing_raised do + # assert_performed_jobs(1, only: ->(job) { job.is_a?(HelloJob) }) do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later('stewie') + # RescueJob.perform_later('david') + # end + # end + # end + # + # If the +:queue+ option is specified, + # then only the job(s) enqueued to a specific queue will be performed. + # + # def test_assert_performed_jobs_with_queue_option + # assert_performed_jobs 1, queue: :some_queue do + # HelloJob.set(queue: :some_queue).perform_later("jeremy") + # HelloJob.set(queue: :other_queue).perform_later("bogdan") + # end + # end + def assert_performed_jobs: (untyped number, ?only: untyped?, ?except: untyped?, ?queue: untyped?) ?{ (?) -> untyped } -> untyped + + # Asserts that no jobs have been performed. + # + # def test_jobs + # assert_no_performed_jobs + # + # perform_enqueued_jobs do + # HelloJob.perform_later('matthew') + # assert_performed_jobs 1 + # end + # end + # + # If a block is passed, asserts that the block will not cause any job to be performed. + # + # def test_jobs_again + # assert_no_performed_jobs do + # # No job should be performed from this block + # end + # end + # + # The block form supports filtering. If the +:only+ option is specified, + # then only the listed job(s) will not be performed. + # + # def test_no_logging + # assert_no_performed_jobs only: LoggingJob do + # HelloJob.perform_later('jeremy') + # end + # end + # + # Also if the +:except+ option is specified, + # then the job(s) except specific class will not be performed. + # + # def test_no_logging + # assert_no_performed_jobs except: HelloJob do + # HelloJob.perform_later('jeremy') + # end + # end + # + # +:only+ and +:except+ options accept Class, Array of Class, or Proc. When passed a Proc, + # an instance of the job will be passed as argument. + # + # If the +:queue+ option is specified, + # then only the job(s) enqueued to a specific queue will not be performed. + # + # def test_assert_no_performed_jobs_with_queue_option + # assert_no_performed_jobs queue: :some_queue do + # HelloJob.set(queue: :other_queue).perform_later("jeremy") + # end + # end + # + # Note: This assertion is simply a shortcut for: + # + # assert_performed_jobs 0, &block + def assert_no_performed_jobs: (?only: untyped?, ?except: untyped?, ?queue: untyped?) { (?) -> untyped } -> untyped + + # Asserts that the job has been enqueued with the given arguments. + # + # def test_assert_enqueued_with + # MyJob.perform_later(1,2,3) + # assert_enqueued_with(job: MyJob, args: [1,2,3]) + # + # MyJob.set(wait_until: Date.tomorrow.noon, queue: "my_queue").perform_later + # assert_enqueued_with(at: Date.tomorrow.noon, queue: "my_queue") + # end + # + # For keyword arguments, specify them as a hash inside an array: + # + # def test_assert_enqueued_with_keyword_arguments + # MyJob.perform_later(arg1: 'value1', arg2: 'value2') + # assert_enqueued_with(job: MyJob, args: [{ arg1: 'value1', arg2: 'value2' }]) + # end + # + # The given arguments may also be specified as matcher procs that return a + # boolean value indicating whether a job's attribute meets certain criteria. + # + # For example, a proc can be used to match a range of times: + # + # def test_assert_enqueued_with + # at_matcher = ->(job_at) { (Date.yesterday..Date.tomorrow).cover?(job_at) } + # + # MyJob.set(wait_until: Date.today.noon).perform_later + # + # assert_enqueued_with(job: MyJob, at: at_matcher) + # end + # + # A proc can also be used to match a subset of a job's args: + # + # def test_assert_enqueued_with + # args_matcher = ->(job_args) { job_args[0].key?(:foo) } + # + # MyJob.perform_later(foo: "bar", other_arg: "No need to check in the test") + # + # assert_enqueued_with(job: MyJob, args: args_matcher) + # end + # + # If a block is passed, asserts that the block will cause the job to be + # enqueued with the given arguments. + # + # def test_assert_enqueued_with + # assert_enqueued_with(job: MyJob, args: [1,2,3]) do + # MyJob.perform_later(1,2,3) + # end + # + # assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon) do + # MyJob.set(wait_until: Date.tomorrow.noon).perform_later + # end + # end + def assert_enqueued_with: (?job: untyped?, ?args: untyped?, ?at: untyped?, ?queue: untyped?, ?priority: untyped?) ?{ (?) -> untyped } -> untyped + + # Asserts that the job has been performed with the given arguments. + # + # def test_assert_performed_with + # MyJob.perform_later(1,2,3) + # + # perform_enqueued_jobs + # + # assert_performed_with(job: MyJob, args: [1,2,3]) + # + # MyJob.set(wait_until: Date.tomorrow.noon, queue: "my_queue").perform_later + # + # perform_enqueued_jobs + # + # assert_performed_with(at: Date.tomorrow.noon, queue: "my_queue") + # end + # + # The given arguments may also be specified as matcher procs that return a + # boolean value indicating whether a job's attribute meets certain criteria. + # + # For example, a proc can be used to match a range of times: + # + # def test_assert_performed_with + # at_matcher = ->(job_at) { (Date.yesterday..Date.tomorrow).cover?(job_at) } + # + # MyJob.set(wait_until: Date.today.noon).perform_later + # + # perform_enqueued_jobs + # + # assert_performed_with(job: MyJob, at: at_matcher) + # end + # + # A proc can also be used to match a subset of a job's args: + # + # def test_assert_performed_with + # args_matcher = ->(job_args) { job_args[0].key?(:foo) } + # + # MyJob.perform_later(foo: "bar", other_arg: "No need to check in the test") + # + # perform_enqueued_jobs + # + # assert_performed_with(job: MyJob, args: args_matcher) + # end + # + # If a block is passed, that block performs all of the jobs that were + # enqueued throughout the duration of the block and asserts that + # the job has been performed with the given arguments in the block. + # + # def test_assert_performed_with + # assert_performed_with(job: MyJob, args: [1,2,3]) do + # MyJob.perform_later(1,2,3) + # end + # + # assert_performed_with(job: MyJob, at: Date.tomorrow.noon) do + # MyJob.set(wait_until: Date.tomorrow.noon).perform_later + # end + # end + def assert_performed_with: (?job: untyped?, ?args: untyped?, ?at: untyped?, ?queue: untyped?, ?priority: untyped?) ?{ (?) -> untyped } -> untyped + + # Performs all enqueued jobs. If a block is given, performs all of the jobs + # that were enqueued throughout the duration of the block. If a block is + # not given, performs all of the enqueued jobs up to this point in the test. + # + # def test_perform_enqueued_jobs + # perform_enqueued_jobs do + # MyJob.perform_later(1, 2, 3) + # end + # assert_performed_jobs 1 + # end + # + # def test_perform_enqueued_jobs_without_block + # MyJob.perform_later(1, 2, 3) + # + # perform_enqueued_jobs + # + # assert_performed_jobs 1 + # end + # + # This method also supports filtering. If the +:only+ option is specified, + # then only the listed job(s) will be performed. + # + # def test_perform_enqueued_jobs_with_only + # perform_enqueued_jobs(only: MyJob) do + # MyJob.perform_later(1, 2, 3) # will be performed + # HelloJob.perform_later(1, 2, 3) # will not be performed + # end + # assert_performed_jobs 1 + # end + # + # Also if the +:except+ option is specified, + # then the job(s) except specific class will be performed. + # + # def test_perform_enqueued_jobs_with_except + # perform_enqueued_jobs(except: HelloJob) do + # MyJob.perform_later(1, 2, 3) # will be performed + # HelloJob.perform_later(1, 2, 3) # will not be performed + # end + # assert_performed_jobs 1 + # end + # + # +:only+ and +:except+ options accept Class, Array of Class, or Proc. When passed a Proc, + # an instance of the job will be passed as argument. + # + # If the +:queue+ option is specified, + # then only the job(s) enqueued to a specific queue will be performed. + # + # def test_perform_enqueued_jobs_with_queue + # perform_enqueued_jobs queue: :some_queue do + # MyJob.set(queue: :some_queue).perform_later(1, 2, 3) # will be performed + # HelloJob.set(queue: :other_queue).perform_later(1, 2, 3) # will not be performed + # end + # assert_performed_jobs 1 + # end + # + # If the +:at+ option is specified, then only jobs that have been enqueued + # to run at or before the given time will be performed. This includes jobs + # that have been enqueued without a time. + # + # If queue_adapter_for_test is overridden to return a different adapter, + # +perform_enqueued_jobs+ will merely execute the block. + def perform_enqueued_jobs: (?only: untyped?, ?except: untyped?, ?queue: untyped?, ?at: untyped?) ?{ (?) -> untyped } -> untyped + + # Accesses the queue_adapter set by ActiveJob::Base. + # + # def test_assert_job_has_custom_queue_adapter_set + # assert_instance_of CustomQueueAdapter, HelloJob.queue_adapter + # end + def queue_adapter: () -> untyped + + private + + def require_active_job_test_adapter!: (untyped method) -> (untyped | nil) + + def using_test_adapter?: () -> untyped + + def clear_enqueued_jobs: () -> (untyped | nil) + + def clear_performed_jobs: () -> (untyped | nil) + + def jobs_with: (untyped jobs, ?only: untyped?, ?except: untyped?, ?queue: untyped?, ?at: untyped?) ?{ (untyped) -> untyped } -> untyped + + def filter_as_proc: (untyped filter) -> untyped + + def enqueued_jobs_with: (?only: untyped?, ?except: untyped?, ?queue: untyped?, ?at: untyped?) { (?) -> untyped } -> untyped + + def performed_jobs_with: (?only: untyped?, ?except: untyped?, ?queue: untyped?) { (?) -> untyped } -> untyped + + def flush_enqueued_jobs: (?only: untyped?, ?except: untyped?, ?queue: untyped?, ?at: untyped?) -> untyped + + def prepare_args_for_assertion: (untyped args) -> untyped + + def deserialize_args_for_assertion: (untyped job) -> untyped + + def instantiate_job: (untyped payload, ?skip_deserialize_arguments: bool) -> untyped + + def queue_adapter_changed_jobs: () -> untyped + + def validate_option: (?only: untyped?, ?except: untyped?) -> (untyped | nil) + end +end + +module ActiveJob + # Returns the currently loaded version of Active Job as a +Gem::Version+. + def self.version: () -> untyped +end From f7a72d36ed4ba93ee8b8152179fc8f5023993cae Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 19:04:24 +0900 Subject: [PATCH 03/16] activejob: Remove fake types for the Que and Sucker Punch adapters Rails no longer has QueAdapter or SuckerPunchAdapter, so nothing refers to Que::Job or SuckerPunch::Job. Co-Authored-By: Claude Opus 5.5 (1M context) --- gems/activejob/8.1/patch.rbs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/gems/activejob/8.1/patch.rbs b/gems/activejob/8.1/patch.rbs index 0752327fd..a9029291f 100644 --- a/gems/activejob/8.1/patch.rbs +++ b/gems/activejob/8.1/patch.rbs @@ -1,9 +1,3 @@ -# Remove the fake type if the real type is available -module Que - class Job - end -end - # Remove the fake type if the real type is available class QC class Queue @@ -21,9 +15,3 @@ module Sneakers module Worker end end - -# Remove the fake type if the real type is available -module SuckerPunch - module Job - end -end From d5e30999a62ff075fe2bd03090f8fd6085c984f8 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 19:05:49 +0900 Subject: [PATCH 04/16] activejob: Add logger and log_arguments to ActiveJob::Base Logging defines them on the job class with cattr_accessor and class_attribute in its included block, which rbs prototype doesn't see. Co-Authored-By: Claude Opus 5.5 (1M context) --- gems/activejob/8.1/_test/activejob.rb | 3 +++ gems/activejob/8.1/activejob.rbs | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/gems/activejob/8.1/_test/activejob.rb b/gems/activejob/8.1/_test/activejob.rb index d0521bf37..f89e54697 100644 --- a/gems/activejob/8.1/_test/activejob.rb +++ b/gems/activejob/8.1/_test/activejob.rb @@ -23,3 +23,6 @@ def around_task SendMessageJob.perform_now SendMessageJob.set(queue: :another_queue).perform_later(flag: false) + +SendMessageJob.logger = SendMessageJob.logger +SendMessageJob.log_arguments = false diff --git a/gems/activejob/8.1/activejob.rbs b/gems/activejob/8.1/activejob.rbs index e5447b4a9..f3f4c5364 100644 --- a/gems/activejob/8.1/activejob.rbs +++ b/gems/activejob/8.1/activejob.rbs @@ -59,4 +59,17 @@ module ActiveJob end end end -end \ No newline at end of file +end + +# Methods rbs prototype doesn't see, which aren't generated from the source +module ActiveJob + class Base + # cattr_accessor and class_attribute in Logging's included block + def self.logger: () -> untyped + def self.logger=: (untyped value) -> untyped + + def self.log_arguments: () -> untyped + def self.log_arguments=: (untyped value) -> untyped + def self.log_arguments?: () -> bool + end +end From 976d9512da3dc1fdae763599909671805f14acdb Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 19:06:06 +0900 Subject: [PATCH 05/16] activejob: Type return values of commonly used job methods job_id, arguments, executions, queue_name, priority, and the other job attributes, enqueue, and perform_later, which returns the job or false when it couldn't be enqueued. Co-Authored-By: Claude Opus 5.5 (1M context) --- gems/activejob/8.1/_test/activejob.rb | 23 +++++++ gems/activejob/8.1/activejob-generated.rbs | 79 ---------------------- gems/activejob/8.1/activejob.rbs | 43 ++++++++++++ 3 files changed, 66 insertions(+), 79 deletions(-) diff --git a/gems/activejob/8.1/_test/activejob.rb b/gems/activejob/8.1/_test/activejob.rb index f89e54697..1e9739315 100644 --- a/gems/activejob/8.1/_test/activejob.rb +++ b/gems/activejob/8.1/_test/activejob.rb @@ -26,3 +26,26 @@ def around_task SendMessageJob.logger = SendMessageJob.logger SendMessageJob.log_arguments = false + +job = SendMessageJob.new(1, "two") +job.job_id.upcase +job.arguments.size +job.executions + 1 +job.exception_executions.each_value(&:succ) +job.queue_name.upcase +job.priority&.succ +job.locale&.upcase +job.timezone&.upcase +job.enqueued_at&.iso8601 +job.enqueue_error&.message +job.successfully_enqueued? + +if (enqueued = SendMessageJob.perform_later(1)) + enqueued.job_id.upcase +end +if (enqueued = SendMessageJob.set(wait: 1.minute).perform_later(1) { |j| j.job_id }) + enqueued.provider_job_id +end +if (enqueued = job.enqueue(queue: :low)) + enqueued.job_id.upcase +end diff --git a/gems/activejob/8.1/activejob-generated.rbs b/gems/activejob/8.1/activejob-generated.rbs index ac0eef7a2..bbc45df27 100644 --- a/gems/activejob/8.1/activejob-generated.rbs +++ b/gems/activejob/8.1/activejob-generated.rbs @@ -334,8 +334,6 @@ module ActiveJob def initialize: (untyped job_class, ?::Hash[untyped, untyped] options) -> void def perform_now: (*untyped, **untyped) ?{ (?) -> untyped } -> untyped - - def perform_later: (*untyped, **untyped) ?{ (untyped) -> untyped } -> untyped end end @@ -804,17 +802,11 @@ module ActiveJob module Core extend ::ActiveSupport::Concern - # Job arguments - attr_accessor arguments: untyped - attr_writer serialized_arguments: untyped # Time when the job should be performed attr_accessor scheduled_at: untyped - # Job Identifier - attr_accessor job_id: untyped - # Queue in which the job will reside. attr_writer queue_name: untyped @@ -824,32 +816,9 @@ module ActiveJob # ID optionally provided by adapter attr_accessor provider_job_id: untyped - # Number of times this job has been executed (which increments on every retry, like after an exception). - attr_accessor executions: untyped - - # Hash that contains the number of times this job handled errors for each specific retry_on declaration. - # Keys are the string representation of the exceptions listed in the retry_on declaration, - # while its associated value holds the number of executions where the corresponding retry_on - # declaration handled one of its listed exceptions. - attr_accessor exception_executions: untyped - - # I18n.locale to be used during the job. - attr_accessor locale: untyped - - # Timezone to be used during the job. - attr_accessor timezone: untyped - - # Track when a job was enqueued - attr_accessor enqueued_at: untyped - # Track whether the adapter received the job successfully. attr_writer successfully_enqueued: untyped - def successfully_enqueued?: () -> untyped - - # Track any exceptions raised by the backend so callers can inspect the errors. - attr_accessor enqueue_error: untyped - # These methods will be included into any Active Job object, adding # helpers for de/serialization and creation of job instances. module ClassMethods @@ -968,53 +937,11 @@ module ActiveJob def enqueue_after_transaction_commit=: (untyped value) -> untyped - # Push a job onto the queue. By default the arguments must be either String, - # Integer, Float, NilClass, TrueClass, FalseClass, BigDecimal, Symbol, Date, - # Time, DateTime, ActiveSupport::TimeWithZone, ActiveSupport::Duration, - # Hash, ActiveSupport::HashWithIndifferentAccess, Array, Range, or - # GlobalID::Identification instances, although this can be extended by adding - # custom serializers. - # - # Returns an instance of the job class queued with arguments available in - # Job#arguments or +false+ if the enqueue did not succeed. - # - # After the attempted enqueue, the job will be yielded to an optional block. - # - # If Active Job is used conjointly with Active Record, and #perform_later is called - # inside an Active Record transaction, then the enqueue is implicitly deferred to after - # the transaction is committed, or dropped if it's rolled back. In such case #perform_later - # will return the job instance like if it was successfully enqueued, but will still return - # +false+ if a callback prevented the job from being enqueued. - # - # This behavior can be changed on a per job basis: - # - # class NotificationJob < ApplicationJob - # self.enqueue_after_transaction_commit = false - # end - def perform_later: (*untyped, **untyped) ?{ (untyped) -> untyped } -> untyped - private def job_or_instantiate: (*untyped args) { (?) -> untyped } -> untyped end - # Enqueues the job to be performed by the queue adapter. - # - # ==== Options - # * :wait - Enqueues the job with the specified delay - # * :wait_until - Enqueues the job at the time specified - # * :queue - Enqueues the job on the specified queue - # * :priority - Enqueues the job with the specified priority - # - # ==== Examples - # - # my_job_instance.enqueue - # my_job_instance.enqueue wait: 5.minutes - # my_job_instance.enqueue queue: :important - # my_job_instance.enqueue wait_until: Date.tomorrow.midnight - # my_job_instance.enqueue priority: 10 - def enqueue: (?::Hash[untyped, untyped] options) -> untyped - private def raw_enqueue: () -> untyped @@ -1917,9 +1844,6 @@ module ActiveJob def queue_name_from_part: (untyped part_name) -> untyped end - - # Returns the name of the queue the job will be run on. - def queue_name: () -> untyped end end @@ -1973,9 +1897,6 @@ module ActiveJob # end def queue_with_priority: (?untyped? priority) ?{ (?) -> untyped } -> untyped end - - # Returns the priority that the job will be created with - def priority: () -> untyped end end diff --git a/gems/activejob/8.1/activejob.rbs b/gems/activejob/8.1/activejob.rbs index f3f4c5364..12bc91130 100644 --- a/gems/activejob/8.1/activejob.rbs +++ b/gems/activejob/8.1/activejob.rbs @@ -73,3 +73,46 @@ module ActiveJob def self.log_arguments?: () -> bool end end + +# Return types of commonly used methods +module ActiveJob + module Core + attr_accessor arguments: Array[untyped] + + attr_accessor job_id: String + + attr_accessor executions: Integer + + attr_accessor exception_executions: Hash[String, Integer] + + attr_accessor locale: String? + + attr_accessor timezone: String? + + attr_accessor enqueued_at: Time? + + def successfully_enqueued?: () -> bool? + + attr_accessor enqueue_error: EnqueueError? + end + + module QueueName + def queue_name: () -> String + end + + module QueuePriority + def priority: () -> Integer? + end + + module Enqueuing + def enqueue: (?Hash[Symbol, untyped] options) -> (self | false) + + module ClassMethods + def perform_later: (*untyped, **untyped) ?{ (ActiveJob::Base job) -> void } -> (ActiveJob::Base | false) + end + end + + class ConfiguredJob + def perform_later: (*untyped, **untyped) ?{ (ActiveJob::Base job) -> void } -> (ActiveJob::Base | false) + end +end From a33924345e72e1930c23a0a705fd935cf4b701bd Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 19:08:14 +0900 Subject: [PATCH 06/16] activejob: Make blocks optional where calling without one is valid rbs prototype marks a block as required unless the method checks it. These forward the block or don't use it: the callbacks with method names (before_perform :foo), assert_no_enqueued_jobs and assert_no_performed_jobs without a block, and a few internal methods. Co-Authored-By: Claude Opus 5.5 (1M context) --- gems/activejob/8.1/_test/activejob.rb | 9 ++++++ gems/activejob/8.1/_test/send_message_job.rbs | 4 +++ gems/activejob/8.1/activejob-generated.rbs | 28 +++++++++---------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/gems/activejob/8.1/_test/activejob.rb b/gems/activejob/8.1/_test/activejob.rb index 1e9739315..546c415a9 100644 --- a/gems/activejob/8.1/_test/activejob.rb +++ b/gems/activejob/8.1/_test/activejob.rb @@ -49,3 +49,12 @@ def around_task if (enqueued = job.enqueue(queue: :low)) enqueued.job_id.upcase end + +class SendMessageJobTest < ActiveJob::TestCase + def test_enqueue + assert_no_enqueued_jobs + SendMessageJob.perform_later + assert_enqueued_jobs 1 + assert_no_performed_jobs + end +end diff --git a/gems/activejob/8.1/_test/send_message_job.rbs b/gems/activejob/8.1/_test/send_message_job.rbs index 70b2b929b..67fd4d848 100644 --- a/gems/activejob/8.1/_test/send_message_job.rbs +++ b/gems/activejob/8.1/_test/send_message_job.rbs @@ -5,3 +5,7 @@ class SendMessageJob < ActiveJob::Base def around_task: () -> void end + +class SendMessageJobTest < ActiveJob::TestCase + def test_enqueue: () -> void +end diff --git a/gems/activejob/8.1/activejob-generated.rbs b/gems/activejob/8.1/activejob-generated.rbs index bbc45df27..8e068f5a5 100644 --- a/gems/activejob/8.1/activejob-generated.rbs +++ b/gems/activejob/8.1/activejob-generated.rbs @@ -222,7 +222,7 @@ module ActiveJob # end # end # - def before_perform: (*untyped filters) { (?) -> untyped } -> untyped + def before_perform: (*untyped filters) ?{ (?) -> untyped } -> untyped # Defines a callback that will get called right after the # job's perform method has finished. @@ -239,7 +239,7 @@ module ActiveJob # end # end # - def after_perform: (*untyped filters) { (?) -> untyped } -> untyped + def after_perform: (*untyped filters) ?{ (?) -> untyped } -> untyped # Defines a callback that will get called around the job's perform method. # @@ -270,7 +270,7 @@ module ActiveJob # end # end # - def around_perform: (*untyped filters) { (?) -> untyped } -> untyped + def around_perform: (*untyped filters) ?{ (?) -> untyped } -> untyped # Defines a callback that will get called right before the # job is enqueued. @@ -287,7 +287,7 @@ module ActiveJob # end # end # - def before_enqueue: (*untyped filters) { (?) -> untyped } -> untyped + def before_enqueue: (*untyped filters) ?{ (?) -> untyped } -> untyped # Defines a callback that will get called right after the # job is enqueued. @@ -305,7 +305,7 @@ module ActiveJob # end # end # - def after_enqueue: (*untyped filters) { (?) -> untyped } -> untyped + def after_enqueue: (*untyped filters) ?{ (?) -> untyped } -> untyped # Defines a callback that will get called around the enqueuing # of the job. @@ -324,7 +324,7 @@ module ActiveJob # end # end # - def around_enqueue: (*untyped filters) { (?) -> untyped } -> untyped + def around_enqueue: (*untyped filters) ?{ (?) -> untyped } -> untyped end end end @@ -939,7 +939,7 @@ module ActiveJob private - def job_or_instantiate: (*untyped args) { (?) -> untyped } -> untyped + def job_or_instantiate: (*untyped args) ?{ (?) -> untyped } -> untyped end private @@ -1176,7 +1176,7 @@ module ActiveJob def perform_now: () -> untyped - def instrument: (untyped operation, ?::Hash[untyped, untyped] payload) { (?) -> untyped } -> untyped + def instrument: (untyped operation, ?::Hash[untyped, untyped] payload) ?{ (?) -> untyped } -> untyped private @@ -1233,9 +1233,9 @@ module ActiveJob def logger: () -> untyped - def info: (?untyped? progname) { (?) -> untyped } -> (nil | untyped) + def info: (?untyped? progname) ?{ (?) -> untyped } -> (nil | untyped) - def error: (?untyped? progname) { (?) -> untyped } -> (nil | untyped) + def error: (?untyped? progname) ?{ (?) -> untyped } -> (nil | untyped) def log_enqueue_source: () -> untyped @@ -2287,7 +2287,7 @@ module ActiveJob # Note: This assertion is simply a shortcut for: # # assert_enqueued_jobs 0, &block - def assert_no_enqueued_jobs: (?only: untyped?, ?except: untyped?, ?queue: untyped?) { (?) -> untyped } -> untyped + def assert_no_enqueued_jobs: (?only: untyped?, ?except: untyped?, ?queue: untyped?) ?{ (?) -> untyped } -> untyped # Asserts that the number of performed jobs matches the given number. # If no block is passed, perform_enqueued_jobs @@ -2429,7 +2429,7 @@ module ActiveJob # Note: This assertion is simply a shortcut for: # # assert_performed_jobs 0, &block - def assert_no_performed_jobs: (?only: untyped?, ?except: untyped?, ?queue: untyped?) { (?) -> untyped } -> untyped + def assert_no_performed_jobs: (?only: untyped?, ?except: untyped?, ?queue: untyped?) ?{ (?) -> untyped } -> untyped # Asserts that the job has been enqueued with the given arguments. # @@ -2627,9 +2627,9 @@ module ActiveJob def filter_as_proc: (untyped filter) -> untyped - def enqueued_jobs_with: (?only: untyped?, ?except: untyped?, ?queue: untyped?, ?at: untyped?) { (?) -> untyped } -> untyped + def enqueued_jobs_with: (?only: untyped?, ?except: untyped?, ?queue: untyped?, ?at: untyped?) ?{ (?) -> untyped } -> untyped - def performed_jobs_with: (?only: untyped?, ?except: untyped?, ?queue: untyped?) { (?) -> untyped } -> untyped + def performed_jobs_with: (?only: untyped?, ?except: untyped?, ?queue: untyped?) ?{ (?) -> untyped } -> untyped def flush_enqueued_jobs: (?only: untyped?, ?except: untyped?, ?queue: untyped?, ?at: untyped?) -> untyped From 476eeb355e1ab80f6bf2b3577d163954e0c9a39f Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 19:16:20 +0900 Subject: [PATCH 07/16] activejob: Extend Callbacks and Rescuable class methods in ActiveJob::Base ActiveJob::Callbacks and ActiveJob::Exceptions include ActiveSupport::Callbacks and ActiveSupport::Rescuable, so job classes have rescue_from and set_callback. Co-Authored-By: Claude Opus 5.5 (1M context) --- gems/activejob/8.1/_test/activejob.rb | 2 ++ gems/activejob/8.1/activejob.rbs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/gems/activejob/8.1/_test/activejob.rb b/gems/activejob/8.1/_test/activejob.rb index 546c415a9..4905b10b1 100644 --- a/gems/activejob/8.1/_test/activejob.rb +++ b/gems/activejob/8.1/_test/activejob.rb @@ -8,6 +8,8 @@ class SendMessageJob < ActiveJob::Base around_perform :around_task + rescue_from(ActiveJob::DeserializationError) { |exception| exception.message } + def perform(*args) # Do something later end diff --git a/gems/activejob/8.1/activejob.rbs b/gems/activejob/8.1/activejob.rbs index 12bc91130..e86e99ec1 100644 --- a/gems/activejob/8.1/activejob.rbs +++ b/gems/activejob/8.1/activejob.rbs @@ -8,6 +8,8 @@ module ActiveJob extend ActiveJob::QueueAdapter::ClassMethods extend ActiveJob::QueueName::ClassMethods extend ActiveJob::QueuePriority::ClassMethods + extend ActiveSupport::Callbacks::ClassMethods + extend ActiveSupport::Rescuable::ClassMethods def self.queue_as: (*untyped) -> void | ... From b68095d3c5c7551de91e2c1dc19c6caaf5e585ce Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 19:10:34 +0900 Subject: [PATCH 08/16] actionmailer: Copy hand-written RBS and test from 7.0 to 8.1 Co-Authored-By: Claude Opus 5.5 (1M context) --- gems/actionmailer/8.1/_test/metadata.yaml | 2 + gems/actionmailer/8.1/_test/railsguides.rb | 39 +++++++++++++++++ gems/actionmailer/8.1/_test/railsguides.rbs | 23 ++++++++++ gems/actionmailer/8.1/_test/test.rb | 4 ++ gems/actionmailer/8.1/actionmailer.rbs | 47 +++++++++++++++++++++ gems/actionmailer/8.1/mail-message.rbs | 4 ++ gems/actionmailer/8.1/manifest.yaml | 13 ++++++ 7 files changed, 132 insertions(+) create mode 100644 gems/actionmailer/8.1/_test/metadata.yaml create mode 100644 gems/actionmailer/8.1/_test/railsguides.rb create mode 100644 gems/actionmailer/8.1/_test/railsguides.rbs create mode 100644 gems/actionmailer/8.1/_test/test.rb create mode 100644 gems/actionmailer/8.1/actionmailer.rbs create mode 100644 gems/actionmailer/8.1/mail-message.rbs create mode 100644 gems/actionmailer/8.1/manifest.yaml diff --git a/gems/actionmailer/8.1/_test/metadata.yaml b/gems/actionmailer/8.1/_test/metadata.yaml new file mode 100644 index 000000000..00be5669a --- /dev/null +++ b/gems/actionmailer/8.1/_test/metadata.yaml @@ -0,0 +1,2 @@ +additional_gems: + - railties diff --git a/gems/actionmailer/8.1/_test/railsguides.rb b/gems/actionmailer/8.1/_test/railsguides.rb new file mode 100644 index 000000000..514a5b92e --- /dev/null +++ b/gems/actionmailer/8.1/_test/railsguides.rb @@ -0,0 +1,39 @@ +module RailsGuides + class ApplicationMailer < ActionMailer::Base + default from: 'from@example.com' + layout 'mailer' + end + + class NotifierMailer < ApplicationMailer + default from: 'no-reply@example.com', + return_path: 'system@example.com' + + def welcome(recipient) + @account = recipient + mail(to: recipient, + bcc: ["bcc@example.com", "Order Watcher "]) + end + + # @dynamic self.welcome + end + + class UserMailer < ApplicationMailer + default from: 'notifications@example.com' + + def welcome_email + @user = params[:user] + @url = 'http://example.com/login' + mail(to: "@user", subject: 'Welcome to My Awesome Site') + end + end + + class I18nUserMailer < ActionMailer::Base + def welcome(email, username) + mail(to: email, subject: default_i18n_subject(user: username)) + end + end + + NotifierMailer.welcome("first user").deliver_now + NotifierMailer.welcome("first user").message + NotifierMailer.welcome("first user").deliver_later +end diff --git a/gems/actionmailer/8.1/_test/railsguides.rbs b/gems/actionmailer/8.1/_test/railsguides.rbs new file mode 100644 index 000000000..ce3b18404 --- /dev/null +++ b/gems/actionmailer/8.1/_test/railsguides.rbs @@ -0,0 +1,23 @@ +module RailsGuides + class ApplicationMailer < ActionMailer::Base + end + + class NotifierMailer < ApplicationMailer + @account: untyped + + def welcome: (untyped) -> Mail::Message + + def self.welcome: (untyped) -> ActionMailer::MessageDelivery + end + + class UserMailer < ApplicationMailer + @user: untyped + @url: String + + def welcome_email: () -> Mail::Message + end + + class I18nUserMailer < ApplicationMailer + def welcome: (String email, String username) -> Mail::Message + end +end diff --git a/gems/actionmailer/8.1/_test/test.rb b/gems/actionmailer/8.1/_test/test.rb new file mode 100644 index 000000000..fcef0c60a --- /dev/null +++ b/gems/actionmailer/8.1/_test/test.rb @@ -0,0 +1,4 @@ +# Write Ruby code to test the RBS. +# It is type checked by `steep check` command. + +require "actionmailer" diff --git a/gems/actionmailer/8.1/actionmailer.rbs b/gems/actionmailer/8.1/actionmailer.rbs new file mode 100644 index 000000000..37073cd95 --- /dev/null +++ b/gems/actionmailer/8.1/actionmailer.rbs @@ -0,0 +1,47 @@ +module ActionMailer + class Base < AbstractController::Base + include AbstractController::Rendering + include AbstractController::Helpers + extend AbstractController::Helpers::ClassMethods + include AbstractController::Translation + include AbstractController::AssetPaths + include AbstractController::Callbacks + extend AbstractController::Callbacks::ClassMethods + include AbstractController::Caching + extend AbstractController::Caching::ClassMethods + + include ActionView::Layouts + extend ActionView::Layouts::ClassMethods + + def self.default: (**untyped) -> void + + def self.layout: (String) -> void + + def mail: (**untyped) ?{ (untyped) -> void } -> Mail::Message + + def self.with: (**untyped) -> untyped + + def params: () -> Hash[untyped, untyped] + + def self?.email_address_with_name: (String, String) -> String + + def headers: () -> Mail::Message + | (Hash[untyped, untyped]) -> untyped + + private + + def default_i18n_subject: (?Hash[untyped, untyped] interpolations) -> String + end + + class MessageDelivery + def deliver_later: (?wait: untyped, ?wait_until: untyped, ?queue: untyped, ?priority: Integer) -> void + + def deliver_now: () -> void + + def deliver_now!: () -> void + + def message: () -> Mail::Message + + def processed: () -> bool + end +end diff --git a/gems/actionmailer/8.1/mail-message.rbs b/gems/actionmailer/8.1/mail-message.rbs new file mode 100644 index 000000000..5ceaf2ef5 --- /dev/null +++ b/gems/actionmailer/8.1/mail-message.rbs @@ -0,0 +1,4 @@ +module Mail + class Message + end +end diff --git a/gems/actionmailer/8.1/manifest.yaml b/gems/actionmailer/8.1/manifest.yaml new file mode 100644 index 000000000..aac4d14e4 --- /dev/null +++ b/gems/actionmailer/8.1/manifest.yaml @@ -0,0 +1,13 @@ +# manifest.yaml describes dependencies which do not appear in the gemspec. +# If this gem includes such dependencies, comment-out the following lines and +# declare the dependencies. +# If all dependencies appear in the gemspec, you should remove this file. +# +dependencies: + - name: cgi + - name: date + - name: logger + - name: monitor + - name: singleton + - name: time + - name: tsort From ddf4942ba7e5fe0beb92448f00fc423343ff78a8 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 19:11:59 +0900 Subject: [PATCH 09/16] actionmailer: Generate RBS from actionmailer 8.1.4 7.0 had only hand-written signatures. 8.1 adds signatures generated with `rbs prototype rb` from the actionmailer 8.1.4 source and post-processed the same way as the other Rails 8.1 gems: drop members already defined elsewhere, fill missing type arguments, add the modules included inside `included do ... end`, and add accessors defined with class_attribute, mattr_accessor, and cattr_accessor and methods defined with delegate. Methods defined inside `class_methods do ... end` are generated as the concern's ClassMethods. The mailer generator in lib/rails is left out, as its superclass comes from railties, which actionmailer doesn't depend on. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../8.1/actionmailer-generated.rbs | 1618 +++++++++++++++++ 1 file changed, 1618 insertions(+) create mode 100644 gems/actionmailer/8.1/actionmailer-generated.rbs diff --git a/gems/actionmailer/8.1/actionmailer-generated.rbs b/gems/actionmailer/8.1/actionmailer-generated.rbs new file mode 100644 index 000000000..7ccea1b76 --- /dev/null +++ b/gems/actionmailer/8.1/actionmailer-generated.rbs @@ -0,0 +1,1618 @@ +# The generated code is based on Ruby on Rails source code +# You can find the license of Ruby on Rails from following. + +#Copyright (c) David Heinemeier Hansson +# +#Permission is hereby granted, free of charge, to any person obtaining +#a copy of this software and associated documentation files (the +#"Software"), to deal in the Software without restriction, including +#without limitation the rights to use, copy, modify, merge, publish, +#distribute, sublicense, and/or sell copies of the Software, and to +#permit persons to whom the Software is furnished to do so, subject to +#the following conditions: +# +#The above copyright notice and this permission notice shall be +#included in all copies or substantial portions of the Software. +# +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +#EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +#MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +#NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +#LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +#OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +#WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +# :include: ../README.rdoc +module ActionMailer + extend ::ActiveSupport::Autoload + + # Enqueue many emails at once to be delivered through Active Job. + # When the individual job runs, it will send the email using +deliver_now+. + def self.deliver_all_later: (*untyped deliveries, **untyped options) -> untyped + + # Enqueue many emails at once to be delivered through Active Job. + # When the individual job runs, it will send the email using +deliver_now!+. + # That means that the message will be sent bypassing checking +perform_deliveries+ + # and +raise_delivery_errors+, so use with caution. + def self.deliver_all_later!: (*untyped deliveries, **untyped options) -> untyped + + def self.eager_load!: () -> untyped + + def self._deliver_all_later: (untyped delivery_method, *untyped deliveries, **untyped options) -> untyped +end + +module ActionMailer + # = Action Mailer \Base + # + # Action Mailer allows you to send email from your application using a mailer model and views. + # + # == Mailer Models + # + # To use Action Mailer, you need to create a mailer model. + # + # $ bin/rails generate mailer Notifier + # + # The generated model inherits from ApplicationMailer which in turn + # inherits from +ActionMailer::Base+. A mailer model defines methods + # used to generate an email message. In these methods, you can set up variables to be used in + # the mailer views, options on the mail itself such as the :from address, and attachments. + # + # class ApplicationMailer < ActionMailer::Base + # default from: 'from@example.com' + # layout 'mailer' + # end + # + # class NotifierMailer < ApplicationMailer + # default from: 'no-reply@example.com', + # return_path: 'system@example.com' + # + # def welcome(recipient) + # @account = recipient + # mail(to: recipient.email_address_with_name, + # bcc: ["bcc@example.com", "Order Watcher "]) + # end + # end + # + # Within the mailer method, you have access to the following methods: + # + # * attachments[]= - Allows you to add attachments to your email in an intuitive + # manner; attachments['filename.png'] = File.read('path/to/filename.png') + # + # * attachments.inline[]= - Allows you to add an inline attachment to your email + # in the same manner as attachments[]= + # + # * headers[]= - Allows you to specify any header field in your email such + # as headers['X-No-Spam'] = 'True'. Note that declaring a header multiple times + # will add many fields of the same name. Read #headers doc for more information. + # + # * headers(hash) - Allows you to specify multiple headers in your email such + # as headers({'X-No-Spam' => 'True', 'In-Reply-To' => '1234@message.id'}) + # + # * mail - Allows you to specify email to be sent. + # + # The hash passed to the mail method allows you to specify any header that a +Mail::Message+ + # will accept (any valid email header including optional fields). + # + # The +mail+ method, if not passed a block, will inspect your views and send all the views with + # the same name as the method, so the above action would send the +welcome.text.erb+ view + # file as well as the +welcome.html.erb+ view file in a +multipart/alternative+ email. + # + # If you want to explicitly render only certain templates, pass a block: + # + # mail(to: user.email) do |format| + # format.text + # format.html + # end + # + # The block syntax is also useful in providing information specific to a part: + # + # mail(to: user.email) do |format| + # format.text(content_transfer_encoding: "base64") + # format.html + # end + # + # Or even to render a special view: + # + # mail(to: user.email) do |format| + # format.text + # format.html { render "some_other_template" } + # end + # + # == Mailer views + # + # Like Action Controller, each mailer class has a corresponding view directory in which each + # method of the class looks for a template with its name. + # + # To define a template to be used with a mailer, create an .erb file with the same + # name as the method in your mailer model. For example, in the mailer defined above, the template at + # app/views/notifier_mailer/welcome.text.erb would be used to generate the email. + # + # Variables defined in the methods of your mailer model are accessible as instance variables in their + # corresponding view. + # + # Emails by default are sent in plain text, so a sample view for our model example might look like this: + # + # Hi <%= @account.name %>, + # Thanks for joining our service! Please check back often. + # + # You can even use Action View helpers in these views. For example: + # + # You got a new note! + # <%= truncate(@note.body, length: 25) %> + # + # If you need to access the subject, from, or the recipients in the view, you can do that through message object: + # + # You got a new note from <%= message.from %>! + # <%= truncate(@note.body, length: 25) %> + # + # + # == Generating URLs + # + # URLs can be generated in mailer views using url_for or named routes. Unlike controllers from + # Action Pack, the mailer instance doesn't have any context about the incoming request, so you'll need + # to provide all of the details needed to generate a URL. + # + # When using url_for you'll need to provide the :host, :controller, and :action: + # + # <%= url_for(host: "example.com", controller: "welcome", action: "greeting") %> + # + # When using named routes you only need to supply the :host: + # + # <%= users_url(host: "example.com") %> + # + # You should use the named_route_url style (which generates absolute URLs) and avoid using the + # named_route_path style (which generates relative URLs), since clients reading the mail will + # have no concept of a current URL from which to determine a relative path. + # + # It is also possible to set a default host that will be used in all mailers by setting the :host + # option as a configuration option in config/application.rb: + # + # config.action_mailer.default_url_options = { host: "example.com" } + # + # You can also define a default_url_options method on individual mailers to override these + # default settings per-mailer. + # + # By default when config.force_ssl is +true+, URLs generated for hosts will use the HTTPS protocol. + # + # == Sending mail + # + # Once a mailer action and template are defined, you can deliver your message or defer its creation and + # delivery for later: + # + # NotifierMailer.welcome(User.first).deliver_now # sends the email + # mail = NotifierMailer.welcome(User.first) # => an ActionMailer::MessageDelivery object + # mail.deliver_now # generates and sends the email now + # + # The ActionMailer::MessageDelivery class is a wrapper around a delegate that will call + # your method to generate the mail. If you want direct access to the delegator, or +Mail::Message+, + # you can call the message method on the ActionMailer::MessageDelivery object. + # + # NotifierMailer.welcome(User.first).message # => a Mail::Message object + # + # Action Mailer is nicely integrated with Active Job so you can generate and send emails in the background + # (example: outside of the request-response cycle, so the user doesn't have to wait on it): + # + # NotifierMailer.welcome(User.first).deliver_later # enqueue the email sending to Active Job + # + # Note that deliver_later will execute your method from the background job. + # + # You never instantiate your mailer class. Rather, you just call the method you defined on the class itself. + # All instance methods are expected to return a message object to be sent. + # + # == Multipart Emails + # + # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use + # multipart templates, where each template is named after the name of the action, followed by the content + # type. Each such detected template will be added to the message, as a separate part. + # + # For example, if the following templates exist: + # * signup_notification.text.erb + # * signup_notification.html.erb + # * signup_notification.xml.builder + # * signup_notification.yml.erb + # + # Each would be rendered and added as a separate part to the message, with the corresponding content + # type. The content type for the entire message is automatically set to multipart/alternative, + # which indicates that the email contains multiple different representations of the same email + # body. The same instance variables defined in the action are passed to all email templates. + # + # Implicit template rendering is not performed if any attachments or parts have been added to the email. + # This means that you'll have to manually add each part to the email and set the content type of the email + # to multipart/alternative. + # + # == Attachments + # + # Sending attachment in emails is easy: + # + # class NotifierMailer < ApplicationMailer + # def welcome(recipient) + # attachments['free_book.pdf'] = File.read('path/to/file.pdf') + # mail(to: recipient, subject: "New account information") + # end + # end + # + # Which will (if it had both a welcome.text.erb and welcome.html.erb + # template in the view directory), send a complete multipart/mixed email with two parts, + # the first part being a multipart/alternative with the text and HTML email parts inside, + # and the second being a application/pdf with a Base64 encoded copy of the file.pdf book + # with the filename +free_book.pdf+. + # + # If you need to send attachments with no content, you need to create an empty view for it, + # or add an empty body parameter like this: + # + # class NotifierMailer < ApplicationMailer + # def welcome(recipient) + # attachments['free_book.pdf'] = File.read('path/to/file.pdf') + # mail(to: recipient, subject: "New account information", body: "") + # end + # end + # + # You can also send attachments with HTML template, in this case you need to add body, attachments, + # and custom content type like this: + # + # class NotifierMailer < ApplicationMailer + # def welcome(recipient) + # attachments["free_book.pdf"] = File.read("path/to/file.pdf") + # mail(to: recipient, + # subject: "New account information", + # content_type: "text/html", + # body: "Hello there") + # end + # end + # + # == Inline Attachments + # + # You can also specify that a file should be displayed inline with other HTML. This is useful + # if you want to display a corporate logo or a photo. + # + # class NotifierMailer < ApplicationMailer + # def welcome(recipient) + # attachments.inline['photo.png'] = File.read('path/to/photo.png') + # mail(to: recipient, subject: "Here is what we look like") + # end + # end + # + # And then to reference the image in the view, you create a welcome.html.erb file and + # make a call to +image_tag+ passing in the attachment you want to display and then call + # +url+ on the attachment to get the relative content id path for the image source: + # + #

Please Don't Cringe

+ # + # <%= image_tag attachments['photo.png'].url -%> + # + # As we are using Action View's +image_tag+ method, you can pass in any other options you want: + # + #

Please Don't Cringe

+ # + # <%= image_tag attachments['photo.png'].url, alt: 'Our Photo', class: 'photo' -%> + # + # == Observing and Intercepting Mails + # + # Action Mailer provides hooks into the Mail observer and interceptor methods. These allow you to + # register classes that are called during the mail delivery life cycle. + # + # An observer class must implement the :delivered_email(message) method which will be + # called once for every email sent after the email has been sent. + # + # An interceptor class must implement the :delivering_email(message) method which will be + # called before the email is sent, allowing you to make modifications to the email before it hits + # the delivery agents. Your class should make any needed modifications directly to the passed + # in +Mail::Message+ instance. + # + # == Default \Hash + # + # Action Mailer provides some intelligent defaults for your emails, these are usually specified in a + # default method inside the class definition: + # + # class NotifierMailer < ApplicationMailer + # default sender: 'system@example.com' + # end + # + # You can pass in any header value that a +Mail::Message+ accepts. Out of the box, + # +ActionMailer::Base+ sets the following: + # + # * mime_version: "1.0" + # * charset: "UTF-8" + # * content_type: "text/plain" + # * parts_order: [ "text/plain", "text/enriched", "text/html" ] + # + # parts_order and charset are not actually valid +Mail::Message+ header fields, + # but Action Mailer translates them appropriately and sets the correct values. + # + # As you can pass in any header, you need to either quote the header as a string, or pass it in as + # an underscored symbol, so the following will work: + # + # class NotifierMailer < ApplicationMailer + # default 'Content-Transfer-Encoding' => '7bit', + # content_description: 'This is a description' + # end + # + # Finally, Action Mailer also supports passing Proc and Lambda objects into the default hash, + # so you can define methods that evaluate as the message is being generated: + # + # class NotifierMailer < ApplicationMailer + # default 'X-Special-Header' => Proc.new { my_method }, to: -> { @inviter.email_address } + # + # private + # def my_method + # 'some complex call' + # end + # end + # + # Note that the proc/lambda is evaluated right at the start of the mail message generation, so if you + # set something in the default hash using a proc, and then set the same thing inside of your + # mailer method, it will get overwritten by the mailer method. + # + # It is also possible to set these default options that will be used in all mailers through + # the default_options= configuration in config/application.rb: + # + # config.action_mailer.default_options = { from: "no-reply@example.org" } + # + # == \Callbacks + # + # You can specify callbacks using before_action and after_action for configuring your messages, + # and using before_deliver and after_deliver for wrapping the delivery process. + # For example, when you want to add default inline attachments and log delivery for all messages + # sent out by a certain mailer class: + # + # class NotifierMailer < ApplicationMailer + # before_action :add_inline_attachment! + # after_deliver :log_delivery + # + # def welcome + # mail + # end + # + # private + # def add_inline_attachment! + # attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg') + # end + # + # def log_delivery + # Rails.logger.info "Sent email with message id '#{message.message_id}' at #{Time.current}." + # end + # end + # + # Action callbacks in Action Mailer are implemented using + # AbstractController::Callbacks, so you can define and configure + # callbacks in the same manner that you would use callbacks in classes that + # inherit from ActionController::Base. + # + # Note that unless you have a specific reason to do so, you should prefer + # using before_action rather than after_action in your + # Action Mailer classes so that headers are parsed properly. + # + # == Rescuing Errors + # + # +rescue+ blocks inside of a mailer method cannot rescue errors that occur + # outside of rendering -- for example, record deserialization errors in a + # background job, or errors from a third-party mail delivery service. + # + # To rescue errors that occur during any part of the mailing process, use + # {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from]: + # + # class NotifierMailer < ApplicationMailer + # rescue_from ActiveJob::DeserializationError do + # # ... + # end + # + # rescue_from "SomeThirdPartyService::ApiError" do + # # ... + # end + # + # def notify(recipient) + # mail(to: recipient, subject: "Notification") + # end + # end + # + # == Previewing emails + # + # You can preview your email templates visually by adding a mailer preview file to the + # ActionMailer::Base.preview_paths. Since most emails do something interesting + # with database data, you'll need to write some scenarios to load messages with fake data: + # + # class NotifierMailerPreview < ActionMailer::Preview + # def welcome + # NotifierMailer.welcome(User.first) + # end + # end + # + # Methods must return a +Mail::Message+ object which can be generated by calling the mailer + # method without the additional deliver_now / deliver_later. The location of the + # mailer preview directories can be configured using the preview_paths option which has a default + # of test/mailers/previews: + # + # config.action_mailer.preview_paths << "#{Rails.root}/lib/mailer_previews" + # + # An overview of all previews is accessible at http://localhost:3000/rails/mailers + # on a running development server instance. + # + # Previews can also be intercepted in a similar manner as deliveries can be by registering + # a preview interceptor that has a previewing_email method: + # + # class CssInlineStyler + # def self.previewing_email(message) + # # inline CSS styles + # end + # end + # + # config.action_mailer.preview_interceptors :css_inline_styler + # + # Note that interceptors need to be registered both with register_interceptor + # and register_preview_interceptor if they should operate on both sending and + # previewing emails. + # + # == Configuration options + # + # These options are specified on the class level, like + # ActionMailer::Base.raise_delivery_errors = true + # + # * default_options - You can pass this in at a class level as well as within the class itself as + # per the above section. + # + # * logger - the logger is used for generating information on the mailing run if available. + # Can be set to +nil+ for no logging. Compatible with both Ruby's own +Logger+ and Log4r loggers. + # + # * smtp_settings - Allows detailed configuration for :smtp delivery method: + # * :address - Allows you to use a remote mail server. Just change it from its default + # "localhost" setting. + # * :port - On the off chance that your mail server doesn't run on port 25, you can change it. + # * :domain - If you need to specify a HELO domain, you can do it here. + # * :user_name - If your mail server requires authentication, set the username in this setting. + # * :password - If your mail server requires authentication, set the password in this setting. + # * :authentication - If your mail server requires authentication, you need to specify the + # authentication type here. + # This is a symbol and one of :plain (will send the password Base64 encoded), :login (will + # send the password Base64 encoded) or :cram_md5 (combines a Challenge/Response mechanism to exchange + # information and a cryptographic Message Digest 5 algorithm to hash important information) + # * :enable_starttls - Use STARTTLS when connecting to your SMTP server and fail if unsupported. Defaults + # to false. Requires at least version 2.7 of the Mail gem. + # * :enable_starttls_auto - Detects if STARTTLS is enabled in your SMTP server and starts + # to use it. Defaults to true. + # * :openssl_verify_mode - When using TLS, you can set how OpenSSL checks the certificate. This is + # really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name + # of an OpenSSL verify constant ('none' or 'peer') or directly the constant + # (+OpenSSL::SSL::VERIFY_NONE+ or +OpenSSL::SSL::VERIFY_PEER+). + # * :ssl/:tls Enables the SMTP connection to use SMTP/TLS (SMTPS: SMTP over direct TLS connection) + # * :open_timeout Number of seconds to wait while attempting to open a connection. + # * :read_timeout Number of seconds to wait until timing-out a read(2) call. + # + # * sendmail_settings - Allows you to override options for the :sendmail delivery method. + # * :location - The location of the sendmail executable. Defaults to /usr/sbin/sendmail. + # * :arguments - The command line arguments. Defaults to %w[ -i ] with -f sender@address + # added automatically before the message is sent. + # + # * file_settings - Allows you to override options for the :file delivery method. + # * :location - The directory into which emails will be written. Defaults to the application + # tmp/mails. + # + # * raise_delivery_errors - Whether or not errors should be raised if the email fails to be delivered. + # + # * delivery_method - Defines a delivery method. Possible values are :smtp (default), + # :sendmail, :test, and :file. Or you may provide a custom delivery method + # object e.g. +MyOwnDeliveryMethodClass+. See the Mail gem documentation on the interface you need to + # implement for a custom delivery agent. + # + # * perform_deliveries - Determines whether emails are actually sent from Action Mailer when you + # call .deliver on an email message or on an Action Mailer method. This is on by default but can + # be turned off to aid in functional testing. + # + # * deliveries - Keeps an array of all the emails sent out through the Action Mailer with + # delivery_method :test. Most useful for unit and functional testing. + # + # * delivery_job - The job class used with deliver_later. Mailers can set this to use a + # custom delivery job. Defaults to +ActionMailer::MailDeliveryJob+. + # + # * deliver_later_queue_name - The queue name used by deliver_later with the default + # delivery_job. Mailers can set this to use a custom queue name. + class Base + def self.default_params: () -> untyped + def self.default_params=: (untyped value) -> untyped + def self.default_params?: () -> bool + def default_params: () -> untyped + def default_params?: () -> bool + def default_params=: (untyped value) -> untyped + + include ::ActionMailer::Callbacks + + include ::ActionMailer::DeliveryMethods + + include ::ActionMailer::QueuedDelivery + + include ::ActionMailer::Rescuable + + include ::ActionMailer::Parameterized + + include ::ActionMailer::Previews + + include ::ActionMailer::FormBuilder + + include ::AbstractController::Rendering + + include ::AbstractController::Logger + + include ::AbstractController::Helpers + + include ::AbstractController::Translation + + include ::AbstractController::AssetPaths + + include ::AbstractController::Callbacks + + include ::AbstractController::Caching + + include ::ActionView::Layouts + + PROTECTED_IVARS: untyped + + # Register one or more Observers which will be notified when mail is delivered. + def self.register_observers: (*untyped observers) -> untyped + + # Unregister one or more previously registered Observers. + def self.unregister_observers: (*untyped observers) -> untyped + + # Register one or more Interceptors which will be called before mail is sent. + def self.register_interceptors: (*untyped interceptors) -> untyped + + # Unregister one or more previously registered Interceptors. + def self.unregister_interceptors: (*untyped interceptors) -> untyped + + # Register an Observer which will be notified when mail is delivered. + # Either a class, string, or symbol can be passed in as the Observer. + # If a string or symbol is passed in it will be camelized and constantized. + def self.register_observer: (untyped observer) -> untyped + + # Unregister a previously registered Observer. + # Either a class, string, or symbol can be passed in as the Observer. + # If a string or symbol is passed in it will be camelized and constantized. + def self.unregister_observer: (untyped observer) -> untyped + + # Register an Interceptor which will be called before mail is sent. + # Either a class, string, or symbol can be passed in as the Interceptor. + # If a string or symbol is passed in it will be camelized and constantized. + def self.register_interceptor: (untyped interceptor) -> untyped + + # Unregister a previously registered Interceptor. + # Either a class, string, or symbol can be passed in as the Interceptor. + # If a string or symbol is passed in it will be camelized and constantized. + def self.unregister_interceptor: (untyped interceptor) -> untyped + + def self.observer_class_for: (untyped value) -> untyped + + # Returns the name of the current mailer. This method is also being used as a path for a view lookup. + # If this is an anonymous mailer, this method will return +anonymous+ instead. + def self.mailer_name: () -> untyped + + # Allows to set the name of current mailer. + attr_writer self.mailer_name: untyped + + alias self.controller_path self.mailer_name + + alias self.default_options= self.default + + # Wraps an email delivery inside of ActiveSupport::Notifications instrumentation. + # + # This method is actually called by the +Mail::Message+ object itself + # through a callback when you call :deliver on the +Mail::Message+, + # calling +deliver_mail+ directly and passing a +Mail::Message+ will do + # nothing except tell the logger you sent the email. + def self.deliver_mail: (untyped mail) { () -> untyped } -> untyped + + def self.mail: (*untyped, **untyped) ?{ (?) -> untyped } -> untyped + + def self.action_methods: () -> untyped + + def self.set_payload_for_mail: (untyped payload, untyped mail) -> untyped + + def self.method_missing: (untyped method_name, *untyped, **untyped) ?{ (?) -> untyped } -> untyped + + def self.respond_to_missing?: (untyped method, ?bool include_all) -> untyped + + def initialize: () -> void + + def process: (untyped method_name, *untyped args) -> untyped + + class NullMail + def body: () -> "" + + def header: () -> ::Hash[untyped, untyped] + + def respond_to?: (untyped string, ?bool include_all) -> true + + def method_missing: (*untyped, **untyped) ?{ (?) -> untyped } -> nil + end + + # Returns the name of the mailer object. + def mailer_name: () -> untyped + + # Allows you to add attachments to an email, like so: + # + # mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg') + # + # If you do this, then Mail will take the file name and work out the mime type. + # It will also set the +Content-Type+, +Content-Disposition+, and +Content-Transfer-Encoding+, + # and encode the contents of the attachment in Base64. + # + # You can also specify overrides if you want by passing a hash instead of a string: + # + # mail.attachments['filename.jpg'] = {mime_type: 'application/gzip', + # content: File.read('/path/to/filename.jpg')} + # + # If you want to use encoding other than Base64 then you will need to pass encoding + # type along with the pre-encoded content as Mail doesn't know how to decode the + # data: + # + # file_content = SpecialEncode(File.read('/path/to/filename.jpg')) + # mail.attachments['filename.jpg'] = {mime_type: 'application/gzip', + # encoding: 'SpecialEncoding', + # content: file_content } + # + # You can also search for specific attachments: + # + # # By Filename + # mail.attachments['filename.jpg'] # => Mail::Part object or nil + # + # # or by index + # mail.attachments[0] # => Mail::Part (first attachment) + # + def attachments: () -> untyped + + class LateAttachmentsProxy < ::SimpleDelegator + def inline: () -> self + + def []=: (untyped _name, untyped _content) -> untyped + + private + + def _raise_error: () -> untyped + end + + private + + # Used by #mail to set the content type of the message. + # + # It will use the given +user_content_type+, or multipart if the mail + # message has any attachments. If the attachments are inline, the content + # type will be "multipart/related", otherwise "multipart/mixed". + # + # If there is no content type passed in via headers, and there are no + # attachments, or the message is multipart, then the default content type is + # used. + def set_content_type: (untyped m, untyped user_content_type, untyped class_default) -> untyped + + # Emails do not support relative path links. + def self.supports_path?: () -> false + + def apply_defaults: (untyped headers) -> untyped + + def compute_default: (untyped value) -> untyped + + def assign_headers_to_message: (untyped message, untyped headers) -> untyped + + def collect_responses: (untyped headers) ?{ (?) -> untyped } -> untyped + + def collect_responses_from_block: (untyped headers) { (untyped) -> untyped } -> untyped + + def collect_responses_from_text: (untyped headers) -> ::Array[{ body: untyped, content_type: untyped }] + + def collect_responses_from_templates: (untyped headers) -> untyped + + def each_template: (untyped paths, untyped name) { (?) -> untyped } -> untyped + + def wrap_inline_attachments: (untyped message) -> (untyped | nil) + + def create_parts_from_responses: (untyped m, untyped responses) -> untyped + + def insert_part: (untyped container, untyped response, untyped charset) -> untyped + + # This and #instrument_name is for caching instrument + def instrument_payload: (untyped key) -> { mailer: untyped, key: untyped } + + def instrument_name: () -> "action_mailer" + + def _protected_ivars: () -> untyped + end +end + +module ActionMailer + module Callbacks + include ::ActiveSupport::Callbacks + + extend ::ActiveSupport::Concern + + DEFAULT_INTERNAL_METHODS: ::Array[:_run_deliver_callbacks] + + module ClassMethods + # Defines a callback that will get called right before the + # message is sent to the delivery method. + def before_deliver: (*untyped filters) { (?) -> untyped } -> untyped + + # Defines a callback that will get called right after the + # message's delivery method is finished. + def after_deliver: (*untyped filters) { (?) -> untyped } -> untyped + + # Defines a callback that will get called around the message's deliver method. + def around_deliver: (*untyped filters) { (?) -> untyped } -> untyped + + def internal_methods: () -> untyped + end + end +end + +module ActionMailer + class Collector + include ::AbstractController::Collector + + attr_reader responses: untyped + + def initialize: (untyped context) { (?) -> untyped } -> void + + def any: (*untyped args) { (?) -> untyped } -> untyped + + alias all any + + def custom: (untyped mime, ?::Hash[untyped, untyped] options) ?{ () -> untyped } -> untyped + end +end + +module ActionMailer + # = Action Mailer \DeliveryMethods + # + # This module handles everything related to mail delivery, from registering + # new delivery methods to configuring the mail object to be sent. + module DeliveryMethods + def raise_delivery_errors: () -> untyped + + def raise_delivery_errors=: (untyped value) -> untyped + + def perform_deliveries: () -> untyped + + def perform_deliveries=: (untyped value) -> untyped + + def delivery_methods: () -> untyped + + def delivery_methods?: () -> bool + + def delivery_methods=: (untyped value) -> untyped + + def delivery_method: () -> untyped + + def delivery_method?: () -> bool + + def delivery_method=: (untyped value) -> untyped + + extend ::ActiveSupport::Concern + + # Helpers for creating and wrapping delivery behavior, used by DeliveryMethods. + module ClassMethods + def raise_delivery_errors: () -> untyped + + def raise_delivery_errors=: (untyped value) -> untyped + + def perform_deliveries: () -> untyped + + def perform_deliveries=: (untyped value) -> untyped + + def delivery_methods: () -> untyped + + def delivery_methods=: (untyped value) -> untyped + + def delivery_methods?: () -> bool + + def delivery_method: () -> untyped + + def delivery_method=: (untyped value) -> untyped + + def delivery_method?: () -> bool + + def deliveries: (*untyped, **untyped) ?{ (*untyped) -> untyped } -> untyped + + def deliveries=: (*untyped, **untyped) ?{ (*untyped) -> untyped } -> untyped + + # Adds a new delivery method through the given class using the given + # symbol as alias and the default options supplied. + # + # add_delivery_method :sendmail, Mail::Sendmail, + # location: '/usr/sbin/sendmail', + # arguments: %w[ -i ] + def add_delivery_method: (untyped symbol, untyped klass, ?::Hash[untyped, untyped] default_options) -> untyped + + def wrap_delivery_behavior: (untyped mail, ?untyped? method, ?untyped? options) -> untyped + end + + def wrap_delivery_behavior!: (*untyped args) -> untyped + end +end + +module ActionMailer + def self.deprecator: () -> untyped +end + +module ActionMailer + # = Action Mailer Form Builder + # + # Override the default form builder for all views rendered by this + # mailer and any of its descendants. Accepts a subclass of + # ActionView::Helpers::FormBuilder. + # + # While emails typically will not include forms, this can be used + # by views that are shared between controllers and mailers. + # + # For more information, see +ActionController::FormBuilder+. + module FormBuilder + extend ::ActiveSupport::Concern + + module ClassMethods + def _default_form_builder: () -> untyped + + def _default_form_builder=: (untyped value) -> untyped + + def _default_form_builder?: () -> bool + + # Set the form builder to be used as the default for all forms + # in the views rendered by this mailer and its subclasses. + # + # ==== Parameters + # * builder - Default form builder. Accepts a subclass of ActionView::Helpers::FormBuilder + def default_form_builder: (untyped builder) -> untyped + end + + # Default form builder for the mailer + def default_form_builder: () -> untyped + end +end + +module ActionMailer + # Returns the currently loaded version of Action Mailer as a +Gem::Version+. + def self.gem_version: () -> untyped + + module VERSION + MAJOR: 8 + + MINOR: 1 + + TINY: 4 + + PRE: nil + + STRING: untyped + end +end + +module ActionMailer + # = Action Mailer \InlinePreviewInterceptor + # + # Implements a mailer preview interceptor that converts image tag src attributes + # that use inline +cid:+ style URLs to +data:+ style URLs so that they are visible + # when previewing an HTML email in a web browser. + # + # This interceptor is enabled by default. To disable it, delete it from the + # ActionMailer::Base.preview_interceptors array: + # + # ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor) + # + class InlinePreviewInterceptor + PATTERN: ::Regexp + + include ::Base64 + + def self.previewing_email: (untyped message) -> untyped + + def initialize: (untyped message) -> void + + def transform!: () -> untyped + + private + + attr_reader message: untyped + + def html_part: () -> untyped + + def data_url: (untyped part) -> ::String + + def find_part: (untyped cid) -> untyped + end +end + +module ActionMailer + class LogSubscriber < ::ActiveSupport::LogSubscriber + # An email was delivered. + def deliver: (untyped event) -> untyped + + # An email was generated. + def process: (untyped event) -> untyped + + # Use the logger configured for ActionMailer::Base. + def logger: () -> untyped + end +end + +module ActionMailer + # = Action Mailer \MailDeliveryJob + # + # The +ActionMailer::MailDeliveryJob+ class is used when you + # want to send emails outside of the request-response cycle. It supports + # sending either parameterized or normal mail. + # + # Exceptions are rescued and handled by the mailer class. + class MailDeliveryJob < ::ActiveJob::Base + def perform: (untyped mailer, untyped mail_method, untyped delivery_method, args: untyped, ?kwargs: untyped?, ?params: untyped?) -> untyped + + private + + # "Deserialize" the mailer class name by hand in case another argument + # (like a Global ID reference) raised DeserializationError. + def mailer_class: () -> (untyped | nil) + + def handle_exception_with_mailer_class: (untyped exception) -> untyped + end +end + +module ActionMailer + # = Action Mailer \MailHelper + # + # Provides helper methods for ActionMailer::Base that can be used for easily + # formatting messages, accessing mailer or message instances, and the + # attachments list. + module MailHelper + # Take the text and format it, indented two spaces for each line, and + # wrapped at 72 columns: + # + # text = <<-TEXT + # This is + # the paragraph. + # + # * item1 * item2 + # TEXT + # + # block_format text + # # => " This is the paragraph.\n\n * item1\n * item2\n" + def block_format: (untyped text) -> untyped + + # Access the mailer instance. + def mailer: () -> untyped + + # Access the message instance. + def message: () -> untyped + + # Access the message attachments list. + def attachments: () -> untyped + + # Returns +text+ wrapped at +len+ columns and indented +indent+ spaces. + # By default column length +len+ equals 72 characters and indent + # +indent+ equal two spaces. + # + # my_text = 'Here is a sample text with more than 40 characters' + # + # format_paragraph(my_text, 25, 4) + # # => " Here is a sample text\n with more than 40\n characters" + def format_paragraph: (untyped text, ?::Integer len, ?::Integer indent) -> untyped + end +end + +module ActionMailer + # = Action Mailer \MessageDelivery + # + # The +ActionMailer::MessageDelivery+ class is used by + # ActionMailer::Base when creating a new mailer. + # MessageDelivery is a wrapper (+Delegator+ subclass) around a lazy + # created +Mail::Message+. You can get direct access to the + # +Mail::Message+, deliver the email or schedule the email to be sent + # through Active Job. + # + # Notifier.welcome(User.first) # an ActionMailer::MessageDelivery object + # Notifier.welcome(User.first).deliver_now # sends the email + # Notifier.welcome(User.first).deliver_later # enqueue email delivery as a job through Active Job + # Notifier.welcome(User.first).message # a Mail::Message object + class MessageDelivery < ::Delegator + attr_reader mailer_class: untyped + + attr_reader action: untyped + + attr_reader params: untyped + + attr_reader args: untyped + + def initialize: (untyped mailer_class, untyped action, *untyped args) -> void + + # Method calls are delegated to the Mail::Message that's ready to deliver. + def __getobj__: () -> untyped + + # Unused except for delegator internals (dup, marshalling). + def __setobj__: (untyped mail_message) -> untyped + + # Was the delegate loaded, causing the mailer action to be processed? + def processed?: () -> untyped + + # Enqueues the email to be delivered through Active Job. When the + # job runs it will send the email using +deliver_now!+. That means + # that the message will be sent bypassing checking +perform_deliveries+ + # and +raise_delivery_errors+, so use with caution. + # + # Notifier.welcome(User.first).deliver_later! + # Notifier.welcome(User.first).deliver_later!(wait: 1.hour) + # Notifier.welcome(User.first).deliver_later!(wait_until: 10.hours.from_now) + # Notifier.welcome(User.first).deliver_later!(priority: 10) + # + # Options: + # + # * :wait - Enqueue the email to be delivered with a delay + # * :wait_until - Enqueue the email to be delivered at (after) a specific date / time + # * :queue - Enqueue the email on the specified queue + # * :priority - Enqueues the email with the specified priority + # + # By default, the email will be enqueued using ActionMailer::MailDeliveryJob on + # the default queue. Mailer classes can customize the queue name used for the default + # job by assigning a +deliver_later_queue_name+ class variable, or provide a custom job + # by assigning a +delivery_job+. When a custom job is used, it controls the queue name. + # + # class AccountRegistrationMailer < ApplicationMailer + # self.delivery_job = RegistrationDeliveryJob + # end + def deliver_later!: (?::Hash[untyped, untyped] options) -> untyped + + private + + # Returns the processed Mailer instance. We keep this instance + # on hand so we can run callbacks and delegate exception handling to it. + def processed_mailer: () -> untyped + + def enqueue_delivery: (untyped delivery_method, ?::Hash[untyped, untyped] options) -> untyped + end +end + +module ActionMailer + # = Action Mailer \Parameterized + # + # Provides the option to parameterize mailers in order to share instance variable + # setup, processing, and common headers. + # + # Consider this example that does not use parameterization: + # + # class InvitationsMailer < ApplicationMailer + # def account_invitation(inviter, invitee) + # @account = inviter.account + # @inviter = inviter + # @invitee = invitee + # + # subject = "#{@inviter.name} invited you to their Basecamp (#{@account.name})" + # + # mail \ + # subject: subject, + # to: invitee.email_address, + # from: common_address(inviter), + # reply_to: inviter.email_address_with_name + # end + # + # def project_invitation(project, inviter, invitee) + # @account = inviter.account + # @project = project + # @inviter = inviter + # @invitee = invitee + # @summarizer = ProjectInvitationSummarizer.new(@project.bucket) + # + # subject = "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})" + # + # mail \ + # subject: subject, + # to: invitee.email_address, + # from: common_address(inviter), + # reply_to: inviter.email_address_with_name + # end + # + # def bulk_project_invitation(projects, inviter, invitee) + # @account = inviter.account + # @projects = projects.sort_by(&:name) + # @inviter = inviter + # @invitee = invitee + # + # subject = "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})" + # + # mail \ + # subject: subject, + # to: invitee.email_address, + # from: common_address(inviter), + # reply_to: inviter.email_address_with_name + # end + # end + # + # InvitationsMailer.account_invitation(person_a, person_b).deliver_later + # + # Using parameterized mailers, this can be rewritten as: + # + # class InvitationsMailer < ApplicationMailer + # before_action { @inviter, @invitee = params[:inviter], params[:invitee] } + # before_action { @account = params[:inviter].account } + # + # default to: -> { @invitee.email_address }, + # from: -> { common_address(@inviter) }, + # reply_to: -> { @inviter.email_address_with_name } + # + # def account_invitation + # mail subject: "#{@inviter.name} invited you to their Basecamp (#{@account.name})" + # end + # + # def project_invitation + # @project = params[:project] + # @summarizer = ProjectInvitationSummarizer.new(@project.bucket) + # + # mail subject: "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})" + # end + # + # def bulk_project_invitation + # @projects = params[:projects].sort_by(&:name) + # + # mail subject: "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})" + # end + # end + # + # InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later + module Parameterized + extend ::ActiveSupport::Concern + + module ClassMethods + # Provide the parameters to the mailer in order to use them in the instance methods and callbacks. + # + # InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later + # + # See Parameterized documentation for full example. + def with: (untyped params) -> untyped + end + + class Mailer + def initialize: (untyped mailer, untyped params) -> void + + private + + def method_missing: (untyped method_name, *untyped, **untyped) ?{ (?) -> untyped } -> untyped + + def respond_to_missing?: (untyped method, ?bool include_all) -> untyped + end + + class MessageDelivery < ::ActionMailer::MessageDelivery + def initialize: (untyped mailer_class, untyped action, untyped params, *untyped, **untyped) ?{ (?) -> untyped } -> void + + private + + def processed_mailer: () -> untyped + + def enqueue_delivery: (untyped delivery_method, ?::Hash[untyped, untyped] options) -> untyped + end + end +end + +module ActionMailer + module Previews + def preview_paths: () -> untyped + + def show_previews: () -> untyped + + def preview_interceptors: () -> untyped + + extend ::ActiveSupport::Concern + + module ClassMethods + def preview_paths: () -> untyped + + def preview_paths=: (untyped value) -> untyped + + def show_previews: () -> untyped + + def show_previews=: (untyped value) -> untyped + + def preview_interceptors: () -> untyped + + def preview_interceptors=: (untyped value) -> untyped + + # Register one or more Interceptors which will be called before mail is previewed. + def register_preview_interceptors: (*untyped interceptors) -> untyped + + # Unregister one or more previously registered Interceptors. + def unregister_preview_interceptors: (*untyped interceptors) -> untyped + + # Register an Interceptor which will be called before mail is previewed. + # Either a class or a string can be passed in as the Interceptor. If a + # string is passed in it will be constantized. + def register_preview_interceptor: (untyped interceptor) -> untyped + + # Unregister a previously registered Interceptor. + # Either a class or a string can be passed in as the Interceptor. If a + # string is passed in it will be constantized. + def unregister_preview_interceptor: (untyped interceptor) -> untyped + + private + + def interceptor_class_for: (untyped interceptor) -> untyped + end + end + + class Preview + extend ::ActiveSupport::DescendantsTracker + + attr_reader params: untyped + + def initialize: (?::Hash[untyped, untyped] params) -> void + + # Returns all mailer preview classes. + def self.all: () -> untyped + + # Returns the mail object for the given email name. The registered preview + # interceptors will be informed so that they can transform the message + # as they would if the mail was actually being delivered. + def self.call: (untyped email, ?::Hash[untyped, untyped] params) -> untyped + + # Returns all of the available email previews. + def self.emails: () -> untyped + + # Returns +true+ if the email exists. + def self.email_exists?: (untyped email) -> untyped + + # Returns +true+ if the preview exists. + def self.exists?: (untyped preview) -> untyped + + # Find a mailer preview by its underscored class name. + def self.find: (untyped preview) -> untyped + + # Returns the underscored name of the mailer preview without the suffix. + def self.preview_name: () -> untyped + + def self.load_previews: () -> untyped + + def self.preview_paths: () -> untyped + + def self.show_previews: () -> untyped + + def self.inform_preview_interceptors: (untyped message) -> untyped + end +end + +module ActionMailer + module QueuedDelivery + def delivery_job: () -> untyped + + def delivery_job?: () -> bool + + def delivery_job=: (untyped value) -> untyped + + def deliver_later_queue_name: () -> untyped + + def deliver_later_queue_name?: () -> bool + + def deliver_later_queue_name=: (untyped value) -> untyped + + extend ::ActiveSupport::Concern + end +end + +module ActionMailer + class Railtie < ::Rails::Railtie + end +end + +module ActionMailer + # = Action Mailer \Rescuable + # + # Provides + # {rescue_from}[rdoc-ref:ActiveSupport::Rescuable::ClassMethods#rescue_from] + # for mailers. Wraps mailer action processing, mail job processing, and mail + # delivery to handle configured errors. + module Rescuable + extend ::ActiveSupport::Concern + + include ::ActiveSupport::Rescuable + + def handle_exceptions: () { () -> untyped } -> untyped + + private + + def process: (*untyped, **untyped) ?{ (?) -> untyped } -> untyped + end +end + +module ActionMailer + class StructuredEventSubscriber < ::ActiveSupport::StructuredEventSubscriber + # An email was delivered. + def deliver: (untyped event) -> untyped + + # An email was generated. + def process: (untyped event) -> untyped + end +end + +module ActionMailer + class NonInferrableMailerError < ::StandardError + def initialize: (untyped name) -> void + end + + class TestCase < ::ActiveSupport::TestCase + module ClearTestDeliveries + extend ::ActiveSupport::Concern + + private + + def clear_test_deliveries: () -> (untyped | nil) + end + + module Behavior + def _mailer_class: () -> untyped + + def _mailer_class?: () -> bool + + def _mailer_class=: (untyped value) -> untyped + + extend ::ActiveSupport::Concern + + include ::ActiveSupport::Testing::ConstantLookup + + include ::ActionMailer::TestHelper + + include ::Rails::Dom::Testing::Assertions::SelectorAssertions + + include ::Rails::Dom::Testing::Assertions::DomAssertions + + module ClassMethods + def _mailer_class: () -> untyped + + def _mailer_class=: (untyped value) -> untyped + + def _mailer_class?: () -> bool + + def tests: (untyped mailer) -> untyped + + def mailer_class: () -> untyped + + def determine_default_mailer: (untyped name) -> untyped + end + + # Reads the fixture file for the given mailer. + # + # This is useful when testing mailers by being able to write the body of + # an email inside a fixture. See the testing guide for a concrete example: + # https://guides.rubyonrails.org/testing.html#revenge-of-the-fixtures + def read_fixture: (untyped action) -> untyped + + private + + def initialize_test_deliveries: () -> untyped + + def restore_test_deliveries: () -> untyped + + def set_delivery_method: (untyped method) -> untyped + + def restore_delivery_method: () -> untyped + + def set_expected_mail: () -> untyped + + def charset: () -> "UTF-8" + + def encode: (untyped subject) -> untyped + end + + include ::ActionMailer::TestCase::Behavior + end +end + +module ActionMailer + # Provides helper methods for testing Action Mailer, including #assert_emails + # and #assert_no_emails. + module TestHelper + include ::ActiveJob::TestHelper + + # Asserts that the number of emails sent matches the given number. + # + # def test_emails + # assert_emails 0 + # ContactMailer.welcome.deliver_now + # assert_emails 1 + # ContactMailer.welcome.deliver_now + # assert_emails 2 + # end + # + # If a block is passed, that block should cause the specified number of + # emails to be sent. + # + # def test_emails_again + # assert_emails 1 do + # ContactMailer.welcome.deliver_now + # end + # + # assert_emails 2 do + # ContactMailer.welcome.deliver_now + # ContactMailer.welcome.deliver_later + # end + # end + def assert_emails: (untyped number) ?{ (?) -> untyped } -> untyped + + # Asserts that no emails have been sent. + # + # def test_emails + # assert_no_emails + # ContactMailer.welcome.deliver_now + # assert_emails 1 + # end + # + # If a block is passed, that block should not cause any emails to be sent. + # + # def test_emails_again + # assert_no_emails do + # # No emails should be sent from this block + # end + # end + # + # Note: This assertion is simply a shortcut for: + # + # assert_emails 0, &block + def assert_no_emails: () { (?) -> untyped } -> untyped + + # Asserts that the number of emails enqueued for later delivery matches + # the given number. + # + # def test_emails + # assert_enqueued_emails 0 + # ContactMailer.welcome.deliver_later + # assert_enqueued_emails 1 + # ContactMailer.welcome.deliver_later + # assert_enqueued_emails 2 + # end + # + # If a block is passed, that block should cause the specified number of + # emails to be enqueued. + # + # def test_emails_again + # assert_enqueued_emails 1 do + # ContactMailer.welcome.deliver_later + # end + # + # assert_enqueued_emails 2 do + # ContactMailer.welcome.deliver_later + # ContactMailer.welcome.deliver_later + # end + # end + def assert_enqueued_emails: (untyped number) { (?) -> untyped } -> untyped + + # Asserts that a specific email has been enqueued, optionally + # matching arguments and/or params. + # + # def test_email + # ContactMailer.welcome.deliver_later + # assert_enqueued_email_with ContactMailer, :welcome + # end + # + # def test_email_with_parameters + # ContactMailer.with(greeting: "Hello").welcome.deliver_later + # assert_enqueued_email_with ContactMailer, :welcome, params: { greeting: "Hello" } + # end + # + # def test_email_with_arguments + # ContactMailer.welcome("Hello", "Goodbye").deliver_later + # assert_enqueued_email_with ContactMailer, :welcome, args: ["Hello", "Goodbye"] + # end + # + # def test_email_with_named_arguments + # ContactMailer.welcome(greeting: "Hello", farewell: "Goodbye").deliver_later + # assert_enqueued_email_with ContactMailer, :welcome, args: [{ greeting: "Hello", farewell: "Goodbye" }] + # end + # + # def test_email_with_parameters_and_arguments + # ContactMailer.with(greeting: "Hello").welcome("Cheers", "Goodbye").deliver_later + # assert_enqueued_email_with ContactMailer, :welcome, params: { greeting: "Hello" }, args: ["Cheers", "Goodbye"] + # end + # + # def test_email_with_parameters_and_named_arguments + # ContactMailer.with(greeting: "Hello").welcome(farewell: "Goodbye").deliver_later + # assert_enqueued_email_with ContactMailer, :welcome, params: { greeting: "Hello" }, args: [{farewell: "Goodbye"}] + # end + # + # def test_email_with_parameterized_mailer + # ContactMailer.with(greeting: "Hello").welcome.deliver_later + # assert_enqueued_email_with ContactMailer.with(greeting: "Hello"), :welcome + # end + # + # def test_email_with_matchers + # ContactMailer.with(greeting: "Hello").welcome("Cheers", "Goodbye").deliver_later + # assert_enqueued_email_with ContactMailer, :welcome, + # params: ->(params) { /hello/i.match?(params[:greeting]) }, + # args: ->(args) { /cheers/i.match?(args[0]) } + # end + # + # If a block is passed, that block should cause the specified email + # to be enqueued. + # + # def test_email_in_block + # assert_enqueued_email_with ContactMailer, :welcome do + # ContactMailer.welcome.deliver_later + # end + # end + def assert_enqueued_email_with: (untyped mailer, untyped method, ?params: untyped?, ?args: untyped?, ?queue: untyped?) { (?) -> untyped } -> untyped + + # Asserts that no emails are enqueued for later delivery. + # + # def test_no_emails + # assert_no_enqueued_emails + # ContactMailer.welcome.deliver_later + # assert_enqueued_emails 1 + # end + # + # If a block is provided, it should not cause any emails to be enqueued. + # + # def test_no_emails + # assert_no_enqueued_emails do + # # No emails should be enqueued from this block + # end + # end + def assert_no_enqueued_emails: () { (?) -> untyped } -> untyped + + # Delivers all enqueued emails. If a block is given, delivers all of the emails + # that were enqueued throughout the duration of the block. If a block is + # not given, delivers all the enqueued emails up to this point in the test. + # + # def test_deliver_enqueued_emails + # deliver_enqueued_emails do + # ContactMailer.welcome.deliver_later + # end + # + # assert_emails 1 + # end + # + # def test_deliver_enqueued_emails_without_block + # ContactMailer.welcome.deliver_later + # + # deliver_enqueued_emails + # + # assert_emails 1 + # end + # + # If the +:queue+ option is specified, + # then only the emails(s) enqueued to a specific queue will be performed. + # + # def test_deliver_enqueued_emails_with_queue + # deliver_enqueued_emails queue: :external_mailers do + # CustomerMailer.deliver_later_queue_name = :external_mailers + # CustomerMailer.welcome.deliver_later # will be performed + # EmployeeMailer.deliver_later_queue_name = :internal_mailers + # EmployeeMailer.welcome.deliver_later # will not be performed + # end + # + # assert_emails 1 + # end + # + # If the +:at+ option is specified, then only delivers emails enqueued to deliver + # immediately or before the given time. + def deliver_enqueued_emails: (?queue: untyped?, ?at: untyped?) { (?) -> untyped } -> untyped + + # Returns any emails that are sent in the block. + # + # def test_emails + # emails = capture_emails do + # ContactMailer.welcome.deliver_now + # end + # assert_equal "Hi there", emails.first.subject + # + # emails = capture_emails do + # ContactMailer.welcome.deliver_now + # ContactMailer.welcome.deliver_later + # end + # assert_equal "Hi there", emails.first.subject + # end + def capture_emails: () { (?) -> untyped } -> untyped + + private + + def delivery_job_filter: (untyped job) -> untyped + end +end + +module ActionMailer + # Returns the currently loaded version of Action Mailer as a + # +Gem::Version+. + def self.version: () -> untyped +end + +module ActionMailer + module Rescuable + module ClassMethods + def handle_exception: (untyped exception) -> untyped + end + end +end From fa45791f9827e0283259914f27b7fbd8e61aa193 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 19:12:17 +0900 Subject: [PATCH 10/16] actionmailer: Remove the fake Mail::Message actionmailer depends on mail, whose RBS in this repository declares it. Co-Authored-By: Claude Opus 5.5 (1M context) --- gems/actionmailer/8.1/mail-message.rbs | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 gems/actionmailer/8.1/mail-message.rbs diff --git a/gems/actionmailer/8.1/mail-message.rbs b/gems/actionmailer/8.1/mail-message.rbs deleted file mode 100644 index 5ceaf2ef5..000000000 --- a/gems/actionmailer/8.1/mail-message.rbs +++ /dev/null @@ -1,4 +0,0 @@ -module Mail - class Message - end -end From 1a3052f2eed8eb267dbfbffae8bbfe7cea4862d2 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 19:12:35 +0900 Subject: [PATCH 11/16] actionmailer: Rename MessageDelivery#processed to processed? MessageDelivery has no processed method. processed? returns the processed mailer or message, or nil. Co-Authored-By: Claude Opus 5.5 (1M context) --- gems/actionmailer/8.1/_test/railsguides.rb | 3 +++ gems/actionmailer/8.1/actionmailer-generated.rbs | 3 --- gems/actionmailer/8.1/actionmailer.rbs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gems/actionmailer/8.1/_test/railsguides.rb b/gems/actionmailer/8.1/_test/railsguides.rb index 514a5b92e..f5c486021 100644 --- a/gems/actionmailer/8.1/_test/railsguides.rb +++ b/gems/actionmailer/8.1/_test/railsguides.rb @@ -37,3 +37,6 @@ def welcome(email, username) NotifierMailer.welcome("first user").message NotifierMailer.welcome("first user").deliver_later end + +delivery = RailsGuides::NotifierMailer.welcome("first user") +delivery.deliver_now unless delivery.processed? diff --git a/gems/actionmailer/8.1/actionmailer-generated.rbs b/gems/actionmailer/8.1/actionmailer-generated.rbs index 7ccea1b76..cdb8b38cd 100644 --- a/gems/actionmailer/8.1/actionmailer-generated.rbs +++ b/gems/actionmailer/8.1/actionmailer-generated.rbs @@ -1022,9 +1022,6 @@ module ActionMailer # Unused except for delegator internals (dup, marshalling). def __setobj__: (untyped mail_message) -> untyped - # Was the delegate loaded, causing the mailer action to be processed? - def processed?: () -> untyped - # Enqueues the email to be delivered through Active Job. When the # job runs it will send the email using +deliver_now!+. That means # that the message will be sent bypassing checking +perform_deliveries+ diff --git a/gems/actionmailer/8.1/actionmailer.rbs b/gems/actionmailer/8.1/actionmailer.rbs index 37073cd95..4581df5ab 100644 --- a/gems/actionmailer/8.1/actionmailer.rbs +++ b/gems/actionmailer/8.1/actionmailer.rbs @@ -42,6 +42,6 @@ module ActionMailer def message: () -> Mail::Message - def processed: () -> bool + def processed?: () -> boolish end end From d436c62c1f784c9faa4926e8b8a9aac50b91d4d8 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 19:13:53 +0900 Subject: [PATCH 12/16] actionmailer: Make blocks optional where calling without one is valid rbs prototype marks a block as required unless the method checks it. These forward the block or store it: the callbacks with method names (before_deliver :foo), Collector#any, and the test assertions such as assert_no_emails and assert_enqueued_emails, which check the current deliveries when called without a block. Co-Authored-By: Claude Opus 5.5 (1M context) --- gems/actionmailer/8.1/_test/railsguides.rb | 11 ++++++++++ gems/actionmailer/8.1/_test/railsguides.rbs | 4 ++++ .../8.1/actionmailer-generated.rbs | 20 +++++++++---------- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/gems/actionmailer/8.1/_test/railsguides.rb b/gems/actionmailer/8.1/_test/railsguides.rb index f5c486021..4863dca4a 100644 --- a/gems/actionmailer/8.1/_test/railsguides.rb +++ b/gems/actionmailer/8.1/_test/railsguides.rb @@ -40,3 +40,14 @@ def welcome(email, username) delivery = RailsGuides::NotifierMailer.welcome("first user") delivery.deliver_now unless delivery.processed? + +class NotifierMailerTest < ActionMailer::TestCase + def test_welcome + assert_no_emails + assert_no_enqueued_emails + RailsGuides::NotifierMailer.welcome("first user").deliver_later + assert_enqueued_emails 1 + assert_enqueued_email_with RailsGuides::NotifierMailer, :welcome, args: ["first user"] + deliver_enqueued_emails + end +end diff --git a/gems/actionmailer/8.1/_test/railsguides.rbs b/gems/actionmailer/8.1/_test/railsguides.rbs index ce3b18404..0fc082bbb 100644 --- a/gems/actionmailer/8.1/_test/railsguides.rbs +++ b/gems/actionmailer/8.1/_test/railsguides.rbs @@ -21,3 +21,7 @@ module RailsGuides def welcome: (String email, String username) -> Mail::Message end end + +class NotifierMailerTest < ActionMailer::TestCase + def test_welcome: () -> void +end diff --git a/gems/actionmailer/8.1/actionmailer-generated.rbs b/gems/actionmailer/8.1/actionmailer-generated.rbs index cdb8b38cd..e912f5663 100644 --- a/gems/actionmailer/8.1/actionmailer-generated.rbs +++ b/gems/actionmailer/8.1/actionmailer-generated.rbs @@ -726,14 +726,14 @@ module ActionMailer module ClassMethods # Defines a callback that will get called right before the # message is sent to the delivery method. - def before_deliver: (*untyped filters) { (?) -> untyped } -> untyped + def before_deliver: (*untyped filters) ?{ (?) -> untyped } -> untyped # Defines a callback that will get called right after the # message's delivery method is finished. - def after_deliver: (*untyped filters) { (?) -> untyped } -> untyped + def after_deliver: (*untyped filters) ?{ (?) -> untyped } -> untyped # Defines a callback that will get called around the message's deliver method. - def around_deliver: (*untyped filters) { (?) -> untyped } -> untyped + def around_deliver: (*untyped filters) ?{ (?) -> untyped } -> untyped def internal_methods: () -> untyped end @@ -746,9 +746,9 @@ module ActionMailer attr_reader responses: untyped - def initialize: (untyped context) { (?) -> untyped } -> void + def initialize: (untyped context) ?{ (?) -> untyped } -> void - def any: (*untyped args) { (?) -> untyped } -> untyped + def any: (*untyped args) ?{ (?) -> untyped } -> untyped alias all any @@ -1440,7 +1440,7 @@ module ActionMailer # Note: This assertion is simply a shortcut for: # # assert_emails 0, &block - def assert_no_emails: () { (?) -> untyped } -> untyped + def assert_no_emails: () ?{ (?) -> untyped } -> untyped # Asserts that the number of emails enqueued for later delivery matches # the given number. @@ -1466,7 +1466,7 @@ module ActionMailer # ContactMailer.welcome.deliver_later # end # end - def assert_enqueued_emails: (untyped number) { (?) -> untyped } -> untyped + def assert_enqueued_emails: (untyped number) ?{ (?) -> untyped } -> untyped # Asserts that a specific email has been enqueued, optionally # matching arguments and/or params. @@ -1521,7 +1521,7 @@ module ActionMailer # ContactMailer.welcome.deliver_later # end # end - def assert_enqueued_email_with: (untyped mailer, untyped method, ?params: untyped?, ?args: untyped?, ?queue: untyped?) { (?) -> untyped } -> untyped + def assert_enqueued_email_with: (untyped mailer, untyped method, ?params: untyped?, ?args: untyped?, ?queue: untyped?) ?{ (?) -> untyped } -> untyped # Asserts that no emails are enqueued for later delivery. # @@ -1538,7 +1538,7 @@ module ActionMailer # # No emails should be enqueued from this block # end # end - def assert_no_enqueued_emails: () { (?) -> untyped } -> untyped + def assert_no_enqueued_emails: () ?{ (?) -> untyped } -> untyped # Delivers all enqueued emails. If a block is given, delivers all of the emails # that were enqueued throughout the duration of the block. If a block is @@ -1576,7 +1576,7 @@ module ActionMailer # # If the +:at+ option is specified, then only delivers emails enqueued to deliver # immediately or before the given time. - def deliver_enqueued_emails: (?queue: untyped?, ?at: untyped?) { (?) -> untyped } -> untyped + def deliver_enqueued_emails: (?queue: untyped?, ?at: untyped?) ?{ (?) -> untyped } -> untyped # Returns any emails that are sent in the block. # From 9fb02445b98bc6b7a3fa4dc6f82bc1f5b6ee141d Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 19:14:56 +0900 Subject: [PATCH 13/16] actionmailer: Add delivery_job and deliver_later_queue_name to ActionMailer::Base QueuedDelivery defines them on the mailer class with class_attribute in its included block, which rbs prototype doesn't see. Co-Authored-By: Claude Opus 5.5 (1M context) --- gems/actionmailer/8.1/_test/railsguides.rb | 3 +++ gems/actionmailer/8.1/actionmailer.rbs | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/gems/actionmailer/8.1/_test/railsguides.rb b/gems/actionmailer/8.1/_test/railsguides.rb index 4863dca4a..91afd57f7 100644 --- a/gems/actionmailer/8.1/_test/railsguides.rb +++ b/gems/actionmailer/8.1/_test/railsguides.rb @@ -51,3 +51,6 @@ def test_welcome deliver_enqueued_emails end end + +RailsGuides::ApplicationMailer.deliver_later_queue_name = :low +RailsGuides::ApplicationMailer.delivery_job = ActionMailer::MailDeliveryJob diff --git a/gems/actionmailer/8.1/actionmailer.rbs b/gems/actionmailer/8.1/actionmailer.rbs index 4581df5ab..bec4b24f7 100644 --- a/gems/actionmailer/8.1/actionmailer.rbs +++ b/gems/actionmailer/8.1/actionmailer.rbs @@ -45,3 +45,23 @@ module ActionMailer def processed?: () -> boolish end end + +# Methods rbs prototype doesn't see, which aren't generated from the source +module ActionMailer + class Base + # class_attribute in QueuedDelivery's included block + def self.delivery_job: () -> untyped + def self.delivery_job=: (untyped value) -> untyped + def self.delivery_job?: () -> bool + def delivery_job: () -> untyped + def delivery_job=: (untyped value) -> untyped + def delivery_job?: () -> bool + + def self.deliver_later_queue_name: () -> untyped + def self.deliver_later_queue_name=: (untyped value) -> untyped + def self.deliver_later_queue_name?: () -> bool + def deliver_later_queue_name: () -> untyped + def deliver_later_queue_name=: (untyped value) -> untyped + def deliver_later_queue_name?: () -> bool + end +end From 1d4932060c5f344ca154a7adb7368d6e04e46f2a Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 19:17:36 +0900 Subject: [PATCH 14/16] actionmailer: Extend the class methods of the concerns ActionMailer::Base includes Only the ones from Action Pack and Action View were listed, so mailers had no before_deliver, rescue_from, deliveries, or with. Co-Authored-By: Claude Opus 5.5 (1M context) --- gems/actionmailer/8.1/_test/railsguides.rb | 9 +++++++++ gems/actionmailer/8.1/_test/railsguides.rbs | 3 +++ gems/actionmailer/8.1/actionmailer.rbs | 13 +++++++++++++ 3 files changed, 25 insertions(+) diff --git a/gems/actionmailer/8.1/_test/railsguides.rb b/gems/actionmailer/8.1/_test/railsguides.rb index 91afd57f7..3c308ded6 100644 --- a/gems/actionmailer/8.1/_test/railsguides.rb +++ b/gems/actionmailer/8.1/_test/railsguides.rb @@ -2,6 +2,15 @@ module RailsGuides class ApplicationMailer < ActionMailer::Base default from: 'from@example.com' layout 'mailer' + + before_deliver :log_delivery + rescue_from(ArgumentError) { |exception| exception.message } + self.raise_delivery_errors = true + + private + + def log_delivery + end end class NotifierMailer < ApplicationMailer diff --git a/gems/actionmailer/8.1/_test/railsguides.rbs b/gems/actionmailer/8.1/_test/railsguides.rbs index 0fc082bbb..1795dae81 100644 --- a/gems/actionmailer/8.1/_test/railsguides.rbs +++ b/gems/actionmailer/8.1/_test/railsguides.rbs @@ -1,5 +1,8 @@ module RailsGuides class ApplicationMailer < ActionMailer::Base + private + + def log_delivery: () -> void end class NotifierMailer < ApplicationMailer diff --git a/gems/actionmailer/8.1/actionmailer.rbs b/gems/actionmailer/8.1/actionmailer.rbs index bec4b24f7..8152b5b85 100644 --- a/gems/actionmailer/8.1/actionmailer.rbs +++ b/gems/actionmailer/8.1/actionmailer.rbs @@ -13,6 +13,19 @@ module ActionMailer include ActionView::Layouts extend ActionView::Layouts::ClassMethods + # ClassMethods of the concerns included in ActionMailer::Base and the ones they include + extend ActionMailer::Callbacks::ClassMethods + extend ActionMailer::DeliveryMethods::ClassMethods + extend ActionMailer::Rescuable::ClassMethods + extend ActionMailer::Parameterized::ClassMethods + extend ActionMailer::Previews::ClassMethods + extend ActionMailer::FormBuilder::ClassMethods + extend ActiveSupport::Callbacks::ClassMethods + extend ActiveSupport::Rescuable::ClassMethods + extend AbstractController::Caching::Fragments::ClassMethods + extend ActionView::Rendering::ClassMethods + extend ActionView::ViewPaths::ClassMethods + def self.default: (**untyped) -> void def self.layout: (String) -> void From ca585ae3dd56129b8ef02261efc492871d93baf5 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 19:18:06 +0900 Subject: [PATCH 15/16] actionmailer: Type return values of deliver_later, deliveries, and capture_emails deliver_later and deliver_later! return the enqueued job, or false if it couldn't be enqueued. deliveries is delegated to Mail::TestMailer. Co-Authored-By: Claude Opus 5.5 (1M context) --- gems/actionmailer/8.1/_test/railsguides.rb | 16 +++++++ gems/actionmailer/8.1/_test/railsguides.rbs | 4 ++ .../8.1/actionmailer-generated.rbs | 47 ------------------- gems/actionmailer/8.1/actionmailer.rbs | 20 +++++++- 4 files changed, 39 insertions(+), 48 deletions(-) diff --git a/gems/actionmailer/8.1/_test/railsguides.rb b/gems/actionmailer/8.1/_test/railsguides.rb index 3c308ded6..122ce818c 100644 --- a/gems/actionmailer/8.1/_test/railsguides.rb +++ b/gems/actionmailer/8.1/_test/railsguides.rb @@ -63,3 +63,19 @@ def test_welcome RailsGuides::ApplicationMailer.deliver_later_queue_name = :low RailsGuides::ApplicationMailer.delivery_job = ActionMailer::MailDeliveryJob + +if (job = RailsGuides::NotifierMailer.welcome("first user").deliver_later(wait: 1.hour)) + job.job_id.upcase +end +if (job = RailsGuides::NotifierMailer.welcome("first user").deliver_later!) + job.job_id.upcase +end +ActionMailer::Base.deliveries.last&.subject +ActionMailer::Base.deliveries.clear + +class NotifierMailerTest + def test_capture + emails = capture_emails { RailsGuides::NotifierMailer.welcome("first user").deliver_later } + emails.first&.to + end +end diff --git a/gems/actionmailer/8.1/_test/railsguides.rbs b/gems/actionmailer/8.1/_test/railsguides.rbs index 1795dae81..58f066bb3 100644 --- a/gems/actionmailer/8.1/_test/railsguides.rbs +++ b/gems/actionmailer/8.1/_test/railsguides.rbs @@ -28,3 +28,7 @@ end class NotifierMailerTest < ActionMailer::TestCase def test_welcome: () -> void end + +class NotifierMailerTest + def test_capture: () -> void +end diff --git a/gems/actionmailer/8.1/actionmailer-generated.rbs b/gems/actionmailer/8.1/actionmailer-generated.rbs index e912f5663..d945db6fc 100644 --- a/gems/actionmailer/8.1/actionmailer-generated.rbs +++ b/gems/actionmailer/8.1/actionmailer-generated.rbs @@ -806,10 +806,6 @@ module ActionMailer def delivery_method?: () -> bool - def deliveries: (*untyped, **untyped) ?{ (*untyped) -> untyped } -> untyped - - def deliveries=: (*untyped, **untyped) ?{ (*untyped) -> untyped } -> untyped - # Adds a new delivery method through the given class using the given # symbol as alias and the default options supplied. # @@ -1022,33 +1018,6 @@ module ActionMailer # Unused except for delegator internals (dup, marshalling). def __setobj__: (untyped mail_message) -> untyped - # Enqueues the email to be delivered through Active Job. When the - # job runs it will send the email using +deliver_now!+. That means - # that the message will be sent bypassing checking +perform_deliveries+ - # and +raise_delivery_errors+, so use with caution. - # - # Notifier.welcome(User.first).deliver_later! - # Notifier.welcome(User.first).deliver_later!(wait: 1.hour) - # Notifier.welcome(User.first).deliver_later!(wait_until: 10.hours.from_now) - # Notifier.welcome(User.first).deliver_later!(priority: 10) - # - # Options: - # - # * :wait - Enqueue the email to be delivered with a delay - # * :wait_until - Enqueue the email to be delivered at (after) a specific date / time - # * :queue - Enqueue the email on the specified queue - # * :priority - Enqueues the email with the specified priority - # - # By default, the email will be enqueued using ActionMailer::MailDeliveryJob on - # the default queue. Mailer classes can customize the queue name used for the default - # job by assigning a +deliver_later_queue_name+ class variable, or provide a custom job - # by assigning a +delivery_job+. When a custom job is used, it controls the queue name. - # - # class AccountRegistrationMailer < ApplicationMailer - # self.delivery_job = RegistrationDeliveryJob - # end - def deliver_later!: (?::Hash[untyped, untyped] options) -> untyped - private # Returns the processed Mailer instance. We keep this instance @@ -1578,22 +1547,6 @@ module ActionMailer # immediately or before the given time. def deliver_enqueued_emails: (?queue: untyped?, ?at: untyped?) ?{ (?) -> untyped } -> untyped - # Returns any emails that are sent in the block. - # - # def test_emails - # emails = capture_emails do - # ContactMailer.welcome.deliver_now - # end - # assert_equal "Hi there", emails.first.subject - # - # emails = capture_emails do - # ContactMailer.welcome.deliver_now - # ContactMailer.welcome.deliver_later - # end - # assert_equal "Hi there", emails.first.subject - # end - def capture_emails: () { (?) -> untyped } -> untyped - private def delivery_job_filter: (untyped job) -> untyped diff --git a/gems/actionmailer/8.1/actionmailer.rbs b/gems/actionmailer/8.1/actionmailer.rbs index 8152b5b85..c9ef2d8a6 100644 --- a/gems/actionmailer/8.1/actionmailer.rbs +++ b/gems/actionmailer/8.1/actionmailer.rbs @@ -47,7 +47,10 @@ module ActionMailer end class MessageDelivery - def deliver_later: (?wait: untyped, ?wait_until: untyped, ?queue: untyped, ?priority: Integer) -> void + # The enqueued job, or false if it couldn't be enqueued + def deliver_later: (?wait: untyped, ?wait_until: untyped, ?queue: untyped, ?priority: Integer) -> (ActiveJob::Base | false) + + def deliver_later!: (?wait: untyped, ?wait_until: untyped, ?queue: untyped, ?priority: Integer) -> (ActiveJob::Base | false) def deliver_now: () -> void @@ -78,3 +81,18 @@ module ActionMailer def deliver_later_queue_name?: () -> bool end end + +# Return types of commonly used methods +module ActionMailer + module DeliveryMethods + module ClassMethods + # Delegated to Mail::TestMailer + def deliveries: () -> Array[Mail::Message] + def deliveries=: (Array[Mail::Message] deliveries) -> Array[Mail::Message] + end + end + + module TestHelper + def capture_emails: () { () -> void } -> Array[Mail::Message] + end +end From e1deacb18b4a162a50f523544640253fd90e2d88 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Tue, 13 Oct 2026 19:36:03 +0900 Subject: [PATCH 16/16] actionmailer: Extend the class methods of the concerns ActionMailer::TestCase includes The generated classes now extend the ClassMethods of the concerns they include, directly or through other concerns, as ActiveSupport::Concern does. ActionMailer::TestCase gets tests and determine_default_mailer. Co-Authored-By: Claude Opus 5.5 (1M context) --- gems/actionmailer/8.1/_test/railsguides.rb | 2 ++ gems/actionmailer/8.1/actionmailer-generated.rbs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/gems/actionmailer/8.1/_test/railsguides.rb b/gems/actionmailer/8.1/_test/railsguides.rb index 122ce818c..36b7587b4 100644 --- a/gems/actionmailer/8.1/_test/railsguides.rb +++ b/gems/actionmailer/8.1/_test/railsguides.rb @@ -51,6 +51,8 @@ def welcome(email, username) delivery.deliver_now unless delivery.processed? class NotifierMailerTest < ActionMailer::TestCase + tests RailsGuides::NotifierMailer + def test_welcome assert_no_emails assert_no_enqueued_emails diff --git a/gems/actionmailer/8.1/actionmailer-generated.rbs b/gems/actionmailer/8.1/actionmailer-generated.rbs index d945db6fc..dbc9ab6b5 100644 --- a/gems/actionmailer/8.1/actionmailer-generated.rbs +++ b/gems/actionmailer/8.1/actionmailer-generated.rbs @@ -1292,6 +1292,10 @@ module ActionMailer end class TestCase < ::ActiveSupport::TestCase + extend ::ActionMailer::TestCase::Behavior::ClassMethods + + extend ::ActiveSupport::Testing::ConstantLookup::ClassMethods + module ClearTestDeliveries extend ::ActiveSupport::Concern