Instruction file imported from wintermeyer/cursor-rails-rules (
.cursor/rules/views.mdc). Copyright stays with the author.
Structure
- Directory Organization
app/views/
├── layouts/
├── shared/
├── components/
└── [resource]/
├── index.html.erb
├── show.html.erb
├── _form.html.erb
└── [action].html.erb
- Naming Conventions
- Use lowercase
- Use underscores for spaces
- Prefix partials with underscore
- Use meaningful names
- Follow resource naming
Templates
- Layout Files
<!DOCTYPE html>
<html>
<head>
<title><%= content_for?(:title) ? yield(:title) : 'Default Title' %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application" %>
<%= javascript_include_tag "application", defer: true %>
</head>
<body>
<%= render "shared/header" %>
<main>
<%= render "shared/flash" %>
<%= yield %>
</main>
<%= render "shared/footer" %>
</body>
</html>
- Index Views
<% content_for :title, t('.title') %>
<div class="container">
<header class="mb-4">
<h1><%= t('.title') %></h1>
<%= link_to t('.new'), new_resource_path, class: 'button' %>
</header>
<%= render 'filter_form' if @filters.present? %>
<% if @resources.any? %>
<%= render @resources %>
<% else %>
<%= render 'shared/empty_state' %>
<% end %>
<%= render 'shared/pagination', collection: @resources %>
</div>
- Show Views
<% content_for :title, t('.title', name: @resource.name) %>
<div class="container">
<header class="mb-4">
<h1><%= t('.title', name: @resource.name) %></h1>
<div class="actions">
<%= link_to t('.edit'), edit_resource_path(@resource), class: 'button' %>
<%= button_to t('.delete'), resource_path(@resource),
method: :delete,
class: 'button button--danger',
data: { confirm: t('.confirm_delete') } %>
</div>
</header>
<%= render 'details', resource: @resource %>
<%= render 'related_resources' if @resource.related.any? %>
</div>
- Form Partials
<%= form_with model: @resource, class: 'form' do |f| %>
<%= render 'shared/error_messages', resource: @resource %>
<div class="form-group">
<%= f.label :attribute %>
<%= f.text_field :attribute, class: 'form-control' %>
<%= f.error_message :attribute %>
</div>
<div class="actions">
<%= f.submit class: 'button' %>
<%= link_to t('.cancel'), :back, class: 'button button--secondary' %>
</div>
<% end %>
Components
- Component Structure
# app/components/card_component.rb
class CardComponent < ViewComponent::Base
def initialize(title:, content:)
@title = title
@content = content
end
end
# app/components/card_component.html.erb
<div class="card">
<div class="card-header">
<h3><%= @title %></h3>
</div>
<div class="card-body">
<%= @content %>
</div>
</div>
- Component Usage
<%= render(CardComponent.new(title: t('.title'), content: content)) %>
Helpers
- View Helpers
# app/helpers/application_helper.rb
module ApplicationHelper
def page_title(title = nil)
if title.present?
content_for(:title) { title }
else
content_for?(:title) ? content_for(:title) : t('common.default_title')
end
end
def format_date(date)
l(date, format: :long) if date.present?
end
end
- Form Helpers
# app/helpers/form_helper.rb
module FormHelper
def error_message_for(resource, attribute)
return unless resource.errors[attribute].any?
content_tag(:span, resource.errors[attribute].first, class: 'error-message')
end
end
Best Practices
- General Guidelines
- Keep views simple and focused
- Use partials for reusability
- Leverage view components
- Follow DRY principles
- Use proper HTML semantics
- Performance
- Cache where appropriate
- Minimize database queries
- Use fragment caching
- Optimize assets
- Lazy load when possible
- Security
- Escape HTML by default
- Use content_tag helpers
- Sanitize user input
- Protect against XSS
- Use CSRF protection
- Accessibility
- Use semantic HTML
- Include ARIA attributes
- Provide alt text
- Ensure keyboard navigation
- Test with screen readers
Testing
- View Tests
# test/views/users/index_test.rb
require "test_helper"
class Users::IndexTest < ActionView::TestCase
test "displays user list" do
render template: "users/index"
assert_select "h1", text: I18n.t("users.index.title")
assert_select ".user", count: User.count
end
end
- Component Tests
# test/components/card_component_test.rb
require "test_helper"
class CardComponentTest < ViewComponent::TestCase
test "renders component" do
render_inline(CardComponent.new(title: "Test", content: "Content"))
assert_selector ".card"
assert_selector "h3", text: "Test"
assert_text "Content"
end
end
Maintenance
- Regular Tasks
- Review view complexity
- Update deprecated syntax
- Check accessibility
- Optimize performance
- Update dependencies
- Code Quality
- Run linters
- Check HTML validity
- Verify translations
- Test responsiveness
- Review security
