You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Previously, `cursor_position` was handed as-is to the queue adapter. This could
lead to the queue adapter corrupting cursors of certain classes. For example,
if given a `Time` cursor, Sidekiq would save it as JSON by calling `to_s`,
resulting in the deserialized cursor being a `String` instead of a `Time`.
To prevent this, we now leverage `ActiveJob::Arguments` to (de)serialize the
`cursor_position` and ensure it will make the round trip safely.
However, as this is a breaking change (as unsafe cursors would previously be
accepted, but possibly corrupted, whereas they would now be rejected), we begin
by rescuing (de)serialization failures and emitting a deprecation warning.
Starting in Job Iteration version 2.0, the deprecation warning will be removed,
and (de)serialization failure will raise.
Application owners can opt-in to the 2.0 behavior either globally by setting
JobIteration.enforce_serializable_cursors = true
or on an inheritable per-class basis by setting
class MyJob < ActiveJob::Base
include JobIteration::Iteration
self.job_iteration_enforce_serializable_cursors = true
# ...
end
Copy file name to clipboardExpand all lines: guides/custom-enumerator.md
+57Lines changed: 57 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,9 @@
1
+
# Custom Enumerator
2
+
1
3
Iteration leverages the [Enumerator](http://ruby-doc.org/core-2.5.1/Enumerator.html) pattern from the Ruby standard library, which allows us to use almost any resource as a collection to iterate.
2
4
5
+
## Cursorless Enumerator
6
+
3
7
Consider a custom Enumerator that takes items from a Redis list. Because a Redis List is essentially a queue, we can ignore the cursor:
4
8
5
9
```ruby
@@ -19,6 +23,8 @@ class ListJob < ActiveJob::Base
19
23
end
20
24
```
21
25
26
+
## Enumerator with cursor
27
+
22
28
But what about iterating based on a cursor? Consider this Enumerator that wraps third party API (Stripe) for paginated iteration:
23
29
24
30
```ruby
@@ -82,6 +88,57 @@ class StripeJob < ActiveJob::Base
82
88
end
83
89
```
84
90
91
+
## Notes
92
+
85
93
We recommend that you read the implementation of the other enumerators that come with the library (`CsvEnumerator`, `ActiveRecordEnumerator`) to gain a better understanding of building Enumerator objects.
86
94
95
+
### Post-`yield` code
96
+
87
97
Code that is written after the `yield` in a custom enumerator is not guaranteed to execute. In the case that a job is forced to exit ie `job_should_exit?` is true, then the job is re-enqueued during the yield and the rest of the code in the enumerator does not run. You can follow that logic [here](https://github.com/Shopify/job-iteration/blob/9641f455b9126efff2214692c0bef423e0d12c39/lib/job-iteration/iteration.rb#L128-L131).
98
+
99
+
### Cursor types
100
+
101
+
Cursors should be of a [type that Active Job can serialize](https://guides.rubyonrails.org/active_job_basics.html#supported-types-for-arguments).
102
+
103
+
For example, consider:
104
+
105
+
```ruby
106
+
FancyCursor=Struct.new(:wrapped_value) do
107
+
defto_s
108
+
wrapped_value
109
+
end
110
+
end
111
+
```
112
+
113
+
```ruby
114
+
defbuild_enumerator(cursor:)
115
+
Enumerator.newdo |yielder|
116
+
# ...something with fancy cursor...
117
+
yield123, FancyCursor.new(:abc)
118
+
end
119
+
end
120
+
```
121
+
122
+
If this job was interrupted, Active Job would be unable to serialize
123
+
`FancyCursor`, and Job Iteration would fallback to the legacy behavior of not
124
+
serializing the cursor. This would typically result in the queue adapter
125
+
eventually serializing the cursor as JSON by calling `.to_s` on it. The cursor
126
+
would be deserialized as `:abc`, rather than the intended `FancyCursor.new(:abc)`.
127
+
128
+
To avoid this, job authors should take care to ensure that their cursor is
129
+
serializable by Active Job. This can be done in a couple ways, such as:
130
+
-[adding GlobalID support to the cursor class](https://guides.rubyonrails.org/active_job_basics.html#globalid)
131
+
-[implementing a custom Active Job argument serializer for the cursor class](https://guides.rubyonrails.org/active_job_basics.html#serializers)
132
+
- handling (de)serialization in the job/enumerator itself
0 commit comments