From vanilla-rails
Use when writing Rails controllers or implementing state changes - enforces resource extraction, thin controllers delegating to models, params.expect, and controller concerns for scoping
How this skill is triggered — by the user, by Claude, or both
Slash command
/vanilla-rails:controllersThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
**State changes are resources.** Every state change becomes its own resource controller with CRUD operations.
State changes are resources. Every state change becomes its own resource controller with CRUD operations.
# Bad - custom actions
resources :cards do
post :close
post :archive
end
# Good - state as resource
resources :cards do
resource :closure, only: [:create, :destroy]
resource :archival, only: [:create, :destroy]
end
See vanilla-rails-routing for route structure, nesting, and directory mapping.
| State Change | Resource | create = | destroy = |
|---|---|---|---|
| Close/Reopen | closure | close | reopen |
| Archive/Unarchive | archival | archive | unarchive |
| Pin/Unpin | pin | pin | unpin |
| Publish/Unpublish | publication | publish | unpublish |
| Assign/Unassign | assignment | assign | unassign |
| Follow/Unfollow | subscription | subscribe | unsubscribe |
Controllers delegate to intention-revealing model methods. No business logic in controllers.
# Bad - logic in controller
class Cards::ArchivalsController < ApplicationController
def create
@card.update(archived: true)
end
end
# Good - delegate to model
class Cards::ArchivalsController < ApplicationController
include CardScoped
def create
@card.archive
respond_to do |format|
format.turbo_stream
format.json { head :no_content }
end
end
def destroy
@card.unarchive
respond_to do |format|
format.turbo_stream
format.json { head :no_content }
end
end
end
Extract parent-finding into concerns. Name describes what's scoped:
# app/controllers/concerns/card_scoped.rb
module CardScoped
extend ActiveSupport::Concern
included do
before_action :set_card
end
private
def set_card
@card = Card.find(params[:card_id])
end
end
# app/controllers/concerns/board_scoped.rb
module BoardScoped
extend ActiveSupport::Concern
included do
before_action :set_board
end
private
def set_board
@board = Current.account.boards.find(params[:board_id])
end
end
Common concerns:
| Concern | Sets | Used by |
|---|---|---|
BoardScoped | @board | All controllers under boards/ |
CardScoped | @card | All controllers under cards/ |
Authenticated | session check | All controllers needing auth |
Use params.expect() instead of params.require().permit():
# Bad
def card_params
params.require(:card).permit(:title, :description)
end
# Good
def card_params
params.expect(card: [:title, :description])
end
State resources need a table tracking who/when:
create_table :closures, id: :uuid do |t|
t.uuid :card_id, null: false
t.uuid :user_id
t.timestamps
end
add_index :closures, :card_id, unique: true
| Red flag | Fix |
|---|---|
post :close, patch :activate | Extract resource |
| Business logic in controller | Move to model |
params.require().permit() | Use params.expect() |
before_action duplicated across controllers | Extract scoping concern |
| Controller > 30 lines per action | Delegate more to model |
params.expect() for strong parametersnpx claudepluginhub zemptime/zemptime-marketplace --plugin vanilla-railsBuilds RESTful Rails controllers with 7 standard actions, nested resources, skinny architecture, reusable concerns, and strong parameters. Enforces thin controllers and security rules.
Provides Rails Action Controller patterns for routing, before_action filters, strong parameters, and REST conventions. Useful for building robust MVC controllers.
Guides Rails code with rubocop-rails-omakase conventions on Turbo Streams, controllers, concerns, services, modern Ruby style, enums, scopes, error handling, and Minitest testing.