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,508 @@
1
+ ---
2
+ name: rails-views
3
+ description: ERB templates, helpers, layouts, partials, and view patterns
4
+ version: 1.0.0
5
+ rails_version: ">= 7.0"
6
+ tags:
7
+ - views
8
+ - erb
9
+ - templates
10
+ - helpers
11
+ ---
12
+
13
+ # Rails Views
14
+
15
+ ## Quick Reference
16
+
17
+ | Pattern | Example |
18
+ |---------|---------|
19
+ | **Output** | `<%= @post.title %>` |
20
+ | **Code** | `<% @posts.each do |post| %>` |
21
+ | **Link** | `<%= link_to 'Home', root_path %>` |
22
+ | **Form** | `<%= form_with model: @post do |f| %>` |
23
+ | **Partial** | `<%= render 'shared/header' %>` |
24
+ | **Helper** | `<%= truncate @post.body, length: 100 %>` |
25
+ | **Asset** | `<%= image_tag 'logo.png' %>` |
26
+
27
+ ## ERB Basics
28
+
29
+ ```erb
30
+ <%# Comment - won't be rendered %>
31
+
32
+ <% # Ruby code - executed but not displayed %>
33
+ <% if user_signed_in? %>
34
+ <p>Welcome back!</p>
35
+ <% end %>
36
+
37
+ <%= # Ruby code with output %>
38
+ <%= @post.title %>
39
+ <%= current_user.name %>
40
+
41
+ <%== # Output without HTML escaping (dangerous!) %>
42
+ <%== raw_html_content %>
43
+
44
+ <%- # Suppress whitespace before tag %>
45
+ <%- if condition -%>
46
+
47
+ <%= # Safe output (escapes HTML by default) %>
48
+ <%= user_input %> <%# Safe from XSS %>
49
+ ```
50
+
51
+ ## Layouts
52
+
53
+ ```erb
54
+ <%# app/views/layouts/application.html.erb %>
55
+ <!DOCTYPE html>
56
+ <html>
57
+ <head>
58
+ <title><%= content_for?(:title) ? yield(:title) : "My App" %></title>
59
+ <%= csrf_meta_tags %>
60
+ <%= csp_meta_tag %>
61
+
62
+ <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
63
+ <%= javascript_importmap_tags %>
64
+
65
+ <%= yield :head %>
66
+ </head>
67
+
68
+ <body class="<%= controller_name %> <%= action_name %>">
69
+ <%= render 'shared/header' %>
70
+
71
+ <% flash.each do |type, message| %>
72
+ <div class="alert alert-<%= type %>">
73
+ <%= message %>
74
+ </div>
75
+ <% end %>
76
+
77
+ <main>
78
+ <%= yield %>
79
+ </main>
80
+
81
+ <%= render 'shared/footer' %>
82
+ </body>
83
+ </html>
84
+ ```
85
+
86
+ ### Content For
87
+
88
+ ```erb
89
+ <%# In view template %>
90
+ <% content_for :title do %>
91
+ <%= @post.title %> - My Blog
92
+ <% end %>
93
+
94
+ <% content_for :head do %>
95
+ <%= stylesheet_link_tag "posts" %>
96
+ <meta name="description" content="<%= @post.excerpt %>">
97
+ <% end %>
98
+
99
+ <%# Content here will be yielded in layout %>
100
+ <article>
101
+ <%= @post.body %>
102
+ </article>
103
+ ```
104
+
105
+ ## Partials
106
+
107
+ ### Basic Partials
108
+
109
+ ```erb
110
+ <%# Render partial %>
111
+ <%= render 'shared/header' %>
112
+ <%= render 'post' %>
113
+ <%= render partial: 'post' %>
114
+
115
+ <%# With local variables %>
116
+ <%= render 'post', post: @post %>
117
+ <%= render partial: 'post', locals: { post: @post, show_author: true } %>
118
+
119
+ <%# Partial file: app/views/shared/_header.html.erb %>
120
+ <header>
121
+ <h1>My Blog</h1>
122
+ </header>
123
+
124
+ <%# Partial file: app/views/posts/_post.html.erb %>
125
+ <article>
126
+ <h2><%= post.title %></h2>
127
+ <p><%= post.body %></p>
128
+ <% if local_assigns[:show_author] %>
129
+ <p>By <%= post.author.name %></p>
130
+ <% end %>
131
+ </article>
132
+ ```
133
+
134
+ ### Collection Partials
135
+
136
+ ```erb
137
+ <%# Render for each item %>
138
+ <%= render partial: 'post', collection: @posts %>
139
+
140
+ <%# Shorthand %>
141
+ <%= render @posts %>
142
+
143
+ <%# With local variable name %>
144
+ <%= render partial: 'post', collection: @posts, as: :item %>
145
+
146
+ <%# With spacer template %>
147
+ <%= render partial: 'post', collection: @posts, spacer_template: 'post_divider' %>
148
+
149
+ <%# In partial, access current index %>
150
+ <article data-index="<%= post_counter %>">
151
+ <%= post.title %>
152
+ </article>
153
+ ```
154
+
155
+ ## Forms
156
+
157
+ ### Form Helpers
158
+
159
+ ```erb
160
+ <%= form_with model: @post do |f| %>
161
+ <% if @post.errors.any? %>
162
+ <div class="errors">
163
+ <h3><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h3>
164
+ <ul>
165
+ <% @post.errors.full_messages.each do |message| %>
166
+ <li><%= message %></li>
167
+ <% end %>
168
+ </ul>
169
+ </div>
170
+ <% end %>
171
+
172
+ <div class="field">
173
+ <%= f.label :title %>
174
+ <%= f.text_field :title, class: 'form-control' %>
175
+ </div>
176
+
177
+ <div class="field">
178
+ <%= f.label :body %>
179
+ <%= f.text_area :body, rows: 10, class: 'form-control' %>
180
+ </div>
181
+
182
+ <div class="field">
183
+ <%= f.label :published %>
184
+ <%= f.check_box :published %>
185
+ </div>
186
+
187
+ <div class="field">
188
+ <%= f.label :category_id %>
189
+ <%= f.collection_select :category_id, Category.all, :id, :name,
190
+ { prompt: 'Select a category' }, { class: 'form-control' } %>
191
+ </div>
192
+
193
+ <div class="field">
194
+ <%= f.label :tag_ids %>
195
+ <%= f.collection_check_boxes :tag_ids, Tag.all, :id, :name %>
196
+ </div>
197
+
198
+ <div class="actions">
199
+ <%= f.submit "Save Post", class: 'btn btn-primary' %>
200
+ </div>
201
+ <% end %>
202
+ ```
203
+
204
+ ### Form Field Types
205
+
206
+ ```erb
207
+ <%= f.text_field :name %>
208
+ <%= f.text_area :description %>
209
+ <%= f.password_field :password %>
210
+ <%= f.email_field :email %>
211
+ <%= f.url_field :website %>
212
+ <%= f.number_field :age %>
213
+ <%= f.date_field :birthday %>
214
+ <%= f.datetime_field :published_at %>
215
+ <%= f.time_field :starts_at %>
216
+ <%= f.hidden_field :user_id %>
217
+
218
+ <%= f.check_box :published %>
219
+ <%= f.radio_button :status, 'active' %>
220
+
221
+ <%= f.select :category_id, Category.pluck(:name, :id) %>
222
+ <%= f.collection_select :author_id, User.all, :id, :name %>
223
+ <%= f.collection_radio_buttons :status, Status.all, :id, :name %>
224
+ <%= f.collection_check_boxes :tag_ids, Tag.all, :id, :name %>
225
+
226
+ <%= f.file_field :avatar %>
227
+ ```
228
+
229
+ ### Non-Model Forms
230
+
231
+ ```erb
232
+ <%= form_with url: search_path, method: :get do |f| %>
233
+ <%= f.text_field :query, placeholder: 'Search...' %>
234
+ <%= f.submit 'Search' %>
235
+ <% end %>
236
+ ```
237
+
238
+ ## Links and URLs
239
+
240
+ ```erb
241
+ <%# Basic link %>
242
+ <%= link_to 'Home', root_path %>
243
+ <%= link_to 'Edit', edit_post_path(@post) %>
244
+ <%= link_to 'Delete', post_path(@post), data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } %>
245
+
246
+ <%# Link to object (uses polymorphic routing) %>
247
+ <%= link_to @post.title, @post %>
248
+ <%= link_to 'Edit', [:edit, @post] %>
249
+
250
+ <%# Link with block %>
251
+ <%= link_to post_path(@post) do %>
252
+ <strong><%= @post.title %></strong>
253
+ <p><%= @post.excerpt %></p>
254
+ <% end %>
255
+
256
+ <%# Link classes and data attributes %>
257
+ <%= link_to 'Click', path, class: 'btn btn-primary', data: { action: 'click->controller#method' } %>
258
+
259
+ <%# Button to (generates a form) %>
260
+ <%= button_to 'Delete', post_path(@post), method: :delete, class: 'btn btn-danger' %>
261
+
262
+ <%# Mail to %>
263
+ <%= mail_to 'user@example.com' %>
264
+ <%= mail_to 'user@example.com', 'Contact Us', subject: 'Hello' %>
265
+ ```
266
+
267
+ ## Asset Helpers
268
+
269
+ ```erb
270
+ <%# Images %>
271
+ <%= image_tag 'logo.png' %>
272
+ <%= image_tag 'logo.png', alt: 'Logo', class: 'logo', size: '100x100' %>
273
+ <%= image_tag @post.cover_image_url %>
274
+
275
+ <%# Stylesheets %>
276
+ <%= stylesheet_link_tag 'application' %>
277
+ <%= stylesheet_link_tag 'posts', media: 'all' %>
278
+
279
+ <%# JavaScript %>
280
+ <%= javascript_include_tag 'application' %>
281
+ <%= javascript_importmap_tags %>
282
+
283
+ <%# Asset path %>
284
+ <%= asset_path 'image.png' %>
285
+ <%= asset_url 'image.png' %>
286
+ ```
287
+
288
+ ## View Helpers
289
+
290
+ ### Text Helpers
291
+
292
+ ```erb
293
+ <%= truncate @post.body, length: 100 %>
294
+ <%= truncate @post.body, length: 100, separator: ' ' %>
295
+
296
+ <%= simple_format @post.body %>
297
+
298
+ <%= pluralize @posts.count, 'post' %>
299
+
300
+ <%= number_to_currency 29.99 %>
301
+ <%= number_to_percentage 85.5 %>
302
+ <%= number_with_delimiter 1000000 %>
303
+ <%= number_to_human 1234567 %>
304
+
305
+ <%= time_ago_in_words @post.created_at %>
306
+ <%= distance_of_time_in_words Time.now, @post.created_at %>
307
+ ```
308
+
309
+ ### Content Helpers
310
+
311
+ ```erb
312
+ <%= content_tag :div, "Hello", class: 'greeting' %>
313
+ <%# Output: <div class="greeting">Hello</div> %>
314
+
315
+ <%= content_tag :div, class: 'post' do %>
316
+ <%= @post.title %>
317
+ <% end %>
318
+
319
+ <%= tag.div "Hello", class: 'greeting' %>
320
+ <%= tag.div class: 'post' do %>
321
+ <%= @post.title %>
322
+ <% end %>
323
+ ```
324
+
325
+ ### Sanitization
326
+
327
+ ```erb
328
+ <%# Strip all HTML tags %>
329
+ <%= strip_tags @post.html_content %>
330
+
331
+ <%# Allow specific tags %>
332
+ <%= sanitize @post.html_content, tags: %w[p br strong em] %>
333
+
334
+ <%# Escape HTML %>
335
+ <%= html_escape user_input %>
336
+ <%= h user_input %> <%# shorthand %>
337
+ ```
338
+
339
+ ## Custom Helpers
340
+
341
+ ```ruby
342
+ # app/helpers/application_helper.rb
343
+ module ApplicationHelper
344
+ def page_title(title)
345
+ content_for(:title) { title }
346
+ content_tag(:h1, title, class: 'page-title')
347
+ end
348
+
349
+ def active_link(text, path, **options)
350
+ active = current_page?(path)
351
+ classes = options[:class].to_s
352
+ classes += ' active' if active
353
+ link_to text, path, class: classes
354
+ end
355
+
356
+ def formatted_date(date)
357
+ return 'N/A' unless date
358
+ date.strftime('%B %d, %Y')
359
+ end
360
+
361
+ def user_avatar(user, size: 50)
362
+ if user.avatar.attached?
363
+ image_tag user.avatar.variant(resize_to_limit: [size, size])
364
+ else
365
+ image_tag "default-avatar.png", size: "#{size}x#{size}"
366
+ end
367
+ end
368
+ end
369
+ ```
370
+
371
+ ```erb
372
+ <%# Using custom helpers %>
373
+ <%= page_title "My Posts" %>
374
+ <%= active_link "Home", root_path, class: 'nav-link' %>
375
+ <%= formatted_date @post.created_at %>
376
+ <%= user_avatar current_user, size: 100 %>
377
+ ```
378
+
379
+ ## Turbo Frames
380
+
381
+ ```erb
382
+ <%# Turbo Frame %>
383
+ <%= turbo_frame_tag "post_#{@post.id}" do %>
384
+ <%= render @post %>
385
+ <% end %>
386
+
387
+ <%# Turbo Frame with lazy loading %>
388
+ <%= turbo_frame_tag "post_#{@post.id}", src: post_path(@post), loading: :lazy do %>
389
+ Loading...
390
+ <% end %>
391
+
392
+ <%# Target a specific frame %>
393
+ <%= link_to "Edit", edit_post_path(@post), data: { turbo_frame: "post_#{@post.id}" } %>
394
+ ```
395
+
396
+ ## Turbo Streams
397
+
398
+ ```erb
399
+ <%# app/views/posts/create.turbo_stream.erb %>
400
+ <%= turbo_stream.prepend "posts" do %>
401
+ <%= render @post %>
402
+ <% end %>
403
+
404
+ <%= turbo_stream.update "flash" do %>
405
+ <div class="notice">Post created!</div>
406
+ <% end %>
407
+
408
+ <%# Available actions: append, prepend, replace, update, remove, before, after %>
409
+ ```
410
+
411
+ ## Conditional Rendering
412
+
413
+ ```erb
414
+ <% if user_signed_in? %>
415
+ <p>Welcome, <%= current_user.name %>!</p>
416
+ <%= link_to "Logout", logout_path, data: { turbo_method: :delete } %>
417
+ <% else %>
418
+ <%= link_to "Login", login_path %>
419
+ <% end %>
420
+
421
+ <% unless @posts.empty? %>
422
+ <%= render @posts %>
423
+ <% else %>
424
+ <p>No posts yet.</p>
425
+ <% end %>
426
+
427
+ <%# Ternary operator %>
428
+ <%= @post.published? ? "Published" : "Draft" %>
429
+ ```
430
+
431
+ ## Loops and Iteration
432
+
433
+ ```erb
434
+ <% @posts.each do |post| %>
435
+ <%= render post %>
436
+ <% end %>
437
+
438
+ <% @posts.each_with_index do |post, index| %>
439
+ <div class="post-<%= index + 1 %>">
440
+ <%= render post %>
441
+ </div>
442
+ <% end %>
443
+
444
+ <%# Check if collection is empty %>
445
+ <% if @posts.any? %>
446
+ <% @posts.each do |post| %>
447
+ <%= render post %>
448
+ <% end %>
449
+ <% else %>
450
+ <p>No posts found.</p>
451
+ <% end %>
452
+ ```
453
+
454
+ ## Best Practices
455
+
456
+ 1. **Keep views simple** - Complex logic belongs in helpers or models
457
+ 2. **Use partials** for reusable components
458
+ 3. **Use helpers** for view-specific logic
459
+ 4. **Escape user input** (Rails does this by default with `<%= %>`)
460
+ 5. **Use semantic HTML** for accessibility
461
+ 6. **Leverage Turbo** for reactive UIs without JavaScript
462
+ 7. **Use content_for** for flexible layouts
463
+ 8. **Keep CSS/JS out** of ERB files (use asset pipeline)
464
+ 9. **Use I18n** for text content to support internationalization
465
+ 10. **Test helpers** with unit tests
466
+
467
+ ## Common Patterns
468
+
469
+ ### Conditional Class Names
470
+
471
+ ```erb
472
+ <div class="<%= 'active' if @post.published? %> post">
473
+ ...
474
+ </div>
475
+
476
+ <%# Better with helper %>
477
+ <div class="<%= post_classes(@post) %>">
478
+ ...
479
+ </div>
480
+
481
+ # In helper
482
+ def post_classes(post)
483
+ classes = ['post']
484
+ classes << 'published' if post.published?
485
+ classes << 'featured' if post.featured?
486
+ classes.join(' ')
487
+ end
488
+ ```
489
+
490
+ ### Empty State
491
+
492
+ ```erb
493
+ <% if @posts.any? %>
494
+ <%= render @posts %>
495
+ <% else %>
496
+ <div class="empty-state">
497
+ <p>No posts yet.</p>
498
+ <%= link_to "Create your first post", new_post_path, class: 'btn' %>
499
+ </div>
500
+ <% end %>
501
+ ```
502
+
503
+ ## References
504
+
505
+ - [Rails Guides - Layouts and Rendering](https://guides.rubyonrails.org/layouts_and_rendering.html)
506
+ - [Rails Guides - Form Helpers](https://guides.rubyonrails.org/form_helpers.html)
507
+ - [Rails Guides - Action View](https://guides.rubyonrails.org/action_view_overview.html)
508
+ - [Hotwire Turbo Handbook](https://turbo.hotwired.dev/handbook/introduction)