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.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.yml +134 -0
- data/.github/ISSUE_TEMPLATE/config.yml +11 -0
- data/.github/ISSUE_TEMPLATE/feature_request.yml +129 -0
- data/.github/ISSUE_TEMPLATE/question.yml +90 -0
- data/.github/dependabot.yml +19 -0
- data/.github/workflows/ci.yml +77 -0
- data/.github/workflows/release.yml +66 -0
- data/.rubocop.yml +52 -0
- data/CHANGELOG.md +94 -0
- data/CLAUDE.md +332 -0
- data/CODE_OF_CONDUCT.md +134 -0
- data/CONTRIBUTING.md +580 -0
- data/LICENSE.txt +21 -0
- data/README.md +544 -0
- data/Rakefile +8 -0
- data/lib/generators/claude/agent/agent_generator.rb +71 -0
- data/lib/generators/claude/agent/templates/agent.md.tt +62 -0
- data/lib/generators/claude/command/command_generator.rb +50 -0
- data/lib/generators/claude/command/templates/command.md.tt +28 -0
- data/lib/generators/claude/commands_library/create-pr.md +27 -0
- data/lib/generators/claude/commands_library/dbchange.md +19 -0
- data/lib/generators/claude/commands_library/quality.md +20 -0
- data/lib/generators/claude/commands_library/stimulus.md +19 -0
- data/lib/generators/claude/commands_library/turbo-feature.md +17 -0
- data/lib/generators/claude/install/install_generator.rb +211 -0
- data/lib/generators/claude/install/templates/README.md.tt +59 -0
- data/lib/generators/claude/install/templates/USAGE +28 -0
- data/lib/generators/claude/install/templates/agents/api-dev.md.tt +46 -0
- data/lib/generators/claude/install/templates/agents/fullstack-dev.md.tt +48 -0
- data/lib/generators/claude/install/templates/agents/rails-developer.md.tt +40 -0
- data/lib/generators/claude/install/templates/settings.local.json.tt +13 -0
- data/lib/generators/claude/rule/rule_generator.rb +175 -0
- data/lib/generators/claude/rule/templates/rule.md.tt +7 -0
- data/lib/generators/claude/rules_library/code-style.md +37 -0
- data/lib/generators/claude/rules_library/database.md +47 -0
- data/lib/generators/claude/rules_library/hotwire.md +56 -0
- data/lib/generators/claude/rules_library/security.md +54 -0
- data/lib/generators/claude/rules_library/testing.md +47 -0
- data/lib/generators/claude/skill/skill_generator.rb +196 -0
- data/lib/generators/claude/skill/templates/SKILL.md.tt +27 -0
- data/lib/generators/claude/skills_library/create-task-files/SKILL.md +311 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/bug.md +60 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/epic.md +47 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/issue.md +45 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/user-story.md +57 -0
- data/lib/generators/claude/skills_library/minitest-testing/SKILL.md +398 -0
- data/lib/generators/claude/skills_library/minitest-testing/references/examples.md +889 -0
- data/lib/generators/claude/skills_library/plan-feature/SKILL.md +253 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/SKILL.md +1041 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/references/api-documentation.md +422 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/references/serialization.md +456 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/SKILL.md +191 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/advanced.md +331 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/api-auth.md +266 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/omniauth.md +194 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/SKILL.md +603 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/api-authorization.md +543 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/complex-permissions.md +572 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/multi-tenancy.md +373 -0
- data/lib/generators/claude/skills_library/rails-controllers/SKILL.md +514 -0
- data/lib/generators/claude/skills_library/rails-debugging/SKILL.md +260 -0
- data/lib/generators/claude/skills_library/rails-deployment/SKILL.md +437 -0
- data/lib/generators/claude/skills_library/rails-deployment/references/examples.md +901 -0
- data/lib/generators/claude/skills_library/rails-hotwire/SKILL.md +367 -0
- data/lib/generators/claude/skills_library/rails-jobs/MISSION_CONTROL_SETUP.md +639 -0
- data/lib/generators/claude/skills_library/rails-jobs/SKILL.md +704 -0
- data/lib/generators/claude/skills_library/rails-mailers/SKILL.md +549 -0
- data/lib/generators/claude/skills_library/rails-models/SKILL.md +379 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/SKILL.md +622 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/api-pagination.md +523 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/custom-themes.md +498 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/performance.md +478 -0
- data/lib/generators/claude/skills_library/rails-views/SKILL.md +508 -0
- data/lib/generators/claude/skills_library/refine-requirements/SKILL.md +226 -0
- data/lib/generators/claude/skills_library/refine-requirements/references/examples.md +344 -0
- data/lib/generators/claude/skills_library/refine-requirements/references/reference.md +298 -0
- data/lib/generators/claude/skills_library/rspec-testing/SKILL.md +572 -0
- data/lib/generators/claude/skills_library/rspec-testing/references/better_specs_guide.md +273 -0
- data/lib/generators/claude/skills_library/rspec-testing/references/thoughtbot_patterns.md +407 -0
- data/lib/generators/claude/skills_library/tailwindcss/SKILL.md +371 -0
- data/lib/generators/claude/views/views_generator.rb +113 -0
- data/lib/rails_claude_skills/railtie.rb +16 -0
- data/lib/rails_claude_skills/version.rb +5 -0
- data/lib/rails_claude_skills.rb +27 -0
- data/sig/rails_claude_skills.rbs +4 -0
- 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)
|