rails-views
npx machina-cli add skill Shoebtamboli/rails_claude_skills/rails-views --openclawFiles (1)
SKILL.md
11.3 KB
Rails Views
Quick Reference
| Pattern | Example |
|---|---|
| Output | <%= @post.title %> |
| Code | `<% @posts.each do |
| Link | <%= link_to 'Home', root_path %> |
| Form | `<%= form_with model: @post do |
| Partial | <%= render 'shared/header' %> |
| Helper | <%= truncate @post.body, length: 100 %> |
| Asset | <%= image_tag 'logo.png' %> |
ERB Basics
<%# Comment - won't be rendered %>
<% # Ruby code - executed but not displayed %>
<% if user_signed_in? %>
<p>Welcome back!</p>
<% end %>
<%= # Ruby code with output %>
<%= @post.title %>
<%= current_user.name %>
<%== # Output without HTML escaping (dangerous!) %>
<%== raw_html_content %>
<%- # Suppress whitespace before tag %>
<%- if condition -%>
<%= # Safe output (escapes HTML by default) %>
<%= user_input %> <%# Safe from XSS %>
Layouts
<%# app/views/layouts/application.html.erb %>
<!DOCTYPE html>
<html>
<head>
<title><%= content_for?(:title) ? yield(:title) : "My App" %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
<%= yield :head %>
</head>
<body class="<%= controller_name %> <%= action_name %>">
<%= render 'shared/header' %>
<% flash.each do |type, message| %>
<div class="alert alert-<%= type %>">
<%= message %>
</div>
<% end %>
<main>
<%= yield %>
</main>
<%= render 'shared/footer' %>
</body>
</html>
Content For
<%# In view template %>
<% content_for :title do %>
<%= @post.title %> - My Blog
<% end %>
<% content_for :head do %>
<%= stylesheet_link_tag "posts" %>
<meta name="description" content="<%= @post.excerpt %>">
<% end %>
<%# Content here will be yielded in layout %>
<article>
<%= @post.body %>
</article>
Partials
Basic Partials
<%# Render partial %>
<%= render 'shared/header' %>
<%= render 'post' %>
<%= render partial: 'post' %>
<%# With local variables %>
<%= render 'post', post: @post %>
<%= render partial: 'post', locals: { post: @post, show_author: true } %>
<%# Partial file: app/views/shared/_header.html.erb %>
<header>
<h1>My Blog</h1>
</header>
<%# Partial file: app/views/posts/_post.html.erb %>
<article>
<h2><%= post.title %></h2>
<p><%= post.body %></p>
<% if local_assigns[:show_author] %>
<p>By <%= post.author.name %></p>
<% end %>
</article>
Collection Partials
<%# Render for each item %>
<%= render partial: 'post', collection: @posts %>
<%# Shorthand %>
<%= render @posts %>
<%# With local variable name %>
<%= render partial: 'post', collection: @posts, as: :item %>
<%# With spacer template %>
<%= render partial: 'post', collection: @posts, spacer_template: 'post_divider' %>
<%# In partial, access current index %>
<article data-index="<%= post_counter %>">
<%= post.title %>
</article>
Forms
Form Helpers
<%= form_with model: @post do |f| %>
<% if @post.errors.any? %>
<div class="errors">
<h3><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h3>
<ul>
<% @post.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %>
<%= f.text_field :title, class: 'form-control' %>
</div>
<div class="field">
<%= f.label :body %>
<%= f.text_area :body, rows: 10, class: 'form-control' %>
</div>
<div class="field">
<%= f.label :published %>
<%= f.check_box :published %>
</div>
<div class="field">
<%= f.label :category_id %>
<%= f.collection_select :category_id, Category.all, :id, :name,
{ prompt: 'Select a category' }, { class: 'form-control' } %>
</div>
<div class="field">
<%= f.label :tag_ids %>
<%= f.collection_check_boxes :tag_ids, Tag.all, :id, :name %>
</div>
<div class="actions">
<%= f.submit "Save Post", class: 'btn btn-primary' %>
</div>
<% end %>
Form Field Types
<%= f.text_field :name %>
<%= f.text_area :description %>
<%= f.password_field :password %>
<%= f.email_field :email %>
<%= f.url_field :website %>
<%= f.number_field :age %>
<%= f.date_field :birthday %>
<%= f.datetime_field :published_at %>
<%= f.time_field :starts_at %>
<%= f.hidden_field :user_id %>
<%= f.check_box :published %>
<%= f.radio_button :status, 'active' %>
<%= f.select :category_id, Category.pluck(:name, :id) %>
<%= f.collection_select :author_id, User.all, :id, :name %>
<%= f.collection_radio_buttons :status, Status.all, :id, :name %>
<%= f.collection_check_boxes :tag_ids, Tag.all, :id, :name %>
<%= f.file_field :avatar %>
Non-Model Forms
<%= form_with url: search_path, method: :get do |f| %>
<%= f.text_field :query, placeholder: 'Search...' %>
<%= f.submit 'Search' %>
<% end %>
Links and URLs
<%# Basic link %>
<%= link_to 'Home', root_path %>
<%= link_to 'Edit', edit_post_path(@post) %>
<%= link_to 'Delete', post_path(@post), data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } %>
<%# Link to object (uses polymorphic routing) %>
<%= link_to @post.title, @post %>
<%= link_to 'Edit', [:edit, @post] %>
<%# Link with block %>
<%= link_to post_path(@post) do %>
<strong><%= @post.title %></strong>
<p><%= @post.excerpt %></p>
<% end %>
<%# Link classes and data attributes %>
<%= link_to 'Click', path, class: 'btn btn-primary', data: { action: 'click->controller#method' } %>
<%# Button to (generates a form) %>
<%= button_to 'Delete', post_path(@post), method: :delete, class: 'btn btn-danger' %>
<%# Mail to %>
<%= mail_to 'user@example.com' %>
<%= mail_to 'user@example.com', 'Contact Us', subject: 'Hello' %>
Asset Helpers
<%# Images %>
<%= image_tag 'logo.png' %>
<%= image_tag 'logo.png', alt: 'Logo', class: 'logo', size: '100x100' %>
<%= image_tag @post.cover_image_url %>
<%# Stylesheets %>
<%= stylesheet_link_tag 'application' %>
<%= stylesheet_link_tag 'posts', media: 'all' %>
<%# JavaScript %>
<%= javascript_include_tag 'application' %>
<%= javascript_importmap_tags %>
<%# Asset path %>
<%= asset_path 'image.png' %>
<%= asset_url 'image.png' %>
View Helpers
Text Helpers
<%= truncate @post.body, length: 100 %>
<%= truncate @post.body, length: 100, separator: ' ' %>
<%= simple_format @post.body %>
<%= pluralize @posts.count, 'post' %>
<%= number_to_currency 29.99 %>
<%= number_to_percentage 85.5 %>
<%= number_with_delimiter 1000000 %>
<%= number_to_human 1234567 %>
<%= time_ago_in_words @post.created_at %>
<%= distance_of_time_in_words Time.now, @post.created_at %>
Content Helpers
<%= content_tag :div, "Hello", class: 'greeting' %>
<%# Output: <div class="greeting">Hello</div> %>
<%= content_tag :div, class: 'post' do %>
<%= @post.title %>
<% end %>
<%= tag.div "Hello", class: 'greeting' %>
<%= tag.div class: 'post' do %>
<%= @post.title %>
<% end %>
Sanitization
<%# Strip all HTML tags %>
<%= strip_tags @post.html_content %>
<%# Allow specific tags %>
<%= sanitize @post.html_content, tags: %w[p br strong em] %>
<%# Escape HTML %>
<%= html_escape user_input %>
<%= h user_input %> <%# shorthand %>
Custom Helpers
# app/helpers/application_helper.rb
module ApplicationHelper
def page_title(title)
content_for(:title) { title }
content_tag(:h1, title, class: 'page-title')
end
def active_link(text, path, **options)
active = current_page?(path)
classes = options[:class].to_s
classes += ' active' if active
link_to text, path, class: classes
end
def formatted_date(date)
return 'N/A' unless date
date.strftime('%B %d, %Y')
end
def user_avatar(user, size: 50)
if user.avatar.attached?
image_tag user.avatar.variant(resize_to_limit: [size, size])
else
image_tag "default-avatar.png", size: "#{size}x#{size}"
end
end
end
<%# Using custom helpers %>
<%= page_title "My Posts" %>
<%= active_link "Home", root_path, class: 'nav-link' %>
<%= formatted_date @post.created_at %>
<%= user_avatar current_user, size: 100 %>
Turbo Frames
<%# Turbo Frame %>
<%= turbo_frame_tag "post_#{@post.id}" do %>
<%= render @post %>
<% end %>
<%# Turbo Frame with lazy loading %>
<%= turbo_frame_tag "post_#{@post.id}", src: post_path(@post), loading: :lazy do %>
Loading...
<% end %>
<%# Target a specific frame %>
<%= link_to "Edit", edit_post_path(@post), data: { turbo_frame: "post_#{@post.id}" } %>
Turbo Streams
<%# app/views/posts/create.turbo_stream.erb %>
<%= turbo_stream.prepend "posts" do %>
<%= render @post %>
<% end %>
<%= turbo_stream.update "flash" do %>
<div class="notice">Post created!</div>
<% end %>
<%# Available actions: append, prepend, replace, update, remove, before, after %>
Conditional Rendering
<% if user_signed_in? %>
<p>Welcome, <%= current_user.name %>!</p>
<%= link_to "Logout", logout_path, data: { turbo_method: :delete } %>
<% else %>
<%= link_to "Login", login_path %>
<% end %>
<% unless @posts.empty? %>
<%= render @posts %>
<% else %>
<p>No posts yet.</p>
<% end %>
<%# Ternary operator %>
<%= @post.published? ? "Published" : "Draft" %>
Loops and Iteration
<% @posts.each do |post| %>
<%= render post %>
<% end %>
<% @posts.each_with_index do |post, index| %>
<div class="post-<%= index + 1 %>">
<%= render post %>
</div>
<% end %>
<%# Check if collection is empty %>
<% if @posts.any? %>
<% @posts.each do |post| %>
<%= render post %>
<% end %>
<% else %>
<p>No posts found.</p>
<% end %>
Best Practices
- Keep views simple - Complex logic belongs in helpers or models
- Use partials for reusable components
- Use helpers for view-specific logic
- Escape user input (Rails does this by default with
<%= %>) - Use semantic HTML for accessibility
- Leverage Turbo for reactive UIs without JavaScript
- Use content_for for flexible layouts
- Keep CSS/JS out of ERB files (use asset pipeline)
- Use I18n for text content to support internationalization
- Test helpers with unit tests
Common Patterns
Conditional Class Names
<div class="<%= 'active' if @post.published? %> post">
...
</div>
<%# Better with helper %>
<div class="<%= post_classes(@post) %>">
...
</div>
# In helper
def post_classes(post)
classes = ['post']
classes << 'published' if post.published?
classes << 'featured' if post.featured?
classes.join(' ')
end
Empty State
<% if @posts.any? %>
<%= render @posts %>
<% else %>
<div class="empty-state">
<p>No posts yet.</p>
<%= link_to "Create your first post", new_post_path, class: 'btn' %>
</div>
<% end %>
References
Source
git clone https://github.com/Shoebtamboli/rails_claude_skills/blob/main/lib/generators/claude/skills_library/rails-views/SKILL.mdView on GitHub Overview
Rails Views covers ERB templates, helpers, layouts, and partials, with common patterns for building dynamic HTML in Rails 7. It teaches how to structure view code using layouts, content_for, and partials, and how to leverage helpers for reusable UI.
How This Skill Works
This skill demonstrates ERB syntax, layout composition with yield and content_for, and rendering strategies like partials and collection partials with locals. It also covers form helpers like form_with and the Rails helpers that generate semantic HTML, while output is escaped by default for safety.
When to Use It
- When building a consistent app chrome with a shared layout (app/views/layouts/application.html.erb) and dynamic titles via content_for.
- When listing resources using a collection partial to render a list of posts with a single reusable template.
- When creating forms for a model with validation errors using form_with and displaying errors in a dedicated block.
- When reusing common UI pieces like headers, footers, or post cards via partials and passing locals for per-page customization.
- When outputting dynamic content and choosing between escaped output and raw HTML when intentionally unsafe content is needed.
Quick Start
- Step 1: Create a layout (app/views/layouts/application.html.erb) using yield and content_for to define sections.
- Step 2: In a view, set content_for :title and render the main content with <%= yield %> and, optionally, yield :head.
- Step 3: Create and render partials with locals and build a simple form with form_with to show patterns in action.
Best Practices
- Use layouts with yield and content_for to keep chrome DRY and centralized.
- Render reusable components via partials with locals to avoid over-fetching assigns.
- Prefer collection rendering for lists and use as and spacer_template when needed.
- Escape HTML by default and only opt into raw output with clear intent and sanitization.
- Leverage built in helpers like link_to, image_tag, and form_with to produce accessible markup.
Example Use Cases
- Output a post title: <%= @post.title %>
- Create a link in navigation: <%= link_to 'Home', root_path %>
- Render a shared header partial: <%= render 'shared/header' %>
- Build a post form with validations: <%= form_with model: @post do |f| %> ...
- Display images: <%= image_tag 'logo.png' %>
Frequently Asked Questions
Add this skill to your agents