ترقيم الصفحات
صفحة واحدة، أو كل الصفحات، أو عنصر واحد في كل مرة، في كل قائمة مرقّمة الصفحات.
list وlist_all وiterate
لكل قائمة مرقّمة الصفحات ثلاثة توابع. يجلب list صفحة واحدة ويعيد OpenEmail::Page. ويتبع list_all المؤشر عبر كل الصفحات ويعيد Array واحدة. ويمر iterate على الصفحات نفسها عنصرًا عنصرًا: يمرّر كل عنصر إلى كتلة (block)، أو يعيد 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، فهي مجمَّدة، وتُقارن بالقيمة، وتتحول إلى Hash عبر to_h.
كتلة أو Enumerator
إن أُعطي كتلة، يمر iterate على كل الصفحات الآن ويمرّر إليها كل عنصر. ودونها يعيد Enumerator ولا يجلب شيئًا حتى تستهلكه. وفي الحالتين لا يطلب الصفحة التالية إلا بعد تمرير كل عناصر الصفحة الحالية، فأي شيء يتوقف مبكرًا يوقف الطلبات أيضًا: يقرأ 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، حين تحتاج إليها مجددًا.
الاستئناف من مؤشر
المؤشر غير شفاف. احتفظ بـ 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المؤشر يخص القائمة والمرشِّحات التي جاء منها، فأرسل معه المرشِّحات نفسها. والمؤشر الذي لا تستطيع القائمة تحديد موضعه يُرفض بـ 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 بدل المؤشر. ويخفي الـ 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 فيما يتبيّن أنه الصفحة الأخيرة، ثم لا يعيد الاستدعاء التالي أي عناصر.
صفحات تحمل أكثر من ذلك
تجيب بعض القوائم بأكثر من الصفوف، فتعيد كائن Data خاصًا بها بدل OpenEmail::Page.
| الطريقة | ما يعيده | ما يضيفه |
|---|---|---|
| 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 العناوين وحدها. |
| 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 | مرقّمة بالأرقام لا بالمؤشر: items وtotal وpage وpage_size. اطلب الصفحة التالية عبر page:. |
| emails.send_batch | OpenEmail::BatchResult | ليست صفحة: items، عنصر لكل رسالة أرسلتها، مع العددين sent وfailed. |
قوائم تعيد Hash الخاص بـ API
بعض القوائم ترقّم الصفحات بالإزاحة، أو برقم الصفحة، أو بمؤشر رقمي خاص بها، وتعيد المتن المحلَّل كما جاء، أي Hash فيه data، بدل OpenEmail::Page. ليس لها 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 مباشرة.