{"record":{"id":"09be3ea6f43dbe01","repo":"sidekiq/sidekiq","slug":"array-must-be-an-array","errorCode":null,"errorMessage":"array must be an Array","messagePattern":"array must be an Array","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/sidekiq/job/iterable/enumerators.rb","lineNumber":21,"sourceCode":"require_relative \"active_record_enumerator\"\nrequire_relative \"csv_enumerator\"\n\nmodule Sidekiq\n  module Job\n    module Iterable\n      module Enumerators\n        # Builds Enumerator object from a given array, using +cursor+ as an offset.\n        #\n        # @param array [Array]\n        # @param cursor [Integer] offset to start iteration from\n        #\n        # @return [Enumerator]\n        #\n        # @example\n        #   array_enumerator(['build', 'enumerator', 'from', 'any', 'array'], cursor: cursor)\n        #\n        def array_enumerator(array, cursor:)\n          raise ArgumentError, \"array must be an Array\" unless array.is_a?(Array)\n\n          x = array.each_with_index.drop(cursor || 0)\n          x.to_enum { x.size }\n        end\n\n        # Builds Enumerator from `ActiveRecord::Relation`.\n        # Each Enumerator tick moves the cursor one row forward.\n        #\n        # @param relation [ActiveRecord::Relation] relation to iterate\n        # @param cursor [Object] offset id to start iteration from\n        # @param options [Hash] additional options that will be passed to relevant\n        #   ActiveRecord batching methods\n        #\n        # @return [ActiveRecordEnumerator]\n        #\n        # @example\n        #   def build_enumerator(cursor:)\n        #     active_record_records_enumerator(User.all, cursor: cursor)","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/sidekiq/sidekiq/blob/1bb4aa06e5aa178a114a5e855f9f3d5c24f6c61b/lib/sidekiq/job/iterable/enumerators.rb#L3-L39","documentation":"`array_enumerator(array, cursor:)` is the iterable helper that turns a plain Ruby Array into a resumable Enumerator. It only accepts an actual Array; anything else raises ArgumentError immediately, because the cursor logic relies on `each_with_index.drop(cursor)` array semantics.","triggerScenarios":"Passing a Range (`1..100`), a Set, an ActiveRecord::Relation that was `.to_a`-ed conditionally, an Enumerator, or nil to `array_enumerator`. The raise happens when `build_enumerator` runs on the worker (or in tests that instantiate the enumerator).","commonSituations":"Calling `.map`/`.select` chains that lazily return enumerators instead of arrays. Passing `Model.pluck(:id)` is fine (Array), but `Model.all` (Relation) or `1..50.times` style values are not. Data loaded from JSON or params that is actually nil or a Hash.","solutions":["Materialize to an Array before calling the helper: `array_enumerator(items.to_a, cursor: cursor)`.","For ActiveRecord relations use `active_record_records_enumerator(relation, cursor:)` instead — it batches efficiently and doesn't need an Array.","For other enumerables (Range, Set), call `.to_a` first or build a custom `.to_enum { size }` enumerator."],"exampleFix":"// before\ndef build_enumerator(cursor:)\n  array_enumerator((1..100), cursor: cursor) # Range -> ArgumentError\nend\n\n// after\ndef build_enumerator(cursor:)\n  array_enumerator((1..100).to_a, cursor: cursor)\nend","handlingStrategy":"type-guard","validationCode":"items = items.to_a unless items.is_a?(Array)\nraise ArgumentError, 'array_enumerator needs an Array' unless items.is_a?(Array)\narray_enumerator(items, cursor: cursor)","typeGuard":"def array_input?(obj)\n  obj.is_a?(Array)\nend","tryCatchPattern":"begin\n  array_enumerator(items, cursor: cursor)\nrescue ArgumentError\n  array_enumerator(items.to_a, cursor: cursor) # only if items is safely materializable\nend","preventionTips":["Call .to_a on ranges/sets/lazy enumerables before the helper, or pick the matching helper (active_record_records_enumerator for relations).","Unit-test build_enumerator against the real shape of your data source.","Beware nil from params/JSON — guard with Array(items).to_a when appropriate."],"tags":["sidekiq","iterable-jobs","argument-validation","enumerator"],"backgroundTag":"argument-type-validation","analyzedSha":"1bb4aa06e5aa178a114a5e855f9f3d5c24f6c61b","analyzedAt":"2026-08-21T15:49:18.863Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}