rails_claude_skills 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.yml +134 -0
  3. data/.github/ISSUE_TEMPLATE/config.yml +11 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.yml +129 -0
  5. data/.github/ISSUE_TEMPLATE/question.yml +90 -0
  6. data/.github/dependabot.yml +19 -0
  7. data/.github/workflows/ci.yml +77 -0
  8. data/.github/workflows/release.yml +66 -0
  9. data/.rubocop.yml +52 -0
  10. data/CHANGELOG.md +94 -0
  11. data/CLAUDE.md +332 -0
  12. data/CODE_OF_CONDUCT.md +134 -0
  13. data/CONTRIBUTING.md +580 -0
  14. data/LICENSE.txt +21 -0
  15. data/README.md +544 -0
  16. data/Rakefile +8 -0
  17. data/lib/generators/claude/agent/agent_generator.rb +71 -0
  18. data/lib/generators/claude/agent/templates/agent.md.tt +62 -0
  19. data/lib/generators/claude/command/command_generator.rb +50 -0
  20. data/lib/generators/claude/command/templates/command.md.tt +28 -0
  21. data/lib/generators/claude/commands_library/create-pr.md +27 -0
  22. data/lib/generators/claude/commands_library/dbchange.md +19 -0
  23. data/lib/generators/claude/commands_library/quality.md +20 -0
  24. data/lib/generators/claude/commands_library/stimulus.md +19 -0
  25. data/lib/generators/claude/commands_library/turbo-feature.md +17 -0
  26. data/lib/generators/claude/install/install_generator.rb +211 -0
  27. data/lib/generators/claude/install/templates/README.md.tt +59 -0
  28. data/lib/generators/claude/install/templates/USAGE +28 -0
  29. data/lib/generators/claude/install/templates/agents/api-dev.md.tt +46 -0
  30. data/lib/generators/claude/install/templates/agents/fullstack-dev.md.tt +48 -0
  31. data/lib/generators/claude/install/templates/agents/rails-developer.md.tt +40 -0
  32. data/lib/generators/claude/install/templates/settings.local.json.tt +13 -0
  33. data/lib/generators/claude/rule/rule_generator.rb +175 -0
  34. data/lib/generators/claude/rule/templates/rule.md.tt +7 -0
  35. data/lib/generators/claude/rules_library/code-style.md +37 -0
  36. data/lib/generators/claude/rules_library/database.md +47 -0
  37. data/lib/generators/claude/rules_library/hotwire.md +56 -0
  38. data/lib/generators/claude/rules_library/security.md +54 -0
  39. data/lib/generators/claude/rules_library/testing.md +47 -0
  40. data/lib/generators/claude/skill/skill_generator.rb +196 -0
  41. data/lib/generators/claude/skill/templates/SKILL.md.tt +27 -0
  42. data/lib/generators/claude/skills_library/create-task-files/SKILL.md +311 -0
  43. data/lib/generators/claude/skills_library/create-task-files/templates/bug.md +60 -0
  44. data/lib/generators/claude/skills_library/create-task-files/templates/epic.md +47 -0
  45. data/lib/generators/claude/skills_library/create-task-files/templates/issue.md +45 -0
  46. data/lib/generators/claude/skills_library/create-task-files/templates/user-story.md +57 -0
  47. data/lib/generators/claude/skills_library/minitest-testing/SKILL.md +398 -0
  48. data/lib/generators/claude/skills_library/minitest-testing/references/examples.md +889 -0
  49. data/lib/generators/claude/skills_library/plan-feature/SKILL.md +253 -0
  50. data/lib/generators/claude/skills_library/rails-api-controllers/SKILL.md +1041 -0
  51. data/lib/generators/claude/skills_library/rails-api-controllers/references/api-documentation.md +422 -0
  52. data/lib/generators/claude/skills_library/rails-api-controllers/references/serialization.md +456 -0
  53. data/lib/generators/claude/skills_library/rails-auth-with-devise/SKILL.md +191 -0
  54. data/lib/generators/claude/skills_library/rails-auth-with-devise/references/advanced.md +331 -0
  55. data/lib/generators/claude/skills_library/rails-auth-with-devise/references/api-auth.md +266 -0
  56. data/lib/generators/claude/skills_library/rails-auth-with-devise/references/omniauth.md +194 -0
  57. data/lib/generators/claude/skills_library/rails-authorization-cancancan/SKILL.md +603 -0
  58. data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/api-authorization.md +543 -0
  59. data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/complex-permissions.md +572 -0
  60. data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/multi-tenancy.md +373 -0
  61. data/lib/generators/claude/skills_library/rails-controllers/SKILL.md +514 -0
  62. data/lib/generators/claude/skills_library/rails-debugging/SKILL.md +260 -0
  63. data/lib/generators/claude/skills_library/rails-deployment/SKILL.md +437 -0
  64. data/lib/generators/claude/skills_library/rails-deployment/references/examples.md +901 -0
  65. data/lib/generators/claude/skills_library/rails-hotwire/SKILL.md +367 -0
  66. data/lib/generators/claude/skills_library/rails-jobs/MISSION_CONTROL_SETUP.md +639 -0
  67. data/lib/generators/claude/skills_library/rails-jobs/SKILL.md +704 -0
  68. data/lib/generators/claude/skills_library/rails-mailers/SKILL.md +549 -0
  69. data/lib/generators/claude/skills_library/rails-models/SKILL.md +379 -0
  70. data/lib/generators/claude/skills_library/rails-pagination-kaminari/SKILL.md +622 -0
  71. data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/api-pagination.md +523 -0
  72. data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/custom-themes.md +498 -0
  73. data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/performance.md +478 -0
  74. data/lib/generators/claude/skills_library/rails-views/SKILL.md +508 -0
  75. data/lib/generators/claude/skills_library/refine-requirements/SKILL.md +226 -0
  76. data/lib/generators/claude/skills_library/refine-requirements/references/examples.md +344 -0
  77. data/lib/generators/claude/skills_library/refine-requirements/references/reference.md +298 -0
  78. data/lib/generators/claude/skills_library/rspec-testing/SKILL.md +572 -0
  79. data/lib/generators/claude/skills_library/rspec-testing/references/better_specs_guide.md +273 -0
  80. data/lib/generators/claude/skills_library/rspec-testing/references/thoughtbot_patterns.md +407 -0
  81. data/lib/generators/claude/skills_library/tailwindcss/SKILL.md +371 -0
  82. data/lib/generators/claude/views/views_generator.rb +113 -0
  83. data/lib/rails_claude_skills/railtie.rb +16 -0
  84. data/lib/rails_claude_skills/version.rb +5 -0
  85. data/lib/rails_claude_skills.rb +27 -0
  86. data/sig/rails_claude_skills.rbs +4 -0
  87. metadata +199 -0
@@ -0,0 +1,622 @@
1
+ ---
2
+ name: rails-pagination-kaminari
3
+ description: "Pagination for Ruby on Rails applications using Kaminari. Use when: (1) Implementing pagination for database records, (2) Building paginated API endpoints, (3) Customizing pagination UI with themes, (4) Handling large datasets efficiently, (5) Creating infinite scroll, (6) Paginating arrays or custom collections, (7) Adding SEO-friendly pagination URLs, (8) Internationalizing pagination labels"
4
+ ---
5
+
6
+ # Rails Pagination with Kaminari
7
+
8
+ Kaminari is a scope and engine-based pagination library that provides a clean, powerful, customizable paginator for Rails applications. It's non-intrusive, chainable with ActiveRecord, and highly customizable.
9
+
10
+ ## Quick Setup
11
+
12
+ ```bash
13
+ # Add to Gemfile
14
+ bundle add kaminari
15
+
16
+ # Generate configuration file (optional)
17
+ rails g kaminari:config
18
+
19
+ # Generate view templates for customization (optional)
20
+ rails g kaminari:views default
21
+ ```
22
+
23
+ ## Basic Usage
24
+
25
+ ### Controller Pagination
26
+
27
+ ```ruby
28
+ # app/controllers/posts_controller.rb
29
+ class PostsController < ApplicationController
30
+ def index
31
+ @posts = Post.order(:created_at).page(params[:page])
32
+ # Returns 25 items per page by default
33
+ end
34
+ end
35
+ ```
36
+
37
+ ### View Helper
38
+
39
+ ```erb
40
+ <!-- app/views/posts/index.html.erb -->
41
+ <%= paginate @posts %>
42
+ ```
43
+
44
+ That's it! Kaminari automatically adds pagination links.
45
+
46
+ ## Core Methods
47
+
48
+ ### Page Scope
49
+
50
+ ```ruby
51
+ # Basic pagination
52
+ User.page(1) # First page, 25 items
53
+ User.page(params[:page]) # Dynamic page from params
54
+
55
+ # Custom per-page
56
+ User.page(1).per(50) # 50 items per page
57
+
58
+ # Chaining with scopes
59
+ User.active.order(:name).page(params[:page]).per(20)
60
+
61
+ # With associations
62
+ User.includes(:posts).page(params[:page])
63
+ ```
64
+
65
+ ### Pagination Metadata
66
+
67
+ ```ruby
68
+ users = User.page(2).per(20)
69
+
70
+ users.current_page #=> 2
71
+ users.total_pages #=> 10
72
+ users.total_count #=> 200
73
+ users.limit_value #=> 20
74
+ users.first_page? #=> false
75
+ users.last_page? #=> false
76
+ users.next_page #=> 3
77
+ users.prev_page #=> 1
78
+ users.out_of_range? #=> false
79
+ ```
80
+
81
+ ## Configuration
82
+
83
+ ### Global Configuration
84
+
85
+ ```ruby
86
+ # config/initializers/kaminari_config.rb
87
+ Kaminari.configure do |config|
88
+ config.default_per_page = 25 # Default items per page
89
+ config.max_per_page = 100 # Maximum allowed per page
90
+ config.max_pages = nil # Maximum pages (nil = unlimited)
91
+ config.window = 4 # Inner window size
92
+ config.outer_window = 0 # Outer window size
93
+ config.left = 0 # Left outer window
94
+ config.right = 0 # Right outer window
95
+ config.page_method_name = :page # Method name (change if conflicts)
96
+ config.param_name = :page # URL parameter name
97
+ end
98
+ ```
99
+
100
+ ### Per-Model Configuration
101
+
102
+ ```ruby
103
+ # app/models/post.rb
104
+ class Post < ApplicationRecord
105
+ paginates_per 50 # This model shows 50 per page
106
+ max_paginates_per 100 # User can't request more than 100
107
+ max_pages 100 # Limit to 100 pages total
108
+ end
109
+ ```
110
+
111
+ ## View Helpers
112
+
113
+ ### Basic Pagination
114
+
115
+ ```erb
116
+ <!-- Simple pagination links -->
117
+ <%= paginate @posts %>
118
+
119
+ <!-- With options -->
120
+ <%= paginate @posts, window: 2 %>
121
+ <%= paginate @posts, outer_window: 1 %>
122
+ <%= paginate @posts, left: 1, right: 1 %>
123
+
124
+ <!-- Custom parameter name -->
125
+ <%= paginate @posts, param_name: :pagina %>
126
+
127
+ <!-- For AJAX/Turbo -->
128
+ <%= paginate @posts, remote: true %>
129
+ ```
130
+
131
+ ### Navigation Links
132
+
133
+ ```erb
134
+ <!-- Previous/Next links -->
135
+ <%= link_to_prev_page @posts, 'Previous', class: 'btn' %>
136
+ <%= link_to_next_page @posts, 'Next', class: 'btn' %>
137
+
138
+ <!-- With custom content -->
139
+ <%= link_to_prev_page @posts do %>
140
+ <span aria-hidden="true">&larr;</span> Older
141
+ <% end %>
142
+
143
+ <%= link_to_next_page @posts do %>
144
+ Newer <span aria-hidden="true">&rarr;</span>
145
+ <% end %>
146
+ ```
147
+
148
+ ### Page Info
149
+
150
+ ```erb
151
+ <!-- Shows: "Displaying posts 1 - 25 of 100 in total" -->
152
+ <%= page_entries_info @posts %>
153
+
154
+ <!-- Custom format -->
155
+ <%= page_entries_info @posts, entry_name: 'item' %>
156
+ ```
157
+
158
+ ### SEO Helpers
159
+
160
+ ```erb
161
+ <!-- Add rel="next" and rel="prev" link tags to <head> -->
162
+ <%= rel_next_prev_link_tags @posts %>
163
+ ```
164
+
165
+ ### URL Helpers
166
+
167
+ ```ruby
168
+ # Get URLs for navigation
169
+ path_to_next_page(@posts) #=> "/posts?page=3"
170
+ path_to_prev_page(@posts) #=> "/posts?page=1"
171
+ ```
172
+
173
+ ## Customization
174
+
175
+ ### Generating Custom Views
176
+
177
+ ```bash
178
+ # Generate default theme
179
+ rails g kaminari:views default
180
+
181
+ # Generate with namespace
182
+ rails g kaminari:views default --views-prefix admin
183
+
184
+ # Generate specific theme
185
+ rails g kaminari:views bootstrap4
186
+ ```
187
+
188
+ This creates templates in `app/views/kaminari/`:
189
+ - `_first_page.html.erb`
190
+ - `_prev_page.html.erb`
191
+ - `_page.html.erb`
192
+ - `_next_page.html.erb`
193
+ - `_last_page.html.erb`
194
+ - `_gap.html.erb`
195
+ - `_paginator.html.erb`
196
+
197
+ ### Using Themes
198
+
199
+ ```erb
200
+ <!-- Default theme -->
201
+ <%= paginate @posts %>
202
+
203
+ <!-- Custom theme -->
204
+ <%= paginate @posts, theme: 'my_theme' %>
205
+
206
+ <!-- Bootstrap theme -->
207
+ <%= paginate @posts, theme: 'twitter-bootstrap-4' %>
208
+ ```
209
+
210
+ ### Custom Pagination Template
211
+
212
+ ```erb
213
+ <!-- app/views/kaminari/_paginator.html.erb -->
214
+ <nav class="pagination" role="navigation" aria-label="Pagination">
215
+ <ul class="pagination-list">
216
+ <%= first_page_tag %>
217
+ <%= prev_page_tag %>
218
+
219
+ <% each_page do |page| %>
220
+ <% if page.left_outer? || page.right_outer? || page.inside_window? %>
221
+ <%= page_tag page %>
222
+ <% elsif !page.was_truncated? %>
223
+ <%= gap_tag %>
224
+ <% end %>
225
+ <% end %>
226
+
227
+ <%= next_page_tag %>
228
+ <%= last_page_tag %>
229
+ </ul>
230
+ </nav>
231
+ ```
232
+
233
+ ## API Pagination
234
+
235
+ ### JSON Response
236
+
237
+ ```ruby
238
+ # app/controllers/api/v1/posts_controller.rb
239
+ module Api
240
+ module V1
241
+ class PostsController < ApplicationController
242
+ def index
243
+ @posts = Post.page(params[:page]).per(params[:per_page] || 20)
244
+
245
+ render json: {
246
+ posts: @posts.map { |p| PostSerializer.new(p) },
247
+ meta: pagination_meta(@posts)
248
+ }
249
+ end
250
+
251
+ private
252
+
253
+ def pagination_meta(collection)
254
+ {
255
+ current_page: collection.current_page,
256
+ next_page: collection.next_page,
257
+ prev_page: collection.prev_page,
258
+ total_pages: collection.total_pages,
259
+ total_count: collection.total_count
260
+ }
261
+ end
262
+ end
263
+ end
264
+ end
265
+ ```
266
+
267
+ ### API Response Helper
268
+
269
+ ```ruby
270
+ # app/controllers/concerns/paginatable.rb
271
+ module Paginatable
272
+ extend ActiveSupport::Concern
273
+
274
+ def paginate(collection)
275
+ collection
276
+ .page(params[:page] || 1)
277
+ .per(params[:per_page] || default_per_page)
278
+ end
279
+
280
+ def pagination_links(collection)
281
+ {
282
+ self: request.original_url,
283
+ first: url_for(page: 1),
284
+ prev: collection.prev_page ? url_for(page: collection.prev_page) : nil,
285
+ next: collection.next_page ? url_for(page: collection.next_page) : nil,
286
+ last: url_for(page: collection.total_pages)
287
+ }
288
+ end
289
+
290
+ def pagination_meta(collection)
291
+ {
292
+ current_page: collection.current_page,
293
+ total_pages: collection.total_pages,
294
+ total_count: collection.total_count,
295
+ per_page: collection.limit_value
296
+ }
297
+ end
298
+
299
+ private
300
+
301
+ def default_per_page
302
+ 20
303
+ end
304
+ end
305
+ ```
306
+
307
+ ## Performance Optimization
308
+
309
+ ### Without Count Query
310
+
311
+ For very large datasets, skip expensive COUNT queries:
312
+
313
+ ```ruby
314
+ # Controller
315
+ def index
316
+ @posts = Post.order(:created_at).page(params[:page]).without_count
317
+ end
318
+
319
+ # View - use simple navigation only
320
+ <%= link_to_prev_page @posts, 'Previous' %>
321
+ <%= link_to_next_page @posts, 'Next' %>
322
+ ```
323
+
324
+ **Note**: `total_pages`, `total_count`, and numbered page links won't work with `without_count`.
325
+
326
+ ### Eager Loading
327
+
328
+ ```ruby
329
+ # Prevent N+1 queries
330
+ @posts = Post.includes(:user, :comments)
331
+ .order(:created_at)
332
+ .page(params[:page])
333
+ ```
334
+
335
+ ### Caching
336
+
337
+ ```ruby
338
+ # Fragment caching
339
+ <% cache ["posts-page", @posts.current_page] do %>
340
+ <%= render @posts %>
341
+ <%= paginate @posts %>
342
+ <% end %>
343
+ ```
344
+
345
+ ## Advanced Features
346
+
347
+ ### Paginating Arrays
348
+
349
+ ```ruby
350
+ # Controller
351
+ @items = expensive_operation_returning_array
352
+ @paginated_items = Kaminari.paginate_array(@items, total_count: @items.count)
353
+ .page(params[:page])
354
+ .per(10)
355
+
356
+ # Or with known total
357
+ @paginated_items = Kaminari.paginate_array(
358
+ @items,
359
+ total_count: 145,
360
+ limit: 10,
361
+ offset: (params[:page].to_i - 1) * 10
362
+ ).page(params[:page]).per(10)
363
+ ```
364
+
365
+ ### SEO-Friendly URLs
366
+
367
+ ```ruby
368
+ # config/routes.rb
369
+ resources :posts do
370
+ get 'page/:page', action: :index, on: :collection
371
+ end
372
+
373
+ # Creates URLs like: /posts/page/2 instead of /posts?page=2
374
+
375
+ # Or using concerns
376
+ concern :paginatable do
377
+ get '(page/:page)', action: :index, on: :collection, as: ''
378
+ end
379
+
380
+ resources :posts, concerns: :paginatable
381
+ resources :articles, concerns: :paginatable
382
+ ```
383
+
384
+ ### Infinite Scroll
385
+
386
+ ```ruby
387
+ # app/controllers/posts_controller.rb
388
+ def index
389
+ @posts = Post.order(:created_at).page(params[:page])
390
+
391
+ respond_to do |format|
392
+ format.html
393
+ format.js # For infinite scroll
394
+ end
395
+ end
396
+ ```
397
+
398
+ ```javascript
399
+ // app/views/posts/index.js.erb
400
+ $('#posts').append('<%= j render @posts %>');
401
+
402
+ <% if @posts.next_page %>
403
+ $('.pagination').replaceWith('<%= j paginate @posts %>');
404
+ <% else %>
405
+ $('.pagination').remove();
406
+ <% end %>
407
+ ```
408
+
409
+ ### Custom Scopes with Pagination
410
+
411
+ ```ruby
412
+ # app/models/post.rb
413
+ class Post < ApplicationRecord
414
+ scope :published, -> { where(published: true) }
415
+ scope :by_author, ->(author_id) { where(author_id: author_id) }
416
+ scope :recent_first, -> { order(created_at: :desc) }
417
+
418
+ # Chainable with pagination
419
+ # Post.published.recent_first.page(1)
420
+ end
421
+ ```
422
+
423
+ ## Internationalization
424
+
425
+ ```yaml
426
+ # config/locales/en.yml
427
+ en:
428
+ views:
429
+ pagination:
430
+ first: "&laquo; First"
431
+ last: "Last &raquo;"
432
+ previous: "&lsaquo; Prev"
433
+ next: "Next &rsaquo;"
434
+ truncate: "&hellip;"
435
+ helpers:
436
+ page_entries_info:
437
+ one_page:
438
+ display_entries:
439
+ zero: "No %{entry_name} found"
440
+ one: "Displaying <b>1</b> %{entry_name}"
441
+ other: "Displaying <b>all %{count}</b> %{entry_name}"
442
+ more_pages:
443
+ display_entries: "Displaying %{entry_name} <b>%{first}&nbsp;-&nbsp;%{last}</b> of <b>%{total}</b> in total"
444
+ ```
445
+
446
+ ## Common Patterns
447
+
448
+ ### Search with Pagination
449
+
450
+ ```ruby
451
+ # app/controllers/posts_controller.rb
452
+ def index
453
+ @posts = Post.all
454
+ @posts = @posts.where('title LIKE ?', "%#{params[:q]}%") if params[:q].present?
455
+ @posts = @posts.order(:created_at).page(params[:page])
456
+ end
457
+ ```
458
+
459
+ ```erb
460
+ <!-- app/views/posts/index.html.erb -->
461
+ <%= form_with url: posts_path, method: :get do |f| %>
462
+ <%= f.text_field :q, value: params[:q], placeholder: 'Search...' %>
463
+ <%= f.submit 'Search' %>
464
+ <% end %>
465
+
466
+ <%= render @posts %>
467
+ <%= paginate @posts, params: { q: params[:q] } %>
468
+ ```
469
+
470
+ ### Filtered Pagination
471
+
472
+ ```ruby
473
+ # app/controllers/posts_controller.rb
474
+ def index
475
+ @posts = Post.all
476
+ @posts = @posts.where(category_id: params[:category_id]) if params[:category_id]
477
+ @posts = @posts.where(status: params[:status]) if params[:status]
478
+ @posts = @posts.order(:created_at).page(params[:page])
479
+ end
480
+ ```
481
+
482
+ ```erb
483
+ <%= paginate @posts, params: { category_id: params[:category_id], status: params[:status] } %>
484
+ ```
485
+
486
+ ### Admin Pagination
487
+
488
+ ```ruby
489
+ # app/controllers/admin/users_controller.rb
490
+ module Admin
491
+ class UsersController < AdminController
492
+ def index
493
+ @users = User.order(:email).page(params[:page]).per(50)
494
+ end
495
+ end
496
+ end
497
+ ```
498
+
499
+ ## Testing
500
+
501
+ ### RSpec
502
+
503
+ ```ruby
504
+ # spec/models/post_spec.rb
505
+ RSpec.describe Post, type: :model do
506
+ describe '.page' do
507
+ let!(:posts) { create_list(:post, 30) }
508
+
509
+ it 'returns first page with default per_page' do
510
+ page = Post.page(1)
511
+ expect(page.count).to eq(25)
512
+ expect(page.current_page).to eq(1)
513
+ end
514
+
515
+ it 'returns correct page' do
516
+ page = Post.page(2).per(10)
517
+ expect(page.count).to eq(10)
518
+ expect(page.current_page).to eq(2)
519
+ expect(page.total_pages).to eq(3)
520
+ end
521
+ end
522
+ end
523
+
524
+ # spec/requests/posts_spec.rb
525
+ RSpec.describe 'Posts', type: :request do
526
+ describe 'GET /posts' do
527
+ let!(:posts) { create_list(:post, 30) }
528
+
529
+ it 'paginates posts' do
530
+ get posts_path, params: { page: 2 }
531
+ expect(response).to have_http_status(:ok)
532
+ expect(assigns(:posts).current_page).to eq(2)
533
+ end
534
+
535
+ it 'handles out of range pages' do
536
+ get posts_path, params: { page: 999 }
537
+ expect(response).to have_http_status(:ok)
538
+ expect(assigns(:posts)).to be_empty
539
+ expect(assigns(:posts).out_of_range?).to be true
540
+ end
541
+ end
542
+ end
543
+ ```
544
+
545
+ ### Controller Tests
546
+
547
+ ```ruby
548
+ # spec/controllers/posts_controller_spec.rb
549
+ RSpec.describe PostsController, type: :controller do
550
+ describe 'GET #index' do
551
+ let!(:posts) { create_list(:post, 30) }
552
+
553
+ it 'assigns paginated posts' do
554
+ get :index, params: { page: 1 }
555
+ expect(assigns(:posts).count).to eq(25)
556
+ expect(assigns(:posts).total_count).to eq(30)
557
+ end
558
+
559
+ it 'respects per_page parameter' do
560
+ get :index, params: { page: 1, per_page: 10 }
561
+ expect(assigns(:posts).count).to eq(10)
562
+ end
563
+ end
564
+ end
565
+ ```
566
+
567
+ ## Troubleshooting
568
+
569
+ ### Page Parameter Not Working
570
+
571
+ ```ruby
572
+ # If using custom routes, ensure page param is permitted
573
+ params.permit(:page, :per_page)
574
+ ```
575
+
576
+ ### Total Count Performance
577
+
578
+ ```ruby
579
+ # For large tables, use counter cache or without_count
580
+ class Post < ApplicationRecord
581
+ # Option 1: Counter cache
582
+ belongs_to :category, counter_cache: true
583
+
584
+ # Option 2: Skip count query
585
+ # Use .without_count in controller
586
+ end
587
+ ```
588
+
589
+ ### Styling Issues
590
+
591
+ ```bash
592
+ # Ensure you've generated views
593
+ rails g kaminari:views default
594
+
595
+ # Or use a theme
596
+ rails g kaminari:views bootstrap4
597
+ ```
598
+
599
+ ## Best Practices
600
+
601
+ 1. **Always order before paginating**: Ensures consistent results across pages
602
+ 2. **Use `per` wisely**: Set reasonable limits with `max_paginates_per`
603
+ 3. **Eager load associations**: Prevent N+1 queries with `includes`
604
+ 4. **Cache pagination**: Use fragment caching for expensive queries
605
+ 5. **Handle out of range**: Check `out_of_range?` and redirect if needed
606
+ 6. **API pagination**: Always include metadata in JSON responses
607
+ 7. **SEO**: Use `rel_next_prev_link_tags` for better search indexing
608
+ 8. **Test edge cases**: Empty results, last page, out of range pages
609
+ 9. **Use `without_count` for large datasets**: Skip COUNT queries when possible
610
+ 10. **Preserve filters**: Pass filter params to `paginate` helper
611
+
612
+ ## Additional Resources
613
+
614
+ For more advanced patterns, see:
615
+ - **API Pagination**: [references/api-pagination.md](references/api-pagination.md)
616
+ - **Custom Themes**: [references/custom-themes.md](references/custom-themes.md)
617
+ - **Performance Optimization**: [references/performance.md](references/performance.md)
618
+
619
+ ## Resources
620
+
621
+ - [Kaminari GitHub](https://github.com/kaminari/kaminari)
622
+ - [Kaminari Wiki](https://github.com/kaminari/kaminari/wiki)