پرش به مستندات
Ruby

صفحه‌بندی

یک صفحه، همهٔ صفحه‌ها، یا هر بار یک مورد، روی هر فهرستی که صفحه‌بندی دارد.

list، list_all و iterate

هر فهرستی که صفحه‌بندی دارد سه متد دارد. list یک صفحه را می‌گیرد و یک OpenEmail::Page برمی‌گرداند. list_all cursor را در همهٔ صفحه‌ها دنبال می‌کند و یک Array برمی‌گرداند. iterate همان صفحه‌ها را هر بار یک مورد می‌پیماید: هر مورد را به یک بلاک yield می‌کند، یا وقتی بلاکی ندهید یک 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` نفرستد، دقیقاً وقتی true است که یک `next_cursor` وجود داشته باشد.
next_cursorString or nil
آنچه برای صفحهٔ بعد باید به‌عنوان `cursor:` پس بدهید، و روی آخرین صفحه nil است.

هر صفحه یک شیء Data در Ruby است، پس منجمد (frozen) است، بر اساس مقدار مقایسه می‌شود، و با to_h به یک Hash تبدیل می‌شود.

یک بلاک یا یک Enumerator

با یک بلاک، iterate همین حالا همهٔ صفحه‌ها را می‌پیماید و هر مورد را به آن yield می‌کند. بدون بلاک یک Enumerator برمی‌گرداند و تا وقتی آن را مصرف نکنید چیزی نمی‌گیرد. در هر دو حالت، صفحهٔ بعد را فقط وقتی می‌خواهد که همهٔ موردهای صفحهٔ کنونی yield شده باشند، پس هر چیزی که زودتر بایستد درخواست‌ها را هم متوقف می‌کند: 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 را.

ادامه دادن از یک cursor

cursor مبهم (opaque) است. 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

هر cursor به فهرست و فیلترهایی تعلق دارد که از آن‌ها آمده است، پس همان فیلترها را همراهش بفرستید. cursorی که فهرست نتواند جایش را پیدا کند با invalid_cursor رد می‌شود، و راه‌حل در آن صورت این است که بدون cursor از نو آغاز کنید.

limit:

limit: اندازهٔ هر صفحه است، نه یک مجموع. روی list تعداد ردیف‌هایی است که برمی‌گردند. روی list_all و iterate تعدادی است که هر درخواست می‌خواهد، پس مقدار بزرگ‌تر یعنی رفت‌وبرگشت‌های کمتر برای همان ردیف‌ها. هر فهرست بازه و پیش‌فرض خودش را دارد، بیشتر وقت‌ها 1 تا 100 با 25 وقتی چیزی نفرستید، و مقدار بیرون از بازه رد می‌شود، نه اینکه به مرز بازه بریده شود. صفحهٔ هر فهرست بازه‌اش را می‌گوید.

پیمایش کی می‌ایستد

  • وقتی صفحه‌ای بگوید has_more? برابر false است.
  • وقتی صفحه‌ای هیچ next_cursor نداشته باشد، چون صفحه‌ای که ادعای ادامه کند اما هیچ cursorی را نام نبرد تا ابد حلقه می‌زد.
  • وقتی API همان cursorی را که تازه به آن داده شده پس بدهد، به همان دلیل.

هر صفحه یک GET است، پس مانند هر خواندنی پیش از آنکه چیزی raise شود جداگانه دوباره تلاش می‌شود. شکستی که پس از تلاش‌های دوباره باقی بماند از list_all بیرون raise می‌شود، و موردهایی که تا آن زمان گرفته شده‌اند دور ریخته می‌شوند. در iterate، موردهای صفحه‌های پیشین تا آن زمان yield شده‌اند، پس کاری را که بلاک انجام می‌دهد طوری بنویسید که دو بار اجرا شدنش بی‌خطر باشد، یا با list صفحه‌بندی کنید و هر next_cursor را نگه دارید تا تلاش دوم بتواند از همان جایی که تلاش نخست ایستاد آغاز شود.

رشته‌ها و پیش‌نویس‌ها

threads.list و drafts.list، همراه با list_all و iterate آن‌ها، به‌جای cursor با pageToken و nextPageToken در API صفحه‌بندی می‌کنند. gem این تفاوت را پنهان می‌کند: توکن را به‌عنوان 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 باشد، و فراخوانی بعدی آنگاه هیچ موردی برنمی‌گرداند.

صفحه‌هایی که چیزهای بیشتری دارند

چند فهرست با چیزی بیش از ردیف‌ها پاسخ می‌دهند، و به‌جای OpenEmail::Page یک شیء Data مخصوص خود را برمی‌گردانند.

متدچه برمی‌گرداندآنچه می‌افزاید
addresses.listOpenEmail::AddressBookPageaddresses به‌جای items، به‌علاوهٔ unrestricted و domains، همراه با has_more? و next_cursor.
addresses.list_allOpenEmail::AddressBookهمهٔ آدرس‌ها در addresses، با unrestricted و domains همان‌طور که آخرین صفحه گزارششان کرده است. این تنها list_all است که به‌جای یک Array کل دفترچهٔ آدرس را برمی‌گرداند. addresses.iterate فقط خود آدرس‌ها را yield می‌کند.
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به‌جای cursor با شمارهٔ صفحه صفحه‌بندی می‌شود: items، total، page و page_size. صفحهٔ بعد را با page: بخواهید.
emails.send_batchOpenEmail::BatchResultصفحه نیست: items، یکی برای هر پیامی که فرستادید، همراه با شمارهای sent و failed.

فهرست‌هایی که Hash خودِ API را برمی‌گردانند

برخی فهرست‌ها با offset، با شمارهٔ صفحه یا با cursor عددیِ مخصوص خودشان صفحه‌بندی می‌کنند، و به‌جای یک OpenEmail::Page، بدنهٔ تجزیه‌شده را همان‌طور که آمده برمی‌گردانند: یک Hash با data. 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 برمی‌گرداند.