From a139bde102c2aae820bcee6bb635ec9076a21760 Mon Sep 17 00:00:00 2001 From: Runar Ingebrigtsen Date: Tue, 3 Feb 2026 21:21:53 +0100 Subject: [PATCH] switch to pagy for pagination --- Gemfile | 3 +++ Gemfile.lock | 3 +++ app/controllers/application_controller.rb | 1 + app/controllers/entries_controller.rb | 7 ++----- app/helpers/application_helper.rb | 2 ++ app/views/entries/_results.html.erb | 22 +++++++++++---------- config/initializers/pagy.rb | 6 ++++++ test/controllers/entries_controller_test.rb | 3 ++- test/system/public_browsing_test.rb | 2 +- 9 files changed, 32 insertions(+), 17 deletions(-) create mode 100644 config/initializers/pagy.rb diff --git a/Gemfile b/Gemfile index 8ddb135..c8fbf0a 100644 --- a/Gemfile +++ b/Gemfile @@ -47,6 +47,9 @@ gem "thruster", require: false # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] gem "image_processing", "~> 1.2" +# Pagination [https://github.com/ddnexus/pagy] +gem "pagy", "~> 8.0" + group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" diff --git a/Gemfile.lock b/Gemfile.lock index 0f036ed..ee537f5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -232,6 +232,7 @@ GEM nokogiri (1.19.0-x86_64-linux-gnu) racc (~> 1.4) ostruct (0.6.3) + pagy (8.6.3) parallel (1.27.0) parser (3.3.10.1) ast (~> 2.4.1) @@ -430,6 +431,7 @@ DEPENDENCIES importmap-rails jbuilder kamal + pagy (~> 8.0) propshaft puma (>= 5.0) rails (~> 8.1.2) @@ -531,6 +533,7 @@ CHECKSUMS nio4r (2.7.5) sha256=6c90168e48fb5f8e768419c93abb94ba2b892a1d0602cb06eef16d8b7df1dca1 nokogiri (1.19.0-x86_64-linux-gnu) sha256=f482b95c713d60031d48c44ce14562f8d2ce31e3a9e8dd0ccb131e9e5a68b58c ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912 + pagy (8.6.3) sha256=537b2ee3119f237dd6c4a0d0a35c67a77b9d91ebb9d4f85e31407c2686774fb2 parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 parser (3.3.10.1) sha256=06f6a725d2cd91e5e7f2b7c32ba143631e1f7c8ae2fb918fc4cebec187e6a688 pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 930bb3a..4046b2b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,6 @@ class ApplicationController < ActionController::Base include BotBlocker + include Pagy::Backend # Changes to the importmap will invalidate the etag for HTML responses stale_when_importmap_changes diff --git a/app/controllers/entries_controller.rb b/app/controllers/entries_controller.rb index cfe5f6d..dffc6c9 100644 --- a/app/controllers/entries_controller.rb +++ b/app/controllers/entries_controller.rb @@ -6,8 +6,6 @@ class EntriesController < ApplicationController @category = params[:category].presence @query = params[:q].to_s.strip @starts_with = params[:starts_with].presence - @page = [ params[:page].to_i, 1 ].max - @per_page = 25 entries_scope = Entry.active_entries entries_scope = entries_scope.with_category(@category) @@ -16,9 +14,8 @@ class EntriesController < ApplicationController entries_scope = entries_scope.alphabetical_for(@language_code) if @query.blank? && @starts_with.blank? && @language_code.present? entries_scope = entries_scope.order(created_at: :desc) if entries_scope.order_values.empty? - @total_entries = entries_scope.count - @total_pages = (@total_entries.to_f / @per_page).ceil - @entries = entries_scope.offset((@page - 1) * @per_page).limit(@per_page) + @pagy, @entries = pagy(entries_scope, items: 25) + @total_entries = @pagy.count @entry_count = Entry.active_entries.count @requested_count = Entry.requested.count diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 3923f72..287e5d4 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,4 +1,6 @@ module ApplicationHelper + include Pagy::Frontend + def language_name(code) supported_languages.find { |l| l.code == code }&.name end diff --git a/app/views/entries/_results.html.erb b/app/views/entries/_results.html.erb index 423f100..b360922 100644 --- a/app/views/entries/_results.html.erb +++ b/app/views/entries/_results.html.erb @@ -86,17 +86,19 @@
-
Page <%= @page %> of <%= [@total_pages, 1].max %>
+
<%= pagy_info(@pagy) %>
- <% previous_page = @page > 1 ? @page - 1 : nil %> - <% next_page = @page < @total_pages ? @page + 1 : nil %> - <% pagination_params = { q: @query.presence, category: @category.presence, language: @language_code.presence, starts_with: @starts_with.presence }.compact %> - <%= link_to "Previous", previous_page ? entries_path(pagination_params.merge(page: previous_page)) : "#", - class: "px-3 py-1.5 rounded-md border border-slate-200 #{previous_page ? 'hover:border-indigo-300' : 'text-slate-300 cursor-not-allowed'}", - data: { turbo_stream: true } %> - <%= link_to "Next", next_page ? entries_path(pagination_params.merge(page: next_page)) : "#", - class: "px-3 py-1.5 rounded-md border border-slate-200 #{next_page ? 'hover:border-indigo-300' : 'text-slate-300 cursor-not-allowed'}", - data: { turbo_stream: true } %> + <% + pagination_params = { q: @query.presence, category: @category.presence, language: @language_code.presence, starts_with: @starts_with.presence }.compact + prev_url = @pagy.prev ? entries_path(pagination_params.merge(page: @pagy.prev)) : nil + next_url = @pagy.next ? entries_path(pagination_params.merge(page: @pagy.next)) : nil + %> + <%= link_to "Previous", prev_url || "#", + class: "px-3 py-1.5 rounded-md border border-slate-200 #{'opacity-50 pointer-events-none' unless prev_url}", + data: (prev_url ? { turbo_stream: true } : {}) %> + <%= link_to "Next", next_url || "#", + class: "px-3 py-1.5 rounded-md border border-slate-200 #{'opacity-50 pointer-events-none' unless next_url}", + data: (next_url ? { turbo_stream: true } : {}) %>
diff --git a/config/initializers/pagy.rb b/config/initializers/pagy.rb new file mode 100644 index 0000000..be10789 --- /dev/null +++ b/config/initializers/pagy.rb @@ -0,0 +1,6 @@ +# Pagy Configuration +require "pagy/extras/overflow" + +Pagy::DEFAULT[:items] = 25 # Match current 25 items per page +Pagy::DEFAULT[:page_param] = :page +Pagy::DEFAULT[:overflow] = :last_page diff --git a/test/controllers/entries_controller_test.rb b/test/controllers/entries_controller_test.rb index ba56056..ff14812 100644 --- a/test/controllers/entries_controller_test.rb +++ b/test/controllers/entries_controller_test.rb @@ -41,7 +41,8 @@ class EntriesControllerTest < ActionDispatch::IntegrationTest test "should paginate results" do get entries_url, params: { page: 2 } assert_response :success - assert_select "div", text: /Page 2 of/i + assert_not_nil assigns(:pagy) + assert_select "div", text: /Displaying/i end test "should handle invalid language code" do diff --git a/test/system/public_browsing_test.rb b/test/system/public_browsing_test.rb index c10a516..557a904 100644 --- a/test/system/public_browsing_test.rb +++ b/test/system/public_browsing_test.rb @@ -102,7 +102,7 @@ class PublicBrowsingTest < ApplicationSystemTestCase visit root_path assert_selector ".entry-row", count: 25 - assert_link "2" + assert_link "Next" end test "visitor sees entry statistics" do