Перейти к документации
Ruby

Пагинация

Одна страница, все страницы или по одному элементу за раз в каждом списке с постраничной выдачей.

list, list_all и iterate

У каждого списка с постраничной выдачей есть три метода. list получает одну страницу и возвращает OpenEmail::Page. list_all следует за курсором через все страницы и возвращает один Array. iterate обходит те же страницы по одному элементу: передаёт каждый элемент в блок или возвращает Enumerator, если блок не передан. Все три принимают фильтры списка, limit:, cursor: и api_key:.

three_ways.rb
page = client.emails.list(status: "failed", limit: 50)page.items.each { |email| puts "#{email[:id]} #{email[:lastError]}" } failures = client.emails.list_all(status: "failed") client.emails.iterate(status: "failed") do |email|  puts email[:id]end puts failures.size, page.has_more?

Те же три имени повторяются везде, где у пространства имён больше одного списка, и называются по списку, который обходят: list_events, list_all_events и iterate_events в emails, list_deliveries, list_all_deliveries и iterate_deliveries в webhooks и так далее.

OpenEmail::Page

itemsArray<Hash>
Строки этой страницы, извлечённые из конверта `data` API, каждая в виде Hash с ключами типа Symbol. Пусто, если на странице ничего нет.
has_more?Boolean
Есть ли следующая страница. `has_more` без вопросительного знака читает то же значение. Если API не присылает `hasMore`, значение истинно ровно тогда, когда есть `next_cursor`.
next_cursorString or nil
Что передать обратно как `cursor:` для следующей страницы. На последней странице nil.

Страница является объектом Ruby Data, поэтому она заморожена, сравнивается по значению и превращается в Hash через to_h.

Блок или Enumerator

С блоком iterate сразу обходит все страницы и передаёт в блок каждый элемент. Без блока он возвращает Enumerator и ничего не запрашивает, пока вы его не потребите. В обоих случаях он запрашивает следующую страницу, только когда все элементы текущей уже переданы, поэтому всё, что останавливается раньше, останавливает и запросы: first(10) читает столько страниц, сколько нужно для десяти элементов, find останавливается на совпадении, а break в блоке завершает обход.

enumerator.rb
latest = client.emails.iterate(status: "failed", limit: 100).first(10) invoice = client.emails.iterate(status: "failed").find do |email|  email.dig(:tags, :invoice) == "inv_2026_09_4192"end from_api = client.emails.iterate(status: "bounced").lazy.select { |email| email[:source] == "api" }.first(5) p latest.size, invoice&.fetch(:id), from_api.map { |email| email[:id] }

Метод Enumerable, которому нужны все элементы, например select, map или count, вызванный прямо на Enumerator, читает все страницы, прежде чем вернуть результат, как это делает list_all. Поставьте перед ними lazy, чтобы выстроить цепочку и всё равно остановиться раньше.

Enumerator начинает обход заново при каждом потреблении, поэтому двойной вызов first(10) на одном и том же Enumerator дважды получает первую страницу. Если результат нужен снова, сохраняйте результат, а не Enumerator.

Продолжение с курсора

Курсор непрозрачен. Сохраните next_cursor последней прочитанной страницы и передайте его обратно как cursor:, чтобы продолжить с этого места в более позднем запросе или в другом процессе. list_all и iterate тоже принимают cursor: и начинают обход после него.

resume.rb
first_page = client.emails.list(status: "failed", limit: 25)saved = first_page.next_cursor if saved  rest = client.emails.list_all(status: "failed", cursor: saved)  puts rest.sizeend

Курсор принадлежит списку и фильтрам, из которых он получен, поэтому отправляйте вместе с ним те же фильтры. Курсор, который список не может распознать, отклоняется с invalid_cursor, и тогда остаётся начать заново без курсора.

limit:

limit: является размером каждой страницы, а не общим количеством. В list это число возвращаемых строк. В list_all и iterate это число строк, которое запрашивает каждый запрос, поэтому большее значение означает меньше обращений к серверу за те же строки. У каждого списка свой диапазон и значение по умолчанию, чаще всего от 1 до 100 и 25, если ничего не передано, а значение вне диапазона отклоняется, а не подрезается. Диапазон указан на странице каждого списка.

Когда обход останавливается

  • Когда страница сообщает, что has_more? равно false.
  • Когда страница не несёт next_cursor, поскольку страница, которая заявляет о продолжении, но не называет курсора, зациклилась бы навсегда.
  • Когда API возвращает тот же курсор, который ему только что передали, по той же причине.

Каждая страница является GET, поэтому, как любое чтение, она сама повторяется, прежде чем что-либо выбросит исключение. Сбой, переживший повторы, выбрасывается из list_all, а уже полученные элементы отбрасываются. В iterate элементы предыдущих страниц к этому моменту уже переданы в блок, поэтому сделайте действия блока безопасными для повторного выполнения или листайте через list и сохраняйте каждый next_cursor, чтобы вторая попытка могла начать с того места, где остановилась первая.

Цепочки и черновики

threads.list и drafts.list вместе со своими list_all и iterate листают с помощью pageToken и nextPageToken из API, а не курсора. Гем скрывает эту разницу: передавайте токен как cursor: и читайте его из next_cursor.

page_token.rb
page = client.threads.list(folder: "inbox", limit: 50)later = client.threads.list(folder: "inbox", limit: 50, cursor: page.next_cursor) if page.has_more? p page.items.size, later&.items&.size

Сервер предлагает токен всякий раз, когда страница возвращается заполненной, поэтому has_more? может быть true на странице, которая окажется последней, и тогда следующий вызов не вернёт ни одного элемента.

Страницы с дополнительными данными

Несколько списков отвечают не только строками и возвращают собственный объект Data вместо OpenEmail::Page.

МетодВозвращаетЧто добавляет
addresses.listOpenEmail::AddressBookPageaddresses вместо items, а также unrestricted и domains, с has_more? и next_cursor.
addresses.list_allOpenEmail::AddressBookВсе адреса в addresses, с unrestricted и domains в том виде, в каком их сообщила последняя страница. Это единственный list_all, который возвращает всю адресную книгу, а не Array. addresses.iterate передаёт только адреса.
contacts.list_peopleOpenEmail::PeoplePageseen, равное false, когда ключ не может читать адреса, встреченные в почте. list_all_people и iterate_people возвращают только людей.
temp_mail.list_messagesOpenEmail::TempMessagesPageexpires_at, момент, когда срок ящика истекает. list_all_messages и iterate_messages возвращают только сообщения.
templates.list_sendsOpenEmail::TemplateSendsПостраничная выдача по номеру, а не по курсору: items, total, page и page_size. Следующую страницу запрашивайте через page:.
emails.send_batchOpenEmail::BatchResultНе страница: items, по одному на каждое отправленное вами сообщение, со счётчиками sent и failed.

Списки, которые возвращают Hash из API

Некоторые списки листают по смещению, по номеру страницы или по собственному числовому курсору и возвращают разобранное тело как есть, Hash с data, а не OpenEmail::Page. У них нет list_all или iterate, поэтому листать их приходится самостоятельно.

МетодЛистается черезЧто возвращается
exports.listlimit: и offset:data, total и hasMore.
imports.list_failuresafter: и limit:data и nextCursor, Integer для передачи обратно как after:, равный nil на последней странице.
subscriptions.list и subscriptions.list_domainslimit: и offset:data, total, counts и hasMore.
billing.list_invoicespage: и limit:data, total, page, limit, hasMore и metered.
offset_paging.rb
offset = 0 loop do  batch = client.subscriptions.list(status: "active", limit: 50, offset:)  batch[:data].each { |row| puts "#{row[:senderEmail]} #{row[:total]}" }   break unless batch[:hasMore]   offset += batch[:data].sizeend

Список без постраничной выдачи, например languages.list, labels.list_colors или roles.list_permissions, сразу возвращает Array.