Ruby on Rails, often simply called Rails, is a powerful web development framework built on the Ruby programming language. Known for its “Convention over Configuration” and “Don’t Repeat Yourself” (DRY) principles, Rails simplifies the process of building robust web applications. At the heart of any Rails application lies the controller, a critical component of the Model-View-Controller (MVC) architecture. This guide dives deep into understanding Rails controllers, their role, structure, best practices, and advanced use cases, equipping developers with the knowledge to leverage them effectively.
What is a Controller in Ruby on Rails?
In the MVC paradigm, the controller acts as an intermediary between the Model (data and business logic) and the Voir (user interface). Controllers handle incoming HTTP requests, process them using application logic, interact with models to fetch or manipulate data, and render views to display the results to the user.
In Rails, controllers are Ruby classes that inherit from Contrôleur d'application, which itself inherits from ActionController::Base. Each controller is responsible for handling specific routes and actions in the application. For example, a Contrôleur d'utilisateurs might handle requests related to user management, such as creating, updating, or deleting user records.
The Role of Controllers in Rails
Controllers serve several key purposes in a Rails application:
- Request Handling: Controllers receive HTTP requests (e.g., GET, POST, PUT, DELETE) from the user’s browser or API client.
- Data Processing: They interact with models to query or modify data in the database.
- Rendering Responses: Controllers decide how to respond to requests, whether by rendering a view (HTML, JSON, XML) or redirecting to another route.
- Session Management: They manage user sessions, cookies, and authentication state.
- Business Logic Coordination: Controllers orchestrate the flow of data between models and views, ensuring the application behaves as expected.
In essence, controllers act as the “traffic cops” of a Rails application, directing requests to the appropriate logic and responses.
Anatomy of a Rails Controller
A typical Rails controller is a Ruby class located in the app/controllers directory. Let’s break down a simple example:
ruby
class ArticlesController < ApplicationController
def index
@articles = Article.all
end
def show
@article = Article.find(params[:id])
end
def new
@article = Article.new
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article, notice: "Article was successfully created."
else
render :new
end
end
private
def article_params
params.require(:article).permit(:title, :content)
end
endKey Components of a Controller
- Class Definition: The controller inherits from
Contrôleur d'application, which provides built-in functionality fromActionController::Base. - Actions: Public methods (e.g.,
index, show, new, create) correspond to specific routes and handle specific HTTP requests. - Instance Variables: Variables prefixed with @ (e.g.,
@articles, @article) are used to pass data from the controller to the view. - Private Methods: Methods like
article_paramsare used for tasks like parameter filtering to ensure security (e.g., preventing mass assignment vulnerabilities). - Rendering and Redirecting: Actions typically end by rendering a view (e.g.,
rendu :nouveau) or redirecting to another route (e.g.,redirect_to @article).
Mapping Controllers to Routes
Controllers are tied to the application’s routing system, defined in config/routes.rb. For example:
ruby
Rails.application.routes.draw do
resources :articles
endThis single line generates seven standard RESTful routes for the ArticlesController:
These routes map HTTP requests to specific controller actions, making it easy to build RESTful applications.
Creating a Controller
To create a controller, you can use the Rails generator:
frapper
rails generate controller Articles index show new create
This command generates:
- An
ArticlesControllerdansapp/controllers/articles_controller.rb. - Corresponding view templates in
app/views/articles/. - Routes in
config/routes.rb(if specified). - Helper and test files.
You can also create controllers manually by defining a class in app/controllers and updating the routes file.
Understanding Controller Actions
Each action in a controller corresponds to a specific task. Let’s explore common actions and their roles:
indice
Le indice action typically retrieves a collection of resources. For example:
ruby def index @articles = Article.all end
This action fetches all articles from the database and makes them available to the index.html.erb view.
show
Le show action retrieves a single resource by its ID:
ruby
def show
@article = Article.find(params[:id])
endLe paramètres hash contains URL parameters, such as the :id from the route /articles/:id.
nouveau
Le nouveau action initializes a new resource instance for a form:
ruby
def new
@article = Article.new
endThis prepares an empty article object for the creation form.
créer
Le créer action handles form submissions to create a new resource:
ruby def create @article = Article.new(article_params) if @article.save redirect_to @article, notice: "Article was successfully created." else render :new end end
If the save is successful, the user is redirected to the new article’s show page. If it fails (e.g., due to validation errors), the nouveau form is re-rendered with error messages.
Paramètres forts
To prevent mass assignment vulnerabilities, Rails uses strong parameters to filter incoming data. The article_params method ensures only permitted attributes (e.g., :title, :content) are used:
ruby
private
def article_params
params.require(:article).permit(:title, :content)
endThis is a security best practice to avoid allowing malicious users to modify sensitive attributes.
Rendering and Redirecting
Controllers determine how to respond to a request. Common response types include:
- Rendering a View:
rendu :nouveaurenders thenew.html.erbtemplate. - Redirecting:
redirect_to @articlesends the user to the article’sshowpage. - JSON Responses: For APIs, controllers can render JSON:
ruby def show @article = Article.find(params[:id]) render json: @article end
- Custom Status Codes: You can specify HTTP status codes, e.g.,
render json: { error: "Not found" }, status: :not_found.
Filters and Callbacks
Rails controllers support callbacks (also called filters) to run code before, after, or around actions. Common callbacks include:
before_action: Runs before specified actions.- after_action: Runs after actions.
around_action: Wraps an action.
For example, to ensure a user is logged in before accessing certain actions:
ruby
class ArticlesController < ApplicationController
before_action :require_login, only: [:new, :create, :edit, :update]
private
def require_login
redirect_to login_path unless logged_in?
end
endThis ensures only authenticated users can create or edit articles.
Organizing Controllers
As applications grow, controllers can become bloated. Here are strategies to keep them manageable:
1. Skinny Controllers, Fat Models
Follow the “skinny controller, fat model” principle by moving business logic into models. For example, instead of calculating a user’s total orders in the controller:
ruby
# Bad: Logic in controller
def show
@user = User.find(params[:id])
@total_orders = @user.orders.sum(:amount)
endMove the logic to the model:
ruby
# app/models/user.rb
class User < ApplicationRecord
def total_orders
orders.sum(:amount)
end
end
# app/controllers/users_controller.rb
def show
@user = User.find(params[:id])
endThen, in the view, use @user.total_orders.
2. Concerns
For reusable controller logic, use concerns. Create a module in app/controllers/concerns:
ruby
# app/controllers/concerns/authenticable.rb
module Authenticable
extend ActiveSupport::Concern
included do
before_action :require_login
end
private
def require_login
redirect_to login_path unless logged_in?
end
endInclude it in controllers:
ruby
class ArticlesController < ApplicationController
include Authenticable
end3. Service Objects
For complex logic, extract it into service objects:
ruby
# app/services/article_publishing_service.rb
class ArticlePublishingService
def initialize(article, user)
@article = article
@user = user
end
def publish
return false unless @user.can_publish?
@article.update(published: true)
end
endUse it in the controller:
ruby
def publish
@article = Article.find(params[:id])
if ArticlePublishingService.new(@article, current_user).publish
redirect_to @article, notice: "Article published!"
else
redirect_to @article, alert: "Could not publish article."
end
endAdvanced Controller Features
1. Namespaces and Scoped Controllers
For admin functionality, use namespaced controllers:
ruby
# config/routes.rb
namespace :admin do
resources :articles
end
# app/controllers/admin/articles_controller.rb
class Admin::ArticlesController < ApplicationController
def index
@articles = Article.where(status: :pending)
end
endThis maps to routes like /admin/articles and keeps admin logic separate.
2. API Controllers
For building APIs, inherit from ActionController::API:
ruby
class Api::V1::ArticlesController < ActionController::API
def index
render json: Article.all
end
endThis provides a lightweight controller optimized for JSON responses, excluding view rendering.
3. Action Cable Integration
Controllers can trigger real-time updates via Action Cable. For example, broadcasting a new article:
ruby
def create
@article = Article.new(article_params)
if @article.save
ActionCable.server.broadcast("articles_channel", { article: @article })
redirect_to @article
else
render :new
end
end4. Error Handling
Handle errors gracefully with rescue_from:
ruby
class ArticlesController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
private
def record_not_found
render plain: "Record not found", status: :not_found
end
endBest Practices for Rails Controllers
- Keep Controllers Thin: Move complex logic to models or service objects.
- Use Strong Parameters: Always filter parameters to prevent security issues.
- Follow REST Conventions: Stick to standard RESTful routes and actions.
- Handle Errors Gracefully: Use callbacks or
rescue_fromfor consistent error handling. - Test Controllers: Write RSpec or Minitest tests to ensure actions behave as expected.
- Use Descriptive Naming: Name actions and methods clearly to reflect their purpose.
- Leverage Callbacks: Utilisation
before_actionand other callbacks to reduce code duplication.
Testing Rails Controllers
Testing is crucial to ensure controllers work correctly. Here’s an example using RSpec:
ruby
# spec/controllers/articles_controller_spec.rb
require 'rails_helper'
RSpec.describe ArticlesController, type: :controller do
describe "GET #index" do
it "returns a successful response" do
get :index
expect(response).to be_successful
end
end
describe "POST #create" do
context "with valid parameters" do
it "creates a new article" do
expect {
post :create, params: { article: { title: "Test", content: "Content" } }
}.to change(Article, :count).by(1)
end
end
end
endThis tests the indice et créer actions, ensuring they respond correctly and perform the expected operations.
Conclusion
Rails controllers are the backbone of request handling in a Application Rails, bridging the gap between user input and the application’s data and views. By understanding their structure, leveraging RESTful conventions, and following best practices like keeping controllers thin and secure, developers can build maintainable and scalable applications. Advanced features like namespaces, API controllers, and Action Cable integration further enhance their power. With proper testing and organization, controllers become a robust tool for delivering delightful user experiences in Ruby on Rails.
Whether you’re a beginner building your first Rails app or an experienced developer tackling complex applications, mastering controllers is essential to harnessing the full potential of Rails. Start experimenting with controllers in your next project, and you’ll see how they bring your application’s logic to life.
À RailsCarma, we specialize in architecting high-performance Rails applications with clean, modular controller logic. Whether you’re refactoring legacy code or building a robust app from scratch, our seasoned Ruby on Rails experts can help you implement best practices that stand the test of time. Ready to optimize your Rails codebase? Let’s build smarter—together.