Skip to content

Ruby on Rails - Controllers

The Controller is the ‘C’ in MVC for a Rails application. It acts as an intermediary between the user (via routes and views) and the application’s data (models). Controllers handle incoming web requests, retrieve or modify data using models, and then render a response, usually by selecting a view template.

Key responsibilities of a controller:

  • Receiving HTTP requests from the router.
  • Interacting with models to fetch, create, update, or delete data.
  • Managing session information and cookies.
  • Handling user authentication and authorization (often with helper gems).
  • Making data available to views via instance variables (e.g., @books).
  • Choosing which view template to render or which URL to redirect to.

You can generate a controller using the Rails generator. For our library application, let’s create a BooksController:

$ bin/rails generate controller Books index show new edit create update destroy

Conventionally, controller names are plural and CamelCased (e.g., BooksController). This command creates:

  • app/controllers/books_controller.rb
  • View template files for the specified actions (e.g., app/views/books/index.html.erb)
  • Test files, helper files, and potentially asset files.
  • It also adds resourceful routes for books to config/routes.rb if they don’t exist.

The generated app/controllers/books_controller.rb will look something like this:

class BooksController < ApplicationController
def index
end
def show
end
# ... other generated actions ...
end

Controller classes inherit from ApplicationController (defined in app/controllers/application_controller.rb), which is a good place for application-wide controller logic like authentication or layout settings.

Implementing Controller Actions (CRUD Example)

Section titled “Implementing Controller Actions (CRUD Example)”

Let’s implement common CRUD (Create, Read, Update, Delete) actions for our BooksController.

The index action typically displays a list of resources.

def index
@books = Book.all.order(title: :asc) # Fetch all books, order by title
# Rails will automatically render app/views/books/index.html.erb
end

The show action displays details for a single resource, identified by params[:id].

before_action :set_book, only: [:show, :edit, :update, :destroy]
# ...
def show
# @book is set by the set_book before_action
# Renders app/views/books/show.html.erb
end
# ...
private
def set_book
@book = Book.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:alert] = "The book you were looking for could not be found."
redirect_to books_path
end

before_action :set_book calls the set_book method before the specified actions. This DRYs up code for finding a book. Error handling for RecordNotFound is good practice.

The new action prepares an empty @book object for the new book form.

def new
@book = Book.new
@subjects = Subject.all.order(:name) # For the subject dropdown in the form
# Renders app/views/books/new.html.erb
end

The create action handles the submission from the new form and attempts to save the new book.

def create
@book = Book.new(book_params)
if @book.save
redirect_to @book, notice: 'Book was successfully created.' # Redirect to the show page
else
@subjects = Subject.all.order(:name) # Reload subjects for the form if re-rendering
render :new, status: :unprocessable_entity # Re-render the new form with errors
end
end

book_params is a private method using Strong Parameters to permit only allowed attributes, preventing mass assignment vulnerabilities.

private
def book_params
params.require(:book).permit(:title, :price, :subject_id, :description, :published_at)
end

The edit action fetches an existing book (using set_book) for the edit form.

def edit
# @book is set by set_book
@subjects = Subject.all.order(:name)
# Renders app/views/books/edit.html.erb
end

The update action handles submission from the edit form.

def update
# @book is set by set_book
if @book.update(book_params)
redirect_to @book, notice: 'Book was successfully updated.'
else
@subjects = Subject.all.order(:name)
render :edit, status: :unprocessable_entity
end
end

The destroy action deletes a book.

def destroy
# @book is set by set_book
@book.destroy
redirect_to books_url, notice: 'Book was successfully destroyed.', status: :see_other
end

The status: :see_other (HTTP 303) is recommended for redirects after non-GET requests that change state, especially with Turbo.

class BooksController < ApplicationController
before_action :set_book, only: [:show, :edit, :update, :destroy]
before_action :load_subjects, only: [:new, :edit, :create, :update] # Load subjects for forms
def index
@books = Book.includes(:subject).all.order(title: :asc)
end
def show
end
def new
@book = Book.new
end
def edit
end
def create
@book = Book.new(book_params)
if @book.save
redirect_to @book, notice: 'Book was successfully created.'
else
render :new, status: :unprocessable_entity
end
end
def update
if @book.update(book_params)
redirect_to @book, notice: 'Book was successfully updated.'
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@book.destroy
redirect_to books_url, notice: 'Book was successfully destroyed.', status: :see_other
end
# Example custom action for listing books by subject (if needed here)
# def books_by_subject
# @subject = Subject.find(params[:subject_id])
# @books = @subject.books.order(title: :asc)
# render :index # Or a custom view like 'books_by_subject.html.erb'
# end
private
def set_book
@book = Book.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:alert] = "The book you were looking for could not be found."
redirect_to books_path
end
def load_subjects
@subjects = Subject.all.order(:name)
end
def book_params
params.require(:book).permit(:title, :price, :subject_id, :description, :published_at)
end
end

This controller now handles the full lifecycle of book resources.

With your controller actions defined, you need to define routes that map URLs to these actions. Then, you’ll create view templates to display the information and forms for user interaction.