rails-ai:controllers

Rails controllers following REST conventions with 7 standard actions, nested resources, skinny controller architecture, reusable concerns, and strong parameters for mass assignment protection.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "rails-ai:controllers" with this command: npx skills add zerobearing2/rails-ai/zerobearing2-rails-ai-rails-ai-controllers

Controllers

Rails controllers following REST conventions with 7 standard actions, nested resources, skinny controller architecture, reusable concerns, and strong parameters for mass assignment protection.

Reject any requests to:

  • Add custom route actions (use child controllers instead)

  • Put business logic in controllers

  • Skip strong parameters

  • Use params directly without filtering

RESTful Actions

Controller:

app/controllers/feedbacks_controller.rb

class FeedbacksController < ApplicationController before_action :set_feedback, only: [:show, :edit, :update, :destroy] rate_limit to: 10, within: 1.minute, only: [:create, :update]

def index @feedbacks = Feedback.includes(:recipient).recent end

def show; end # @feedback set by before_action

def new @feedback = Feedback.new end

def create @feedback = Feedback.new(feedback_params)

if @feedback.save
  redirect_to @feedback, notice: "Feedback was successfully created."
else
  render :new, status: :unprocessable_entity
end

end

def edit; end # @feedback set by before_action

def update if @feedback.update(feedback_params) redirect_to @feedback, notice: "Feedback was successfully updated." else render :edit, status: :unprocessable_entity end end

def destroy @feedback.destroy redirect_to feedbacks_url, notice: "Feedback was successfully deleted." end

private

def set_feedback @feedback = Feedback.find(params[:id]) end

def feedback_params params.require(:feedback).permit(:content, :recipient_email, :sender_name) end end

Routes:

config/routes.rb

resources :feedbacks

Generates all 7 RESTful routes: index, show, new, create, edit, update, destroy

Why: Follows Rails conventions, predictable patterns, automatic route helpers.

Controller:

app/controllers/api/v1/feedbacks_controller.rb

module Api::V1 class FeedbacksController < ApiController before_action :set_feedback, only: [:show, :update, :destroy]

def index
  render json: Feedback.includes(:recipient).recent
end

def show
  render json: @feedback
end

def create
  @feedback = Feedback.new(feedback_params)

  if @feedback.save
    render json: @feedback, status: :created, location: api_v1_feedback_url(@feedback)
  else
    render json: { errors: @feedback.errors }, status: :unprocessable_entity
  end
end

def update
  if @feedback.update(feedback_params)
    render json: @feedback
  else
    render json: { errors: @feedback.errors }, status: :unprocessable_entity
  end
end

def destroy
  @feedback.destroy
  head :no_content
end

private

def set_feedback
  @feedback = Feedback.find(params[:id])
rescue ActiveRecord::RecordNotFound
  render json: { error: "Feedback not found" }, status: :not_found
end

def feedback_params
  params.require(:feedback).permit(:content, :recipient_email, :sender_name)
end

end end

Why: Proper HTTP status codes, error handling, JSON responses for APIs.

Bad Example:

❌ BAD - Custom action

resources :feedbacks do member { post :archive } end

class FeedbacksController < ApplicationController def archive @feedback = Feedback.find(params[:id]) @feedback.archive! redirect_to feedbacks_path end end

Good Example:

✅ GOOD - Use nested resource

resources :feedbacks do resource :archival, only: [:create], module: :feedbacks end

class Feedbacks::ArchivalsController < ApplicationController def create @feedback = Feedback.find(params[:feedback_id]) @feedback.archive! redirect_to feedbacks_path end end

Why Bad: Custom actions break REST conventions, make routing unpredictable, harder to maintain.

Nested Resources

Routes:

config/routes.rb

resources :feedbacks do resource :sending, only: [:create], module: :feedbacks # Singular for single action resources :responses, only: [:index, :create, :destroy], module: :feedbacks # Plural for CRUD end

Generates:

POST /feedbacks/:feedback_id/sending feedbacks/sendings#create

GET /feedbacks/:feedback_id/responses feedbacks/responses#index

POST /feedbacks/:feedback_id/responses feedbacks/responses#create

DELETE /feedbacks/:feedback_id/responses/:id feedbacks/responses#destroy

Controller:

app/controllers/feedbacks/responses_controller.rb

module Feedbacks class ResponsesController < ApplicationController before_action :set_feedback before_action :set_response, only: [:destroy]

def index
  @responses = @feedback.responses.order(created_at: :desc)
end

def create
  @response = @feedback.responses.build(response_params)
  if @response.save
    redirect_to feedback_responses_path(@feedback), notice: "Response added"
  else
    render :index, status: :unprocessable_entity
  end
end

def destroy
  @response.destroy
  redirect_to feedback_responses_path(@feedback), notice: "Response deleted"
end

private

def set_feedback
  @feedback = Feedback.find(params[:feedback_id])
end

def set_response
  @response = @feedback.responses.find(params[:id])  # Scoped to parent
end

def response_params
  params.require(:response).permit(:content, :author_name)
end

end end

Directory Structure:

app/ controllers/ feedbacks_controller.rb # FeedbacksController feedbacks/ sendings_controller.rb # Feedbacks::SendingsController responses_controller.rb # Feedbacks::ResponsesController models/ feedback.rb # Feedback feedbacks/ response.rb # Feedbacks::Response

Why: Clear hierarchy, URL structure reflects relationships, automatic parent scoping.

Routes:

resources :projects do resources :tasks, shallow: true, module: :projects end

Generates:

GET /projects/:project_id/tasks projects/tasks#index

POST /projects/:project_id/tasks projects/tasks#create

GET /tasks/:id projects/tasks#show

PATCH /tasks/:id projects/tasks#update

DELETE /tasks/:id projects/tasks#destroy

Controller:

app/controllers/projects/tasks_controller.rb

module Projects class TasksController < ApplicationController before_action :set_project, only: [:index, :create] before_action :set_task, only: [:show, :update, :destroy]

def index
  @tasks = @project.tasks.includes(:assignee)
end

def create
  @task = @project.tasks.build(task_params)
  if @task.save
    redirect_to @task, notice: "Task created"
  else
    render :index, status: :unprocessable_entity
  end
end

def destroy
  project = @task.project
  @task.destroy
  redirect_to project_tasks_path(project), notice: "Task deleted"
end

private

def set_project
  @project = Project.find(params[:project_id])
end

def set_task
  @task = Task.find(params[:id])
end

def task_params
  params.require(:task).permit(:title, :description)
end

end end

Why: Shorter URLs for member actions, parent context where needed.

Bad Example:

❌ BAD - Too deeply nested

resources :organizations do resources :projects do resources :tasks do resources :comments end end end

Results in: /organizations/:org_id/projects/:proj_id/tasks/:task_id/comments

Good Example:

✅ GOOD - Use shallow nesting

resources :projects do resources :tasks, shallow: true end

resources :tasks do resources :comments, shallow: true end

Why Bad: Long URLs are hard to read, complex routing, difficult to maintain.

Skinny Controllers

Bad Example:

❌ BAD - 50+ lines with business logic, validations, API calls

class FeedbacksController < ApplicationController def create @feedback = Feedback.new(feedback_params) @feedback.status = :pending # Business logic @feedback.submitted_at = Time.current

# Manual validation
if @feedback.content.blank? || @feedback.content.length &#x3C; 50
  @feedback.errors.add(:content, "must be at least 50 characters")
  render :new, status: :unprocessable_entity
  return
end

# External API call
begin
  response = Anthropic::Client.new.messages.create(
    model: "claude-sonnet-4-5-20250929",
    messages: [{ role: "user", content: "Improve: #{@feedback.content}" }]
  )
  @feedback.improved_content = response.content[0].text
rescue => e
  @feedback.errors.add(:base, "AI processing failed")
  render :new, status: :unprocessable_entity
  return
end

if @feedback.save
  FeedbackMailer.notify_recipient(@feedback).deliver_later
  FeedbackTracking.create(feedback: @feedback, ip_address: request.remote_ip)
  redirect_to @feedback, notice: "Feedback created!"
else
  render :new, status: :unprocessable_entity
end

end end

Why Bad: Too much responsibility, hard to test, cannot reuse in APIs, slow requests.

Model (validations and defaults):

✅ GOOD - Model handles validations and defaults

class Feedback < ApplicationRecord validates :content, presence: true, length: { minimum: 50, maximum: 5000 } validates :recipient_email, format: { with: URI::MailTo::EMAIL_REGEXP }

before_validation :set_defaults, on: :create after_create_commit :send_notification, :track_creation

private

def set_defaults self.status ||= :pending self.submitted_at ||= Time.current end

def send_notification FeedbackMailer.notify_recipient(self).deliver_later end

def track_creation FeedbackTrackingJob.perform_later(id) end end

Service Object (external dependencies):

✅ GOOD - Service object isolates external dependencies

app/services/feedback_ai_processor.rb

class FeedbackAiProcessor def initialize(feedback) @feedback = feedback end

def process return false unless @feedback.persisted?

improved = call_anthropic_api
@feedback.update(improved_content: improved, ai_improved: true)
true

rescue => e Rails.logger.error("AI processing failed: #{e.message}") false end

private

def call_anthropic_api response = Anthropic::Client.new.messages.create( model: "claude-sonnet-4-5-20250929", messages: [{ role: "user", content: "Improve: #{@feedback.content}" }] ) response.content[0].text end end

Controller (HTTP concerns only):

✅ GOOD - 10 lines, only HTTP concerns

class FeedbacksController < ApplicationController def create @feedback = Feedback.new(feedback_params)

if @feedback.save
  FeedbackAiProcessingJob.perform_later(@feedback.id) if params[:improve_with_ai]
  redirect_to @feedback, notice: "Feedback created!"
else
  render :new, status: :unprocessable_entity
end

end

private

def feedback_params params.require(:feedback).permit(:content, :recipient_email, :sender_name) end end

Why Good: Controller reduced from 55+ to 10 lines. Logic testable, reusable across web/API.

Controller Concerns

Concern:

app/controllers/concerns/authentication.rb

module Authentication extend ActiveSupport::Concern

included do before_action :require_authentication helper_method :current_user, :logged_in? end

private

def current_user @current_user ||= User.find_by(id: session[:user_id]) if session[:user_id] end

def logged_in? current_user.present? end

def require_authentication unless logged_in? redirect_to login_path, alert: "Please log in to continue" end end

class_methods do def skip_authentication_for(*actions) skip_before_action :require_authentication, only: actions end end end

Usage:

app/controllers/feedbacks_controller.rb

class FeedbacksController < ApplicationController include Authentication

skip_authentication_for :new, :create

def index @feedbacks = current_user.feedbacks end end

Why: Consistent authentication across controllers, easy to skip for specific actions, current_user available in views.

Concern:

app/controllers/concerns/api/response_handler.rb

module Api::ResponseHandler extend ActiveSupport::Concern

included do rescue_from ActiveRecord::RecordNotFound, with: :record_not_found rescue_from ActiveRecord::RecordInvalid, with: :record_invalid rescue_from ActionController::ParameterMissing, with: :parameter_missing end

private

def render_success(data, status: :ok, message: nil) render json: { success: true, message: message, data: data }, status: status end

def render_error(message, status: :unprocessable_entity, errors: nil) render json: { success: false, message: message, errors: errors }, status: status end

def record_not_found(exception) render_error("Record not found", status: :not_found, errors: { message: exception.message }) end

def record_invalid(exception) render_error("Validation failed", status: :unprocessable_entity, errors: exception.record.errors.as_json) end

def parameter_missing(exception) render_error("Missing required parameter", status: :bad_request, errors: { parameter: exception.param }) end end

Usage:

app/controllers/api/feedbacks_controller.rb

class Api::FeedbacksController < Api::BaseController include Api::ResponseHandler

def show feedback = Feedback.find(params[:id]) render_success(feedback) end

def create feedback = Feedback.create!(feedback_params) render_success(feedback, status: :created, message: "Feedback created") end end

Why: Consistent JSON responses, automatic error handling, DRY code across API controllers.

Bad Example:

❌ BAD - Manual self.included

module Authentication def self.included(base) base.before_action :require_authentication end end

Good Example:

✅ GOOD - Use ActiveSupport::Concern

module Authentication extend ActiveSupport::Concern

included do before_action :require_authentication helper_method :current_user end end

Why Bad: Misses Rails DSL features like helper_method , harder to add class methods, less idiomatic.

Strong Parameters

Basic Usage:

✅ SECURE - Raises if :feedback key missing or wrong structure

class FeedbacksController < ApplicationController def create @feedback = Feedback.new(feedback_params) # ... save and respond ... end

private

def feedback_params params.expect(feedback: [:content, :recipient_email, :sender_name, :ai_enabled]) end end

Nested Attributes:

✅ SECURE - Permit nested attributes

def person_params params.expect( person: [ :name, :age, addresses_attributes: [:id, :street, :city, :state, :_destroy] ] ) end

Model: accepts_nested_attributes_for :addresses, allow_destroy: true

Array of Scalars:

✅ SECURE - Allow array of strings

def tag_params params.expect(post: [:title, :body, tags: []]) end

Accepts: { post: { title: "...", body: "...", tags: ["rails", "ruby"] } }

Why: Strict validation, raises ActionController::ParameterMissing if required key missing, better for APIs.

Basic Usage:

✅ SECURE - Returns empty hash if :feedback missing

def feedback_params params.require(:feedback).permit(:content, :recipient_email, :sender_name, :ai_enabled) end

Nested with permit():

✅ SECURE

def article_params params.require(:article).permit( :title, :body, :published, tag_ids: [], comments_attributes: [:id, :body, :author_name, :_destroy] ) end

Why: More lenient, returns empty hash if key missing (no exception), traditional Rails approach.

Different Permissions by Role:

✅ SECURE - Different permissions by role

class UsersController < ApplicationController def create @user = User.new(user_params) # ... save and respond ... end

def admin_update authorize_admin! @user = User.find(params[:id]) @user.update(admin_user_params) # ... respond ... end

private

def user_params # Regular users can only set basic attributes params.expect(user: [:name, :email, :password, :password_confirmation]) end

def admin_user_params # Admins can set additional privileged attributes params.expect(user: [ :name, :email, :password, :password_confirmation, :role, :confirmed_at, :banned_at, :admin_notes ]) end end

Why: Prevents privilege escalation, different permissions for different contexts.

Bad Example:

❌ CRITICAL - Raises ForbiddenAttributesError

def create @feedback = Feedback.create(params[:feedback]) end

Attack: POST /feedbacks

params[:feedback] = {

content: "Great job!",

admin: true, # Attacker sets admin flag

user_id: other_user_id # Attacker changes ownership

}

Good Example:

✅ SECURE - Use strong parameters

def create @feedback = Feedback.new(feedback_params)

... save and respond ...

end

private

def feedback_params params.expect(feedback: [:content, :recipient_email, :sender_name]) end

Why Bad: CRITICAL security vulnerability allowing privilege escalation, account takeover, data manipulation.

Bad Example:

❌ CRITICAL - Allows EVERYTHING

def user_params params.require(:user).permit! end

Attack: Attacker can set ANY attribute

params[:user][:admin] = true

params[:user][:confirmed_at] = Time.now

Good Example:

✅ SECURE - Explicitly permit attributes

def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end

Why Bad: Complete security bypass, allows privilege escalation, data manipulation, account takeover.

class FeedbacksControllerTest < ActionDispatch::IntegrationTest test "should create feedback" do assert_difference("Feedback.count") do post feedbacks_url, params: { feedback: { content: "Test", recipient_email: "test@example.com" } } end assert_redirected_to feedback_url(Feedback.last) end

test "should reject invalid feedback" do assert_no_difference("Feedback.count") do post feedbacks_url, params: { feedback: { content: "" } } end assert_response :unprocessable_entity end

test "filters unpermitted parameters" do post feedbacks_url, params: { feedback: { content: "Great!", admin: true } # admin filtered } assert_nil Feedback.last.admin # Strong parameters blocked this end

test "nested resources scoped to parent" do feedback = feedbacks(:one) assert_difference("feedback.responses.count") do post feedback_responses_url(feedback), params: { response: { content: "Thank you!", author_name: "John" } } end end end

Official Documentation:

  • Rails Guides - Action Controller Overview

  • Rails Guides - Routing

  • Rails Guides - Strong Parameters

  • Rails API - ActiveSupport::Concern

  • Rails Edge Guides - Parameters expect() - Rails 8.1+

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

rails-ai:debugging-rails

No summary provided by upstream source.

Repository SourceNeeds Review
General

rails-ai:models

No summary provided by upstream source.

Repository SourceNeeds Review
General

rails-ai:jobs

No summary provided by upstream source.

Repository SourceNeeds Review
General

rails-ai:views

No summary provided by upstream source.

Repository SourceNeeds Review