From 50f22140218edbdc25562f629710e31d2227f7c0 Mon Sep 17 00:00:00 2001
From: Rikuoh <mail@riq0h.jp>
Date: Sat, 22 Feb 2025 23:39:32 +0900
Subject: [PATCH] =?UTF-8?q?=E3=83=9A=E3=83=BC=E3=82=B8=E3=83=8D=E3=83=BC?=
 =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E3=82=92kaminari=E3=80=81=E6=A4=9C?=
 =?UTF-8?q?=E7=B4=A2=E3=82=92ransack=E3=81=AB=E7=BD=AE=E3=81=8D=E6=8F=9B?=
 =?UTF-8?q?=E3=81=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Gemfile                                       |  3 +++
 Gemfile.lock                                  | 18 +++++++++++++
 app/controllers/memos_controller.rb           | 12 ++++-----
 .../controllers/search_controller.js          | 10 -------
 app/models/memo.rb                            | 12 ++++-----
 app/views/kaminari/_first_page.html.erb       | 11 ++++++++
 app/views/kaminari/_gap.html.erb              |  8 ++++++
 app/views/kaminari/_last_page.html.erb        | 11 ++++++++
 app/views/kaminari/_next_page.html.erb        |  3 +++
 app/views/kaminari/_page.html.erb             | 12 +++++++++
 app/views/kaminari/_paginator.html.erb        | 15 +++++++++++
 app/views/kaminari/_prev_page.html.erb        | 11 ++++++++
 app/views/memos/index.html.erb                | 27 +++++++------------
 app/views/memos/index.turbo_stream.erb        |  8 ------
 config/initializers/kaminari_config.rb        | 14 ++++++++++
 15 files changed, 127 insertions(+), 48 deletions(-)
 delete mode 100644 app/javascript/controllers/search_controller.js
 create mode 100644 app/views/kaminari/_first_page.html.erb
 create mode 100644 app/views/kaminari/_gap.html.erb
 create mode 100644 app/views/kaminari/_last_page.html.erb
 create mode 100644 app/views/kaminari/_next_page.html.erb
 create mode 100644 app/views/kaminari/_page.html.erb
 create mode 100644 app/views/kaminari/_paginator.html.erb
 create mode 100644 app/views/kaminari/_prev_page.html.erb
 create mode 100644 config/initializers/kaminari_config.rb

diff --git a/Gemfile b/Gemfile
index 5826ff2..2ec0477 100644
--- a/Gemfile
+++ b/Gemfile
@@ -16,6 +16,9 @@ gem 'sqlite3', '>= 1.4'
 gem 'solid_cache'
 gem 'solid_queue'
 
+gem 'kaminari'
+gem 'ransack'
+
 # Use the Puma web server [https://github.com/puma/puma]
 gem 'puma', '>= 5.0'
 
diff --git a/Gemfile.lock b/Gemfile.lock
index 4f8f150..cef2e58 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -120,6 +120,18 @@ GEM
     jbuilder (2.13.0)
       actionview (>= 5.0.0)
       activesupport (>= 5.0.0)
+    kaminari (1.2.2)
+      activesupport (>= 4.1.0)
+      kaminari-actionview (= 1.2.2)
+      kaminari-activerecord (= 1.2.2)
+      kaminari-core (= 1.2.2)
+    kaminari-actionview (1.2.2)
+      actionview
+      kaminari-core (= 1.2.2)
+    kaminari-activerecord (1.2.2)
+      activerecord
+      kaminari-core (= 1.2.2)
+    kaminari-core (1.2.2)
     logger (1.6.6)
     loofah (2.24.0)
       crass (~> 1.0.2)
@@ -212,6 +224,10 @@ GEM
       thor (~> 1.0, >= 1.2.2)
       zeitwerk (~> 2.6)
     rake (13.2.1)
+    ransack (4.3.0)
+      activerecord (>= 6.1.5)
+      activesupport (>= 6.1.5)
+      i18n
     rdoc (6.12.0)
       psych (>= 4.0.0)
     regexp_parser (2.10.0)
@@ -305,9 +321,11 @@ DEPENDENCIES
   debug
   importmap-rails
   jbuilder
+  kaminari
   puma (>= 5.0)
   rails (= 8.0.1)
   rails-i18n
+  ransack
   selenium-webdriver
   solid_cache
   solid_queue
diff --git a/app/controllers/memos_controller.rb b/app/controllers/memos_controller.rb
index 4c30c81..37117d1 100644
--- a/app/controllers/memos_controller.rb
+++ b/app/controllers/memos_controller.rb
@@ -2,13 +2,11 @@ class MemosController < ApplicationController
   before_action :set_memo, only: %i[edit update destroy]
 
   def index
-    page = (params[:page] || 0).to_i
-    @memos = Memo.search(params[:query])
-                 .order(created_at: :desc)
-                 .limit(4)
-                 .offset(page * 4)
-
-    @has_next = Memo.search(params[:query]).count > (page + 1) * 4
+    @q = Memo.ransack(params[:q])
+    @memos = @q.result(distinct: true)
+               .order(created_at: :desc)
+               .page(params[:page])
+               .per(4)
 
     respond_to do |format|
       format.html
diff --git a/app/javascript/controllers/search_controller.js b/app/javascript/controllers/search_controller.js
deleted file mode 100644
index 25ca422..0000000
--- a/app/javascript/controllers/search_controller.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Controller } from "@hotwired/stimulus";
-
-export default class extends Controller {
-  submit() {
-    clearTimeout(this.timeout);
-    this.timeout = setTimeout(() => {
-      this.element.requestSubmit();
-    }, 300);
-  }
-}
diff --git a/app/models/memo.rb b/app/models/memo.rb
index d6125fd..901e1f0 100644
--- a/app/models/memo.rb
+++ b/app/models/memo.rb
@@ -1,11 +1,11 @@
 class Memo < ApplicationRecord
   validates :content, presence: true
 
-  def self.search(query)
-    if query.present?
-      where('content LIKE ?', "%#{query}%").order(created_at: :desc)
-    else
-      order(created_at: :desc)
-    end
+  def self.ransackable_attributes(_auth_object = nil)
+    %w[content created_at id updated_at]
+  end
+
+  def self.ransackable_associations(_auth_object = nil)
+    []
   end
 end
diff --git a/app/views/kaminari/_first_page.html.erb b/app/views/kaminari/_first_page.html.erb
new file mode 100644
index 0000000..0cc83b9
--- /dev/null
+++ b/app/views/kaminari/_first_page.html.erb
@@ -0,0 +1,11 @@
+<%# Link to the "First" page
+  - available local variables
+    url:           url to the first page
+    current_page:  a page object for the currently displayed page
+    total_pages:   total number of pages
+    per_page:      number of items to fetch per page
+    remote:        data-remote
+-%>
+<span class="first">
+  <%= link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, remote: remote %>
+</span>
diff --git a/app/views/kaminari/_gap.html.erb b/app/views/kaminari/_gap.html.erb
new file mode 100644
index 0000000..bbb0f98
--- /dev/null
+++ b/app/views/kaminari/_gap.html.erb
@@ -0,0 +1,8 @@
+<%# Non-link tag that stands for skipped pages...
+  - available local variables
+    current_page:  a page object for the currently displayed page
+    total_pages:   total number of pages
+    per_page:      number of items to fetch per page
+    remote:        data-remote
+-%>
+<span class="page gap"><%= t('views.pagination.truncate').html_safe %></span>
diff --git a/app/views/kaminari/_last_page.html.erb b/app/views/kaminari/_last_page.html.erb
new file mode 100644
index 0000000..bc777b4
--- /dev/null
+++ b/app/views/kaminari/_last_page.html.erb
@@ -0,0 +1,11 @@
+<%# Link to the "Last" page
+  - available local variables
+    url:           url to the last page
+    current_page:  a page object for the currently displayed page
+    total_pages:   total number of pages
+    per_page:      number of items to fetch per page
+    remote:        data-remote
+-%>
+<span class="last">
+  <%= link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, remote: remote %>
+</span>
diff --git a/app/views/kaminari/_next_page.html.erb b/app/views/kaminari/_next_page.html.erb
new file mode 100644
index 0000000..ae9875b
--- /dev/null
+++ b/app/views/kaminari/_next_page.html.erb
@@ -0,0 +1,3 @@
+<%= link_to_next_page @memos, "ζ°—εŠ›εεˆ†πŸ˜€",
+    class: "inline-flex items-center px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors duration-200",
+    data: { turbo_frame: "memos" } %>
diff --git a/app/views/kaminari/_page.html.erb b/app/views/kaminari/_page.html.erb
new file mode 100644
index 0000000..393bfc4
--- /dev/null
+++ b/app/views/kaminari/_page.html.erb
@@ -0,0 +1,12 @@
+<%# Link showing page number
+  - available local variables
+    page:          a page object for "this" page
+    url:           url to this page
+    current_page:  a page object for the currently displayed page
+    total_pages:   total number of pages
+    per_page:      number of items to fetch per page
+    remote:        data-remote
+-%>
+<span class="page<%= ' current' if page.current? %>">
+  <%= link_to_unless page.current?, page, url, {remote: remote, rel: page.rel} %>
+</span>
diff --git a/app/views/kaminari/_paginator.html.erb b/app/views/kaminari/_paginator.html.erb
new file mode 100644
index 0000000..ba19e2f
--- /dev/null
+++ b/app/views/kaminari/_paginator.html.erb
@@ -0,0 +1,15 @@
+<%= paginator.render do %>
+  <nav class="flex justify-center space-x-2">
+    <%= first_page_tag unless current_page.first? %>
+    <%= prev_page_tag unless current_page.first? %>
+    <% each_page do |page| %>
+      <% if page.display_tag? %>
+        <%= page_tag page %>
+      <% elsif !page.was_truncated? %>
+        <%= gap_tag %>
+      <% end %>
+    <% end %>
+    <%= next_page_tag unless current_page.last? %>
+    <%= last_page_tag unless current_page.last? %>
+  </nav>
+<% end %>
diff --git a/app/views/kaminari/_prev_page.html.erb b/app/views/kaminari/_prev_page.html.erb
new file mode 100644
index 0000000..0f32af4
--- /dev/null
+++ b/app/views/kaminari/_prev_page.html.erb
@@ -0,0 +1,11 @@
+<%# Link to the "Previous" page
+  - available local variables
+    url:           url to the previous page
+    current_page:  a page object for the currently displayed page
+    total_pages:   total number of pages
+    per_page:      number of items to fetch per page
+    remote:        data-remote
+-%>
+<span class="prev">
+  <%= link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote %>
+</span>
diff --git a/app/views/memos/index.html.erb b/app/views/memos/index.html.erb
index 586f9cc..43c2f4c 100644
--- a/app/views/memos/index.html.erb
+++ b/app/views/memos/index.html.erb
@@ -1,37 +1,30 @@
 <div class="mb-8 mt-8">
   <div class="flex items-center gap-4">
-    <%= form_with url: memos_path, method: :get,
-        data: { turbo_frame: "search-results", controller: "search" } do |f| %>
+    <%= search_form_for @q, html: { data: { turbo_frame: "search-results", controller: "search" } } do |f| %>
       <div class="relative">
-        <%= f.text_field :query,
+        <%= f.text_field :content_cont,
             class: "w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500",
             placeholder: "怜紒...",
-            data: { action: "input->search#submit" },
-            value: params[:query] %>
+            data: { action: "input->search#submit" } %>
       </div>
     <% end %>
     <%= link_to new_memo_path, class: "bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-lg transition-colors duration-200" do %>
       <span class="flex items-center">
-        </svg>
         οΌ‹ ε‡ΊεŠ›ι–‹ε§‹
       </span>
     <% end %>
   </div>
 </div>
 
-<div id="memos">
-  <%= turbo_frame_tag "memos" do %>
+<%= turbo_frame_tag "memos" do %>
   <div id="memos-container" class="flex flex-col gap-4">
     <%= render @memos %>
-    <%= render "empty_results", query: params[:query] if @memos.empty? %>
+    <%= render "empty_results", query: @q&.content_cont if @memos.empty? %>
   </div>
-<% end %>
-  <div id="load-more" class="text-center mt-8">
-  <% if @has_next %>
-    <%= link_to "ζ°—εŠ›εεˆ†πŸ˜€",
-        memos_path(page: (params[:page] || 0).to_i + 1, query: params[:query]),
+
+  <div class="text-center mt-8">
+    <%= link_to_next_page @memos, "ζ°—εŠ›εεˆ†πŸ˜€",
         class: "inline-flex items-center px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors duration-200",
         data: { turbo_frame: "memos" } %>
-  <% end %>
-</div>
-</div>
+  </div>
+<% end %>
diff --git a/app/views/memos/index.turbo_stream.erb b/app/views/memos/index.turbo_stream.erb
index e1105c2..9ddb0ce 100644
--- a/app/views/memos/index.turbo_stream.erb
+++ b/app/views/memos/index.turbo_stream.erb
@@ -1,11 +1,3 @@
 <%= turbo_stream.append "memos-container" do %>
   <%= render @memos %>
 <% end %>
-<div id="load-more" class="text-center mt-8">
-  <% if @has_next %>
-    <%= link_to "ζ°—εŠ›εεˆ†πŸ˜€",
-        memos_path(page: (params[:page] || 0).to_i + 1, query: params[:query]),
-        class: "inline-flex items-center px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors duration-200",
-        data: { turbo_frame: "memos" } %>
-  <% end %>
-</div>
diff --git a/config/initializers/kaminari_config.rb b/config/initializers/kaminari_config.rb
new file mode 100644
index 0000000..4ba6ee3
--- /dev/null
+++ b/config/initializers/kaminari_config.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+Kaminari.configure do |config|
+  # config.default_per_page = 25
+  # config.max_per_page = nil
+  # config.window = 4
+  # config.outer_window = 0
+  # config.left = 0
+  # config.right = 0
+  # config.page_method_name = :page
+  # config.param_name = :page
+  # config.max_pages = nil
+  # config.params_on_first_page = false
+end