
Rails Controller
- 2 installs
- Updated February 9, 2026
- dchuk/rails_ai_agents
Creates RESTful Rails controllers with a TDD approach, writing integration tests first then the CRUD implementation.
About
Generates RESTful Rails controllers following project conventions with integration tests first. A developer uses it when creating controllers, adding actions, or implementing CRUD operations.
- Integration test first, then implementation
- RESTful CRUD controllers following conventions
Rails Controller by the numbers
- 2 all-time installs (skills.sh)
- Ranked #3,774 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/dchuk/rails_ai_agents --skill rails-controllerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| Last updated | February 9, 2026 |
| Repository | dchuk/rails_ai_agents ↗ |
What it does
Creates RESTful Rails controllers with a TDD approach, writing integration tests first then the CRUD implementation.
Files
Rails Controller Generator (TDD)
Creates RESTful controllers following project conventions with integration tests first.
Quick Start
1. Write failing integration test in test/controllers/ or test/integration/ 2. Run test to confirm RED 3. Implement controller action 4. Run test to confirm GREEN 5. Refactor if needed
Project Conventions
This project uses:
- Pundit for authorization (
authorize @resource,policy_scope(Model)) - Pagy for pagination
- Presenters for view formatting
- Multi-tenancy via
current_account - Turbo Stream responses for dynamic updates
TDD Workflow
Step 1: Create Integration Test (RED)
# test/controllers/[resources]_controller_test.rb
require "test_helper"
class ResourcesControllerTest < ActionDispatch::IntegrationTest
setup do
@user = users(:one)
@resource = resources(:one)
sign_in @user
end
# === INDEX ===
test "GET /resources returns success" do
get resources_path
assert_response :success
end
test "GET /resources shows only current_account resources (multi-tenant)" do
other_resource = resources(:other_account)
get resources_path
assert_includes response.body, @resource.name
assert_not_includes response.body, other_resource.name
end
test "GET /resources paginates results" do
get resources_path
assert_response :success
end
# === SHOW ===
test "GET /resources/:id returns success" do
get resource_path(@resource)
assert_response :success
end
test "GET /resources/:id returns 404 for other account" do
other_resource = resources(:other_account)
assert_raises(ActiveRecord::RecordNotFound) do
get resource_path(other_resource)
end
end
# === NEW ===
test "GET /resources/new returns success" do
get new_resource_path
assert_response :success
end
# === CREATE ===
test "POST /resources creates with valid params" do
assert_difference("Resource.count", 1) do
post resources_path, params: {
resource: { name: "New Resource", field1: "value" }
}
end
assert_redirected_to resources_path
assert_equal @user.account, Resource.last.account
end
test "POST /resources rejects invalid params" do
assert_no_difference("Resource.count") do
post resources_path, params: {
resource: { name: "" }
}
end
assert_response :unprocessable_entity
end
# === EDIT ===
test "GET /resources/:id/edit returns success" do
get edit_resource_path(@resource)
assert_response :success
end
# === UPDATE ===
test "PATCH /resources/:id updates with valid params" do
patch resource_path(@resource), params: {
resource: { name: "Updated Name" }
}
assert_redirected_to resource_path(@resource)
assert_equal "Updated Name", @resource.reload.name
end
test "PATCH /resources/:id rejects invalid params" do
patch resource_path(@resource), params: {
resource: { name: "" }
}
assert_response :unprocessable_entity
end
# === DESTROY ===
test "DELETE /resources/:id destroys resource" do
assert_difference("Resource.count", -1) do
delete resource_path(@resource)
end
assert_redirected_to resources_path
end
# === AUTHORIZATION ===
test "unauthenticated user is redirected" do
sign_out
get resources_path
assert_redirected_to new_session_path
end
endStep 2: Run Test (Confirm RED)
bin/rails test test/controllers/resources_controller_test.rbStep 3: Implement Controller (GREEN)
# app/controllers/[resources]_controller.rb
class ResourcesController < ApplicationController
before_action :set_resource, only: [:show, :edit, :update, :destroy]
def index
authorize Resource, :index?
@pagy, resources = pagy(policy_scope(Resource).order(created_at: :desc))
@resources = resources.map { |r| ResourcePresenter.new(r) }
end
def show
authorize @resource
@resource = ResourcePresenter.new(@resource)
end
def new
@resource = current_account.resources.build
authorize @resource
end
def create
@resource = current_account.resources.build(resource_params)
authorize @resource
if @resource.save
redirect_to resources_path, notice: "Resource created successfully"
else
render :new, status: :unprocessable_entity
end
end
def edit
authorize @resource
end
def update
authorize @resource
if @resource.update(resource_params)
redirect_to @resource, notice: "Resource updated successfully"
else
render :edit, status: :unprocessable_entity
end
end
def destroy
authorize @resource
@resource.destroy
redirect_to resources_path, notice: "Resource deleted successfully"
end
private
def set_resource
@resource = policy_scope(Resource).find(params[:id])
end
def resource_params
params.require(:resource).permit(:name, :field1, :field2)
end
endStep 4: Run Test (Confirm GREEN)
bin/rails test test/controllers/resources_controller_test.rbTest Helpers
Add to test/test_helper.rb:
class ActionDispatch::IntegrationTest
def sign_in(user)
session = user.identity.sessions.create!
cookies.signed[:session_token] = session.token
end
def sign_out
cookies.delete(:session_token)
end
endNamespaced Controllers
For nested routes like settings/accounts:
# app/controllers/settings/accounts_controller.rb
module Settings
class AccountsController < ApplicationController
before_action :set_account
def show
authorize @account
end
private
def set_account
@account = current_account
end
end
endTurbo Stream Response Pattern
def create
@resource = current_account.resources.build(resource_params)
authorize @resource
if @resource.save
respond_to do |format|
format.html { redirect_to resources_path, notice: "Created" }
format.turbo_stream do
flash.now[:notice] = "Created"
@pagy, @resources = pagy(policy_scope(Resource).order(created_at: :desc))
render turbo_stream: [
turbo_stream.replace("resources-list", partial: "resources/list"),
turbo_stream.update("modal", "")
]
end
end
else
render :new, status: :unprocessable_entity
end
endTesting Turbo Streams
test "POST /resources with turbo_stream format" do
post resources_path, params: {
resource: { name: "Turbo Resource" }
}, as: :turbo_stream
assert_response :success
assert_includes response.body, "turbo-stream"
endFull CRUD Fixture Setup
# test/fixtures/resources.yml
one:
name: "My Resource"
field1: "Value 1"
account: one
two:
name: "Another Resource"
field1: "Value 2"
account: one
other_account:
name: "Other Account Resource"
field1: "Value 3"
account: twoChecklist
- [ ] Integration test written first (RED)
- [ ] Multi-tenant isolation tested
- [ ] Authorization tested (redirect/404 for unauthorized)
- [ ] Controller uses
authorizeon every action - [ ] Controller uses
policy_scopefor queries - [ ] Presenter wraps models for views
- [ ] Strong parameters defined
- [ ] All 7 CRUD actions tested (index, show, new, create, edit, update, destroy)
- [ ] Invalid params tested (422 response)
- [ ] Turbo Stream responses tested (if applicable)
- [ ] All tests GREEN