Skip to content

Commit

Permalink
WIP: Add keyset files
Browse files Browse the repository at this point in the history
  • Loading branch information
ddnexus committed Jul 7, 2024
1 parent 9251751 commit 25492f4
Show file tree
Hide file tree
Showing 8 changed files with 430 additions and 24 deletions.
222 changes: 222 additions & 0 deletions gem/apps/keyset.ru
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
# frozen_string_literal: true

# Starting point to reproduce keyset related pagy issues

# DEV USAGE
# pagy clone rails
# pagy ./keyset.ru

# URL
# http://0.0.0.0:8000

# HELP
# pagy -h

# DOC
# https://ddnexus.github.io/pagy/playground/#5-keyset-app

VERSION = '8.6.2'

# Gemfile
require 'bundler/inline'
require 'bundler'
Bundler.configure
gemfile(ENV['PAGY_INSTALL_BUNDLE'] == 'true') do
source 'https://rubygems.org'
gem 'oj'
gem 'puma'
gem 'rails'
# activerecord/sqlite3_adapter.rb probably useless) constraint !!!
# https://github.com/rails/rails/blame/v7.1.3.4/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L14
gem 'sqlite3', '~> 1.4.0'
end

# require 'rails/all' # too much stuff
require 'action_controller/railtie'
require 'active_record'

OUTPUT = Rails.env.showcase? ? IO::NULL : $stdout

# Rails config
class PagyKeyset < Rails::Application # :nodoc:
config.root = __dir__
config.session_store :cookie_store, key: 'cookie_store_key'
Rails.application.credentials.secret_key_base = 'absolute_secret'

config.logger = Logger.new(OUTPUT)
Rails.logger = config.logger

routes.draw do
root to: 'comments#index'
get '/javascripts/:file', to: 'pagy#javascripts', file: /.*/
end
end

# AR config
dir = Rails.env.development? ? '.' : Dir.pwd # app dir in dev or pwd otherwise
unless File.writable?(dir)
warn "ERROR: directory #{dir.inspect} is not writable (the pagy-rails-app needs to create DB files)"
exit 1
end

# Pagy initializer
require 'pagy/extras/pagy'
require 'pagy/extras/items'
require 'pagy/extras/overflow'
Pagy::DEFAULT[:items] = 10
Pagy::DEFAULT[:overflow] = :empty_page
Pagy::DEFAULT.freeze

# Activerecord initializer
ActiveRecord::Base.logger = Logger.new(OUTPUT)
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: "#{dir}/tmp/pagy-keyset.sqlite3")
ActiveRecord::Schema.define do
create_table :pets, force: true do |t|
t.string :name
t.string :animal
t.datetime :birth
end
end

# Models
class Pet < ActiveRecord::Base # :nodoc:
end # :nodoc:

# Helpers
module CommentsHelper
include Pagy::Frontend
end

# Controllers
class CommentsController < ActionController::Base # :nodoc:
include Rails.application.routes.url_helpers
include Pagy::Backend

def index
# @pagy, @pets = pagy_keyset(Pets.all,
# reorder: %i[animal name birth id],
# direction: :asc,
# items: 5)
@pets = Pet.all
render inline: TEMPLATE
end
end

TEMPLATE = <<~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>
<div class="content">
<h1>Pagy Keyset App</h1>
<p>Self-contained, standalone Rails app usable to easily reproduce any keyset related pagy issue.</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>Rails: <%== Rails.version %></li>
<li>Pagy: <%== Pagy::VERSION %></li>
</ul>
<h3>Collection</h3>
<div id="records" class="collection">
<% @pets.each do |pet| %>
<p style="margin: 0;"><%= pet.name %></p>
<% end %>
</div>
<hr>
<%# pagy_next_a(@pagy) %>
</div>
</body>
</html>
ERB

PETS = <<~PETS
Kiki | dog | 2018-3-10
Losi | cat | 2019-5-15
Dodo | dog | 2020-6-25
Wiki | brid | 2018-3-12
Baby | rabbit | 2020-1-13
Neki | horse | 2021-7-20
Tino | donkey | 2019-6-18
Plot | cat | 2022-9-21
Riki | snake | 2018-9-14
Lucky | horse | 2018-10-26
Coco | pig | 2020-8-29
Momo | chicken | 2023-8-25
Lili | cat | 2021-7-22
Beli | pig | 2020-7-26
Milla | chicken | 2022-8-19
Vyvy | panda | 2018-5-16
Susi | cow | 2024-1-25
Ella | cat | 2022-2-7
Rocky | dog | 2019-9-19
Juni | rabbit | 2020-8-24
Sini | bird | 2021-3-17
Hasky | dog | 2021-7-28
Luna | horse | 2023-5-14
Gigi | pig | 2022-5-19
Fema | panda | 2020-2-20
Nino | donkey | 2019-6-17
Dani | cat | 2022-2-9
Popi | dog | 2020-9-26
Anne | pig | 2022-6-18
Mina | cow | 2021-4-21
Willy | rabbit | 2023-5-18
Toni | donkey | 2018-6-22
Sooly | horse | 2019-9-28
Taky | panda | 2019-3-18
Roby | cat | 2023-4-14
Ano | horse | 2022-8-18
Eno | pig | 2020-5-16
Boly | bird | 2020-3-29
Sky | cat | 2023-7-19
Lili | dog | 2020-1-28
Fami | snake | 2023-4-27
Lopi | pig | 2019-6-19
Hopo | snake | 2022-3-13
Denis | dog | 2022-6-19
Maca | cat | 2024-2-11
Cako | panda | 2022-8-15
Jeme | horse | 2019-8-8
Sary | donkey | 2023-4-29
Vivo | chicken | 2023-5-14
Nomo | panda | 2023-5-27
PETS

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

run PagyKeyset
23 changes: 3 additions & 20 deletions gem/lib/pagy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
# frozen_string_literal: true

require 'pathname'
require_relative 'pagy/init_vars'

# Core class
class Pagy
include InitVars

VERSION = '8.6.3'

# Gem root pathname to get the path of Pagy files stylesheets, javascripts, apps, locales, etc.
Expand Down Expand Up @@ -81,26 +84,6 @@ def label
@page.to_s
end

protected

# Apply defaults, cleanup blanks and set @vars
def normalize_vars(vars)
@vars = DEFAULT.merge(vars.delete_if { |k, v| DEFAULT.key?(k) && (v.nil? || v == '') })
end

# Setup and validates the passed vars: var must be present and value.to_i must be >= to min
def setup_vars(name_min)
name_min.each do |name, min|
raise VariableError.new(self, name, ">= #{min}", @vars[name]) \
unless @vars[name]&.respond_to?(:to_i) && instance_variable_set(:"@#{name}", @vars[name].to_i) >= min
end
end

# Setup @items (overridden by the gearbox extra)
def setup_items_var
setup_vars(items: 1)
end

# Setup @offset (overridden by the gearbox extra)
def setup_offset_var
@offset = (@items * (@page - 1)) + @outset # may be already set from gear_box
Expand Down
33 changes: 33 additions & 0 deletions gem/lib/pagy/b64.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

class Pagy # :nodoc:
# Cheap Base64 specialized methods to avoid dependencies
module B64
module_function

def encode(bin)
[bin].pack('m0')
end

def decode(str)
str.unpack1('m0')
end

def urlsafe_encode(bin)
str = encode(bin)
str.chomp!('==') or str.chomp!('=')
str.tr!('+/', '-_')
str
end

def urlsafe_decode(str)
if !str.end_with?('=') && str.length % 4 != 0
str = str.ljust((str.length + 3) & ~3, '=')
str.tr!('-_', '+/')
else
str = str.tr('-_', '+/')
end
decode(str)
end
end
end
6 changes: 6 additions & 0 deletions gem/lib/pagy/exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,10 @@ class I18nError < StandardError; end

# Generic internal error
class InternalError < StandardError; end

# Keyset missing order error
class MissingOrderError < ArgumentError; end

# Keyset inconsistent order error
class InconsistentOrderError < ArgumentError; end
end
8 changes: 4 additions & 4 deletions gem/lib/pagy/extras/js_tools.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require_relative '../b64'

class Pagy # :nodoc:
DEFAULT[:steps] = false # default false will use {0 => @vars[:size]}

Expand Down Expand Up @@ -51,17 +53,15 @@ module FrontendAddOn
# Base64 encoded JSON is smaller than HTML escaped JSON
def pagy_data(pagy, *args)
args << pagy.vars[:page_param] if pagy.vars[:trim_extra]
strict_base64_encoded = [Oj.dump(args, mode: :strict)].pack('m0')
%(data-pagy="#{strict_base64_encoded}")
%(data-pagy="#{B64.encode(Oj.dump(args, mode: :strict))}")
end
else
require 'json'
# Return a data tag with the base64 encoded JSON-serialized args generated with the slower to_json
# Base64 encoded JSON is smaller than HTML escaped JSON
def pagy_data(pagy, *args)
args << pagy.vars[:page_param] if pagy.vars[:trim_extra]
strict_base64_encoded = [args.to_json].pack('m0')
%(data-pagy="#{strict_base64_encoded}")
%(data-pagy="#{B64.encode(args.to_json)}")
end
end
end
Expand Down
42 changes: 42 additions & 0 deletions gem/lib/pagy/extras/keyset.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/keyset
# frozen_string_literal: true

require_relative '../keyset'
require 'base64'

class Pagy # :nodoc:
# Add keyset pagination
module KeysetExtra
# Return Pagy::Keyset object and paginated records
def pagy_keyset(scope, vars = {})
pagy = Keyset.new(scope:, **pagy_keyset_get_vars(scope, vars))
[pagy, pagy_keyset_get_items(scope, pagy)]
end

# Sub-method called only by #pagy_keyset: here for easy customization of variables by overriding
def pagy_keyset_get_vars(scope, vars)
pagy_set_items_from_params(vars) if defined?(ItemsExtra)
vars[:page] ||= pagy_get_page(vars)
vars[:order] ||= pagy_get_order(scope)
end

# Get the page integer from the params
# Overridable by the jsonapi extra
def pagy_get_page(vars)
params[vars[:page_param] || DEFAULT[:page_param]]
end

# Extract the order from the scope
def pagy_get_order(scope)
scope.order_values.each_with_object([]) do |node, order|
order << { node.value.name.to_sym => node.direction }
end
end

# Sub-method called only by #pagy_keyset: here for easy customization of record-extraction by overriding
def pagy_keyset_get_items(scope, pagy)
scope.where(pagy.where).limit(pagy.items)
end
end
Backend.prepend KeysetExtra
end
Loading

0 comments on commit 25492f4

Please sign in to comment.