Skip to content

Commit

Permalink
Implement the code for the keyset_for_ui extra
Browse files Browse the repository at this point in the history
  • Loading branch information
ddnexus committed Dec 2, 2024
1 parent c81dbd2 commit ded4f4b
Show file tree
Hide file tree
Showing 11 changed files with 401 additions and 76 deletions.
6 changes: 3 additions & 3 deletions gem/apps/keyset_ar.ru → gem/apps/keyset.ru
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
# bundle exec pagy -h
#
# DEV USAGE
# bundle exec pagy clone keyset_ar
# bundle exec pagy ./keyset_ar.ru
# bundle exec pagy clone keyset
# bundle exec pagy ./keyset.ru
#
# URL
# http://0.0.0.0:8000
Expand Down Expand Up @@ -148,7 +148,7 @@ ActiveRecord::Base.logger = Logger.new(output)
# SQLite DB files
dir = ENV['APP_ENV'].equal?('development') ? '.' : Dir.pwd # app dir in dev or pwd otherwise
abort "ERROR: Cannot create DB files: the directory #{dir.inspect} is not writable." \
unless File.writable?(dir)
unless File.writable?(dir)
# Connection
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: "#{dir}/tmp/pagy-keyset-ar.sqlite3")
# Schema
Expand Down
227 changes: 227 additions & 0 deletions gem/apps/keyset_for_ui.ru
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
# frozen_string_literal: true

# DESCRIPTION
# Showcase the keyset for UI pagination
#
# DOC
# https://ddnexus.github.io/pagy/playground/#5-keyset-apps
#
# BIN HELP
# bundle exec pagy -h
#
# DEV USAGE
# bundle exec pagy clone keyset_for_ui
# bundle exec pagy ./keyset_for_ui.ru
#
# URL
# http://0.0.0.0:8000

VERSION = '9.3.2'

# Bundle
require 'bundler/inline'
require 'bundler'
Bundler.configure
gemfile(ENV['PAGY_INSTALL_BUNDLE'] == 'true') do
source 'https://rubygems.org'
gem 'activerecord'
gem 'puma'
gem 'sinatra'
gem 'sqlite3'
end

# Pagy initializer
require 'pagy/extras/limit'
require 'pagy/extras/keyset_for_ui'
require 'pagy/extras/pagy'
Pagy::DEFAULT[:limit] = 5
Pagy::DEFAULT.freeze

# Sinatra setup
require 'sinatra/base'
# Sinatra application
class PagyKeyset < Sinatra::Base
enable :sessions

include Pagy::Backend
# Root route/action
get '/' do
Time.zone = 'UTC'

@order = { animal: :asc, name: :asc, birthdate: :desc, id: :asc }
@pagy, @pets = pagy_keyset_for_ui(Pet.order(@order))
erb :main
end

helpers do
include Pagy::Frontend

def order_symbol(dir)
{ asc: '&#x2197;', desc: '&#x2198;' }[dir]
end
end

# Views
template :layout do
<<~ERB
<!DOCTYPE html>
<html lang="en">
<html>
<head>
<title>Pagy Keyset App</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style type="text/css">
@media screen { html, body {
font-size: 1rem;
line-height: 1.2s;
padding: 0;
margin: 0;
} }
body {
background: white !important;
margin: 0 !important;
font-family: sans-serif !important;
}
.content {
padding: 1rem 1.5rem 2rem !important;
}
<%= Pagy.root.join('stylesheets', 'pagy.css').read %>
</style>
</head>
<body>
<%= yield %>
</body>
</html>
ERB
end

template :main do
<<~ERB
<div class="content">
<h1>Pagy Keyset App</h1>
<p>Self-contained, standalone app usable to easily reproduce any keyset related pagy issue with ActiveRecord sets.</p>
<p>Please, report the following versions in any new issue.</p>
<h2>Versions</h2>
<ul>
<li>Ruby: <%= RUBY_VERSION %></li>
<li>Rack: <%= Rack::RELEASE %></li>
<li>Sinatra: <%= Sinatra::VERSION %></li>
<li>Pagy: <%= Pagy::VERSION %></li>
</ul>
<h3>Collection</h3>
<div id="records" class="collection">
<table border="1" cellspacing="0" cellpadding="3">
<tr>
<th>animal <%= order_symbol(@order[:animal]) %></th>
<th>name <%= order_symbol(@order[:name]) %></th>
<th>birthdate <%= order_symbol(@order[:birthdate]) %></th>
<th>id <%= order_symbol(@order[:id]) %></th>
</tr>
<% @pets.each do |pet| %>
<tr>
<td><%= pet.animal %></td>
<td><%= pet.name %></td>
<td><%= pet.birthdate %></td>
<td><%= pet.id %></td>
</tr>
<% end %>
</table>
</div>
<p>
<%= pagy_nav(@pagy) %>
</div>
ERB
end
end

# ActiveRecord setup
require 'active_record'

# Match the microsecods with the strings stored into the time columns of SQLite
# ActiveSupport::JSON::Encoding.time_precision = 6

# Log
output = ENV['APP_ENV'].equal?('showcase') ? IO::NULL : $stdout
ActiveRecord::Base.logger = Logger.new(output)
# SQLite DB files
dir = ENV['APP_ENV'].equal?('development') ? '.' : Dir.pwd # app dir in dev or pwd otherwise
abort "ERROR: Cannot create DB files: the directory #{dir.inspect} is not writable." \
unless File.writable?(dir)
# Connection
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: "#{dir}/tmp/pagy-keyset-ar.sqlite3")
# Schema
ActiveRecord::Schema.define do
create_table :pets, force: true do |t|
t.string :animal
t.string :name
t.date :birthdate
end
end

# Models
class Pet < ActiveRecord::Base; end

data = <<~DATA
Luna | dog | 2018-03-10
Coco | cat | 2019-05-15
Dodo | dog | 2020-06-25
Wiki | bird | 2018-03-12
Baby | rabbit | 2020-01-13
Neki | horse | 2021-07-20
Tino | donkey | 2019-06-18
Plot | cat | 2022-09-21
Riki | cat | 2018-09-14
Susi | horse | 2018-10-26
Coco | pig | 2020-08-29
Momo | bird | 2023-08-25
Lili | cat | 2021-07-22
Beli | pig | 2020-07-26
Rocky | bird | 2022-08-19
Vyvy | dog | 2018-05-16
Susi | horse | 2024-01-25
Ella | cat | 2020-02-20
Rocky | dog | 2019-09-19
Juni | rabbit | 2020-08-24
Coco | bird | 2021-03-17
Susi | dog | 2021-07-28
Luna | horse | 2023-05-14
Gigi | pig | 2022-05-19
Coco | cat | 2020-02-20
Nino | donkey | 2019-06-17
Luna | cat | 2022-02-09
Popi | dog | 2020-09-26
Lili | pig | 2022-06-18
Mina | horse | 2021-04-21
Susi | rabbit | 2023-05-18
Toni | donkey | 2018-06-22
Rocky | horse | 2019-09-28
Lili | cat | 2019-03-18
Roby | cat | 2022-06-19
Anto | horse | 2022-08-18
Susi | pig | 2021-04-21
Boly | bird | 2020-03-29
Sky | cat | 2023-07-19
Lili | dog | 2020-01-28
Fami | snake | 2023-04-27
Lopi | pig | 2019-06-19
Rocky | snake | 2022-03-13
Denis | dog | 2022-06-19
Maca | cat | 2022-06-19
Luna | dog | 2022-08-15
Jeme | horse | 2019-08-08
Sary | bird | 2023-04-29
Rocky | bird | 2023-05-14
Coco | dog | 2023-05-27
DATA

# DB seed
pets = []
data.each_line(chomp: true) do |pet|
name, animal, birthdate = pet.split('|').map(&:strip)
pets << { name:, animal:, birthdate: }
end
Pet.insert_all(pets)

run PagyKeyset
6 changes: 3 additions & 3 deletions gem/apps/keyset_s.ru → gem/apps/keyset_sequel.ru
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

# DESCRIPTION
# Showcase the keyset ActiveRecord pagination
# Showcase the keyset Sequel pagination
#
# DOC
# https://ddnexus.github.io/pagy/playground/#5-keyset-apps
Expand All @@ -10,8 +10,8 @@
# bundle exec pagy -h
#
# DEV USAGE
# bundle exec pagy clone keyset_ar
# bundle exec pagy ./keyset_ar.ru
# bundle exec pagy clone keyset_sequel
# bundle exec pagy ./keyset_sequel.ru
#
# URL
# http://0.0.0.0:8000
Expand Down
41 changes: 2 additions & 39 deletions gem/lib/pagy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ def self.root
end

include SharedMethods
include SharedMethodsForUI

attr_reader :count, :from, :in, :last, :next, :offset, :prev, :to
alias pages last
attr_reader :count

# Merge and validate the options, do some simple arithmetic and set the instance variables
def initialize(**vars)
Expand Down Expand Up @@ -62,43 +62,6 @@ def assign_prev_and_next
def check_overflow
raise OverflowError.new(self, :page, "in 1..#{@last}", @page) if @page > @last
end

# Label for the current page. Allow the customization of the output (overridden by the calendar extra)
def label = @page.to_s

# Label for any page. Allow the customization of the output (overridden by the calendar extra)
def label_for(page) = page.to_s

# Return the array of page numbers and :gap e.g. [1, :gap, 8, "9", 10, :gap, 36]
def series(size: @vars[:size], **_)
raise VariableError.new(self, :size, 'to be an Integer >= 0', size) \
unless size.is_a?(Integer) && size >= 0
return [] if size.zero?

[].tap do |series|
if size >= @last
series.push(*1..@last)
else
left = ((size - 1) / 2.0).floor # left half might be 1 page shorter for even size
start = if @page <= left # beginning pages
1
elsif @page > (@last - size + left) # end pages
@last - size + 1
else # intermediate pages
@page - left
end
series.push(*start...start + size)
# Set first and last pages plus gaps when needed, respecting the size
if vars[:ends] && size >= 7
series[0] = 1
series[1] = :gap unless series[1] == 2
series[-2] = :gap unless series[-2] == @last - 1
series[-1] = @last
end
end
series[series.index(@page)] = @page.to_s
end
end
end

require_relative 'pagy/backend'
Expand Down
2 changes: 1 addition & 1 deletion gem/lib/pagy/extras/keyset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
require_relative '../keyset'

class Pagy # :nodoc:
# Add keyset pagination
# Add keyset methods
module KeysetExtra
private

Expand Down
27 changes: 27 additions & 0 deletions gem/lib/pagy/extras/keyset_for_ui.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/keyset/for_ui
# frozen_string_literal: true

require_relative '../keyset/for_ui'

class Pagy # :nodoc:
# Add keyset for UI methods
module KeysetForUIExtra
private

# Return Pagy::Keyset::ForUI object and paginated records
def pagy_keyset_for_ui(set, **vars)
vars[:cache] ||= pagy_keyset_for_ui_cache(vars)
vars[:page] ||= pagy_get_page(vars) # numeric page
vars[:limit] ||= pagy_get_limit(vars)
pagy = Keyset::ForUI.new(set, **vars).finalize
[pagy, pagy.records]
end

# Return the cached hash
def pagy_keyset_for_ui_cache(vars)
key = "pagy-keyset-#{B64.encode(params.slice(vars.delete(:query_params)).to_json)}"
session[key] ||= {}
end
end
Backend.prepend KeysetForUIExtra
end
Loading

0 comments on commit ded4f4b

Please sign in to comment.