Programmierung

Mar 23, 2021

Einführung von JSOM-Pagination - ein eigenständiges Paginierungs-Juwel

Einführung von JSOM-Pagination - ein eigenständiges Paginierungs-Juwel

Einführung von JSOM-Pagination - ein eigenständiges Paginierungs-Juwel

Sebastian Wilgosz

Ruby-Entwickler

Ruby-Paginierung einfach gemacht: JSOM-Pagination behebt die Schwächen von Pagy, Kaminari und Will_Paginate.

Ich habe das Vergnügen, Ihnen ein neues Gem vorzustellen, der zur Unterstützung einer JSON:API-basierten Webanwendung erstellt wurde. Eigenständige Paginierungsunterstützung.

Warum ein zusätzliches Paginierungs-Juwel?

Tja, ich weiss, was Sie jetzt denken - WARUM habe ich mir die Mühe gemacht, ein weiteres Paginierungs-Gem zu bauen? Es gibt viele Paginierungs-Plugins für Ruby-Webanwendungen, um nur ein paar beliebte zu nennen:

The ersten beiden sind "Kaminari" und "Will_paginate". Beide sind eng mit ActiveRecord verbunden, was je nach Bedarf gut oder schlecht sein kann. Sicherlich ist die Implementierung einer der oben genannten extrem einfach, da sie einfach Ihre Modelle erweitern!

Sie haben jedoch zwei grosse Probleme.

  1. Es ist super schwer, sie aus den ActiveRecord-basierten Sammlungen zu verwenden.

  2. Sie sind langsam.

Glücklichweise sind schlaue Leute da draussen bereits beide dieser Probleme angegangen und haben Pagy erfunden.

Pagy ist ein viel attraktiveres Schmuckstück, da es dutzende Male schneller ist als jede der oben aufgeführten Lösungen und wirklich Framework-agnostisch. Es kann in allen Rack-Anwendungen verwendet werden und ich verwende es (direkt oder indirekt) in allen meinen Projekten, egal mit welchem Framework ich spiele.

Es hat jedoch auch mindestens 2 Probleme, die ich für wichtig halte.

1. Die architektonischen Mängel

Das erste Problem, mit dem ich konfrontiert wurde, ist, dass Pagy auf Modulen und einer Menge assumptions basiert ist, die die Benutzer:innen dazu zwingen, viele Konfigurationen vorzunehmen, wenn man diesen ausserhalb von Rails-basierten Standard-Controllern verwenden möchte.

Zum Beispiel nimmt Pagy an, dass es eine params Methode gibt, die in der Klasse zugänglich ist, auf der man das Pagy::Backend Modul basiert. Wenn wir Metadaten und zugehörige Link-Informationen hinzufügen wollen, benötigen wir eine Erweiterung, die ebenfalls eingebunden werden muss, aber diese setzt voraus, dass wir eine request Methode zugänglich haben!

All das ist kein Problem, wenn wir es innerhalb von Rails-Anwendungen verwenden wollen, aber es erzeugt Probleme bei ausgefalleneren Anwendungsfällen.

Sagen wir, wir wollen ein Query Objekt haben, das die Datensätze aus der Datenbank auswählt und das paginierte Ergebnis liefert. Oder eine Endpoint Instanz, wo man gerne als Ergebnis eine bereits serialisierte und paginierte Antwort erhalten würde. Anstatt die Abhängigkeiten wie eine request-Instanz in das Pagy-Objekt zu injizieren, müssen wir Methoden in der Aufruferklasse definieren.

Dies ist ein schwerwiegender Fehler in der Architektur, da die Aufrufenden umfassende Kenntnisse darüber haben muss, wie der Paginator Ressourcen paginiert. Ausserdem zwingt die vom Aufrufenden verwendete Paginierungs-Engine dem/der Aufrufer:in die spezifische Implementierung auf, was es sehr schwer macht, sie in Zukunft zu ersetzen, falls ein Bedarf dafür besteht.

Ich bin mir nicht sicher, warum es auf diese Weise entworfen wurde, wo die Paginierungssteuerung auf überschriebenen Methoden und globalem Status basiert, aber es ist unnötige Komplexität und eine Menge Probleme, die ohne Notwendigkeit eingeführt wurden. Es gibt einen viel einfacheren Weg.

The JSON:API-Unterstützung

Das andere Problem ist, dass, da dieser Edelstein so Framework-agnostisch ist, es immer ein bisschen Code gibt, den man schreiben muss, um ihn mit dem JSON:API-Standard kompatibel zu machen und nicht nur dafür. Zum Beispiel nimmt pagy standardmässig an, dass Sie page und per_page Parameter haben und stürzt bei benutzerdefinierten Paginierungsparametern ab. Da JSON:API vorschlägt, die Paginierung über einen verschachtelten Query-Parameter page[size] und page[number] zu steuern, muss das pagy gem angepasst werden, um es effektiv zu nutzen.

Da ich täglich mit mehreren Projekten arbeite, wäre es wirklich mühsam, das immer wieder zu wiederholen.

The JSOM:Paginierung

Um die beiden oben genannten Probleme zu lösen, habe ich mich entschlossen, einen kleinen Wrapper um pagy zu schreiben, um eine bequeme Erfahrung für jede JSON-API-kompatible Anwendung zu bieten - und habe ihn benannt: jsom-pagination.

JSOM:Pagination ist eine einfache Klasse, die wir an jeder Stelle der Anwendung (nicht nur in den Controllern!) - und in jeder Art von Ruby-Anwendung verwenden können. Sie brauchen keine zusätzliche Konfigurationen und keine verschiedenen Module, um es in Rails oder Hanami oder in einer IRB-Session zu verwenden!

Hier sind zwei Beispiele, wie Sie es in Ihrer App verwenden können.

Zuerst müssen Sie die Instanz eines Paginators instanziieren.

require 'jsom-pagination'
paginator = JSOM::Pagination::Paginator.new

Und um ein Array zu paginieren, muss man es nur mit einer Collection, einem Parameter-Hash und dem Basis-URL-Wert aufrufen - um die zugehörigen Paginierungs-Links zu generieren, wie z.B. next, last, usw.

collection = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pagination_params = { number: 2, size: 3 }
paginated = paginator.call(collection, params: pagination_params, base_url: 'https://example.com'

In Rails-Controllern und ActiveRecord-Sammlungen würde es wie folgt aussehen:

collection = Article.published
pagination_params = params.permit(page: [:number, size]))[:page]
paginated = paginator.call(collection, params: pagination_params, base_url: request.url

So, und was ist das Coole daran?

Es mag auf den ersten Blick nicht beeindruckend aussehen, aber die Injektion der Abhängigkeiten in den Paginator ermöglicht es uns, die Paginierung überall einfach auszuführen. Ausserdem ist die JSON:API-Unterstützung eingebaut.

Alle Meta-Informationen sind sofort zugänglich:

paginated.meta
# => #<JSOM::Pagination::MetaData total=10 pages=4>

paginated.meta.to_h
# => {:total=>10, :pages=>4}

Das Gleiche gilt für Links zu den verwandten Seiten.

paginated.links
# => #<JSOM::Pagination::Links:0x00007fdf5d0dd2b8 @url="https://example.com", @page=#<JSOM::Pagination::Page number=2 size=3>, @total_pages=4, @first="https://example.com?page[size]=3", @prev="https://example.com?page[size]=3", @self="https://example.com?page[number]=2&page[size]=3", @next="https://example.com?page[number]=3&page[size]=3", @last="https://example.com?page[number]=4&page[size]=3">

paginated.links.to_h
# => {
#   :first=>"https://example.com?page[size]=3",
#   :prev=>"https://example.com?page[size]=3",
#   :self=>"https://example.com?page[number]=2&page[size]=3",
#   :next=>"https://example.com?page[number]=3&page[size]=3",
#   :last=>"https://example.com?page[number]=4&page[size]=3"
# }

Und das ist alles.

Serialisierung der paginierten Sammlung

jsom-pagination zwingt Sie nicht dazu, eine bestimmte Serialisierungsmethode zu verwenden, um diese Daten zurückzugeben. Sie können jede beliebige Methode zur Serialisierung des Ergebnisses verwenden. Wenn Sie beispielsweise jsonapi-serializer verwenden, dann sollten sie es so tun

options = { meta: paginated.meta.to_h, links: paginated.links.to_h }
render json: ArticleSerializer.new(paginated.items, options

Etwas Zusätzliches für Rails-Benutzer:innen

Speziell für Rails-Benutzer:innen, habe ich ein einfaches Anliegen geschrieben, in dem wir alle paginierungsbezogenen Methoden sammeln können, um die Nutzung so weit wie möglich zu vereinfachen.

# app/controllers/concerns/paginable.rb

# Collects methods related to paginating resources
# Requires 'serializer' to be defined.
#
module Paginable
  extend ActiveSupport::Concern

  def paginate(collection)
    paginator.call(
      collection,
      params: pagination_params,
      base_url: request.url
    )
  end

  def paginator
    JSOM::Pagination::Paginator.new
  end

  def pagination_params
    params.permit(page: [:number, :size])[:page]
  end

  def render_collection(paginated)
    options =
      {
        meta: paginated.meta.to_h,
        links: paginated.links.to_h
      }
      result = serializer.new(paginated.items, options)
    render json: result, status: :ok
  end
end

Damit können wir die render_collection Methode verwenden, um die Sammlung unter der Haube zu serialisieren:

# app/controllers/articles_controller.rb

def index
  paginated = paginate(Article.all)
  render_collection(paginated)
end

Nun weiss der Controller fast nichts darüber, wie man Sammlungen paginiert, und wir können diese Funktion ganz einfach überall nutzen, wo wir wollen, und unsere Klassen schlank und einfach zu verwalten halten.

Es ist eine enorme Verbesserung und ich hoffe, es hat Ihnen bis jetzt gefallen!

Zusammenfassung

Das Schreiben von nützlichen Juwelen ist keine triviale Sache. Aber in der Ruby-Welt sehe ich, dass die Dinge oft zu kompliziert sind und zu viel voraussetzen. Es ist überraschend, dass ein so einfaches Problem wie die Paginierung so viele Probleme bei der Implementierung und Verwendung der vorgeschlagenen Lösungen erzeugt.

Ich hoffe, dass jsom-pagination Ihnen helfen wird, sie zu umgehen!

Melden Sie auf Twitter, was Sie davon halten!

✍️

ÜBER DEN AUTOR

Sebastian Wilgosz

Ruby-Entwickler

Ruby-Enthusiast, Kaffeeliebhaber und Produktivitätsfanatiker. Ich liebe es, kreative Wege zu finden, um alles, was ich tue, zu verbessern.

Sie haben eine Projektidee? Lassen Sie uns darüber reden und sie zum Leben erwecken.

Ihre hochqualifizierten Spezialisten sind da. Kontaktieren Sie uns, um zu sehen, was wir gemeinsam tun können.

Dariusz Michalski

Dariusz Michalski, CEO

dariusz@useo.pl

Konrad Pochodaj

Konrad Pochodaj, CGO

konrad@useo.pl

Sie haben eine Projektidee? Lassen Sie uns darüber reden und sie zum Leben erwecken.

Ihre hochqualifizierten Spezialisten sind da. Kontaktieren Sie uns, um zu sehen, was wir gemeinsam tun können.

Dariusz Michalski

Dariusz Michalski, CEO

dariusz@useo.pl

Konrad Pochodaj

Konrad Pochodaj, CGO

konrad@useo.pl

Sie haben eine Projektidee? Lassen Sie uns darüber reden und sie zum Leben erwecken.

Ihre hochqualifizierten Spezialisten sind da. Kontaktieren Sie uns, um zu sehen, was wir gemeinsam tun können.

©2009 - 2025 Useo sp. z o.o.