job-iteration
overrides the perform
method of ActiveJob::Base
to allow for iteration. The perform
method preserves all the standard calling conventions of the original, but the way the subsequent methods work might differ from what one expects from an ActiveJob subclass.
The call sequence is usually 3 methods:
perform -> build_enumerator -> each_iteration|each_batch
In that sense job-iteration
works like a framework (it calls your code) rather than like a library (that you call). When using jobs with parameters, the following rules of thumb are good to keep in mind.
Jobs without arguments do not pass anything into either build_enumerator
or each_iteration
except for the cursor
which job-iteration
persists by itself:
class ArglessJob < ActiveJob::Base
include JobIteration::Iteration
def build_enumerator(cursor:)
# ...
end
def each_iteration(single_object_yielded_from_enumerator)
# ...
end
end
To enqueue the job:
ArglessJob.perform_later
Jobs with positional arguments will have those arguments available to both build_enumerator
and each_iteration
:
class ArgumentativeJob < ActiveJob::Base
include JobIteration::Iteration
def build_enumerator(arg1, arg2, arg3, cursor:)
# ...
end
def each_iteration(single_object_yielded_from_enumerator, arg1, arg2, arg3)
# ...
end
end
To enqueue the job:
ArgumentativeJob.perform_later(_arg1 = "One", _arg2 = "Two", _arg3 = "Three")
Jobs with keyword arguments will have the keyword arguments available to both build_enumerator
and each_iteration
, but these arguments come packaged into a Hash in both cases. You will need to fetch
or []
your parameter from the Hash
you get passed in:
class ParameterizedJob < ActiveJob::Base
include JobIteration::Iteration
def build_enumerator(kwargs, cursor:)
name = kwargs.fetch(:name)
email = kwargs.fetch(:email)
# ...
end
def each_iteration(object_yielded_from_enumerator, kwargs)
name = kwargs.fetch(:name)
email = kwargs.fetch(:email)
# ...
end
end
To enqueue the job:
ParameterizedJob.perform_later(name: "Jane", email: "jane@host.example")
Note that you cannot use ruby2_keywords
at present, and the keyword arguments syntax is not supported in each_iteration
/ build_enumerator
.
Jobs with keyword arguments will have the keyword arguments available to both build_enumerator
and each_iteration
, but these arguments come packaged into a Hash in both cases. You will need to fetch
or []
your parameter from the Hash
you get passed in. Positional arguments get passed first and "unsplatted" (not combined into an array), the Hash
containing keyword arguments comes after:
class HighlyConfigurableGreetingJob < ActiveJob::Base
include JobIteration::Iteration
def build_enumerator(subject_line, kwargs, cursor:)
name = kwargs.fetch(:sender_name)
email = kwargs.fetch(:sender_email)
# ...
end
def each_iteration(object_yielded_from_enumerator, subject_line, kwargs)
name = kwargs.fetch(:sender_name)
email = kwargs.fetch(:sender_email)
# ...
end
end
To enqueue the job:
HighlyConfigurableGreetingJob.perform_later(_subject_line = "Greetings everybody!", sender_name: "Jane", sender_email: "jane@host.example")
Note that you cannot use ruby2_keywords
at present, and the keyword arguments syntax is not supported in each_iteration
/ build_enumerator
.
When defining a custom enumerator (see the custom enumerator guide) you need to yield two positional arguments from it: the object that will be the value for the current iteration (like a single ActiveModel instance, a single number...) and the value you want to be persisted as the cursor
value should job-iteration
decide to interrupt you after this iteration. Calling the enumerator with that cursor should return the next object after the one returned in this iteration. That new cursor
value does not get passed to each_iteration
:
Enumerator.new do |yielder|
# In this case `cursor` is an Integer
cursor.upto(99999) do |offset|
yielder.yield(fetch_record_at(offset), offset)
end
end