صفحهبندی
یک صفحه، همهٔ صفحهها، یا هر بار یک مورد، روی هر فهرستی که صفحهبندی دارد.
list، list_all و iterate
هر فهرستی که صفحهبندی دارد سه متد دارد. list یک صفحه را میگیرد و یک OpenEmail::Page برمیگرداند. list_all cursor را در همهٔ صفحهها دنبال میکند و یک Array برمیگرداند. iterate همان صفحهها را هر بار یک مورد میپیماید: هر مورد را به یک بلاک yield میکند، یا وقتی بلاکی ندهید یک Enumerator برمیگرداند. هر سه، فیلترهای فهرست، limit:، cursor: و api_key: را میگیرند.
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 در یک بلاک پیمایش را پایان میدهد.
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: را میگیرند و پیمایششان را پس از آن آغاز میکنند.
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 = 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.list | OpenEmail::AddressBookPage | addresses بهجای items، بهعلاوهٔ unrestricted و domains، همراه با has_more? و next_cursor. |
| addresses.list_all | OpenEmail::AddressBook | همهٔ آدرسها در addresses، با unrestricted و domains همانطور که آخرین صفحه گزارششان کرده است. این تنها list_all است که بهجای یک Array کل دفترچهٔ آدرس را برمیگرداند. addresses.iterate فقط خود آدرسها را yield میکند. |
| contacts.list_people | OpenEmail::PeoplePage | seen، که وقتی کلید نمیتواند آدرسهای دیدهشده در ایمیلها را بخواند false است. list_all_people و iterate_people فقط خود افراد را برمیگردانند. |
| temp_mail.list_messages | OpenEmail::TempMessagesPage | expires_at، زمانی که صندوق منقضی میشود. list_all_messages و iterate_messages فقط خود پیامها را برمیگردانند. |
| templates.list_sends | OpenEmail::TemplateSends | بهجای cursor با شمارهٔ صفحه صفحهبندی میشود: items، total، page و page_size. صفحهٔ بعد را با page: بخواهید. |
| emails.send_batch | OpenEmail::BatchResult | صفحه نیست: items، یکی برای هر پیامی که فرستادید، همراه با شمارهای sent و failed. |
فهرستهایی که Hash خودِ API را برمیگردانند
برخی فهرستها با offset، با شمارهٔ صفحه یا با cursor عددیِ مخصوص خودشان صفحهبندی میکنند، و بهجای یک OpenEmail::Page، بدنهٔ تجزیهشده را همانطور که آمده برمیگردانند: یک Hash با data. list_all یا iterate ندارند، پس صفحهبندیشان را خودتان انجام میدهید.
| متد | صفحهبندی با | آنچه برمیگردد |
|---|---|---|
| exports.list | limit: و offset: | data، total و hasMore. |
| imports.list_failures | after: و limit: | data، و nextCursor، یک Integer که باید بهعنوان after: پس بدهید و روی آخرین صفحه nil است. |
| subscriptions.list و subscriptions.list_domains | limit: و offset: | data، total، counts و hasMore. |
| billing.list_invoices | page: و limit: | data، total، page، limit، hasMore و metered. |
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 برمیگرداند.