Skip
Arish's avatar

18. Controllers Basics


Controllers in Rails

Controllers are the C in MVC. They receive HTTP requests, interact with models to get data, and render views or redirect users.

Creating a Controller

Using the Generator

bash
1rails generate controller Articles index show new create edit update destroy
2# Creates:
3# app/controllers/articles_controller.rb
4# app/views/articles/ (with view files)
5# test/controllers/articles_controller_test.rb
6# app/helpers/articles_helper.rb

Manual Creation

ruby
1# app/controllers/articles_controller.rb
2class ArticlesController < ApplicationController
3  def index
4    @articles = Article.all
5  end
6  
7  def show
8    @article = Article.find(params[:id])
9  end
10end

Controller Actions

Each public method in a controller is an action that can respond to a route:

ruby
1class ArticlesController < ApplicationController
2  # GET /articles
3  def index
4    @articles = Article.published.recent
5  end
6  
7  # GET /articles/:id
8  def show
9    @article = Article.find(params[:id])
10  end
11  
12  # GET /articles/new
13  def new
14    @article = Article.new
15  end
16  
17  # POST /articles
18  def create
19    @article = Article.new(article_params)
20    
21    if @article.save
22      redirect_to @article, notice: 'Article created!'
23    else
24      render :new, status: :unprocessable_entity
25    end
26  end
27  
28  # GET /articles/:id/edit
29  def edit
30    @article = Article.find(params[:id])
31  end
32  
33  # PATCH/PUT /articles/:id
34  def update
35    @article = Article.find(params[:id])
36    
37    if @article.update(article_params)
38      redirect_to @article, notice: 'Article updated!'
39    else
40      render :edit, status: :unprocessable_entity
41    end
42  end
43  
44  # DELETE /articles/:id
45  def destroy
46    @article = Article.find(params[:id])
47    @article.destroy
48    redirect_to articles_path, notice: 'Article deleted!'
49  end
50  
51  private
52  
53  def article_params
54    params.require(:article).permit(:title, :body, :published)
55  end
56end

Working with Parameters

Accessing Parameters

ruby
1# URL: /articles?category=ruby&page=2
2params[:category]  # => "ruby"
3params[:page]      # => "2"
4
5# URL: /articles/5
6params[:id]        # => "5"
7
8# Form data (POST/PATCH)
9params[:article][:title]  # => "My Title"

Strong Parameters

Strong parameters protect against mass assignment vulnerabilities:

ruby
1class ArticlesController < ApplicationController
2  private
3  
4  # Basic usage
5  def article_params
6    params.require(:article).permit(:title, :body, :published)
7  end
8  
9  # With nested attributes
10  def article_params
11    params.require(:article).permit(
12      :title, 
13      :body,
14      :category_id,
15      tag_ids: [],                    # Array of values
16      images_attributes: [:id, :url, :_destroy],  # Nested
17      metadata: {}                    # Hash (permit all keys)
18    )
19  end
20  
21  # Conditional parameters
22  def article_params
23    permitted = [:title, :body]
24    permitted << :published if current_user.admin?
25    params.require(:article).permit(permitted)
26  end
27end

Rendering Responses

Render Views

ruby
1class ArticlesController < ApplicationController
2  def show
3    @article = Article.find(params[:id])
4    # Automatically renders app/views/articles/show.html.erb
5  end
6  
7  def index
8    @articles = Article.all
9    render :index  # Explicit render (same as default)
10  end
11  
12  def special
13    @article = Article.find(params[:id])
14    render :show  # Render a different template
15  end
16  
17  def from_other
18    render 'other_controller/other_action'  # Render from another controller
19  end
20end

Render with Options

ruby
1# Render with status code
2render :new, status: :unprocessable_entity  # 422
3render :not_found, status: :not_found       # 404
4
5# Render different formats
6render json: @article
7render xml: @article
8render plain: 'Hello World'
9render html: '<h1>Hello</h1>'.html_safe
10
11# Render with layout
12render :show, layout: 'admin'
13render :show, layout: false  # No layout
14
15# Render partial
16render partial: 'form'
17render partial: 'article', collection: @articles

Redirects

ruby
1class ArticlesController < ApplicationController
2  def create
3    @article = Article.new(article_params)
4    
5    if @article.save
6      # Redirect to the article's show page
7      redirect_to @article
8      
9      # Or with a flash message
10      redirect_to @article, notice: 'Created!'
11      
12      # Or with alert
13      redirect_to articles_path, alert: 'Something went wrong'
14    else
15      render :new, status: :unprocessable_entity
16    end
17  end
18  
19  def old_action
20    # Redirect to another URL
21    redirect_to 'https://example.com'
22    
23    # Redirect back to previous page
24    redirect_back(fallback_location: root_path)
25    
26    # Redirect with status
27    redirect_to @article, status: :see_other  # 303
28  end
29end

Flash Messages

Flash messages display information to users after redirects:

ruby
1class ArticlesController < ApplicationController
2  def create
3    @article = Article.new(article_params)
4    
5    if @article.save
6      flash[:notice] = 'Article created successfully!'
7      # Or shorthand:
8      redirect_to @article, notice: 'Article created!'
9    else
10      flash.now[:alert] = 'Please fix the errors'
11      render :new, status: :unprocessable_entity
12    end
13  end
14end

Display flash messages in layouts:

erb
1<!-- app/views/layouts/application.html.erb -->
2<body>
3  <% if flash[:notice] %>
4    <div class="alert alert-success"><%= flash[:notice] %></div>
5  <% end %>
6  
7  <% if flash[:alert] %>
8    <div class="alert alert-danger"><%= flash[:alert] %></div>
9  <% end %>
10  
11  <%= yield %>
12</body>

Filters (Callbacks)

Run code before, after, or around controller actions:

Before Action

ruby
1class ArticlesController < ApplicationController
2  before_action :authenticate_user!
3  before_action :set_article, only: [:show, :edit, :update, :destroy]
4  before_action :authorize_user, only: [:edit, :update, :destroy]
5  
6  def show
7    # @article is already set by before_action
8  end
9  
10  private
11  
12  def set_article
13    @article = Article.find(params[:id])
14  end
15  
16  def authorize_user
17    unless @article.user == current_user
18      redirect_to articles_path, alert: 'Not authorized'
19    end
20  end
21end

After and Around Actions

ruby
1class ArticlesController < ApplicationController
2  after_action :track_view, only: [:show]
3  around_action :log_request
4  
5  private
6  
7  def track_view
8    @article.increment!(:view_count)
9  end
10  
11  def log_request
12    Rails.logger.info "Starting request..."
13    yield  # Execute the action
14    Rails.logger.info "Finished request"
15  end
16end

Skip Filters

ruby
1class Admin::ArticlesController < ApplicationController
2  skip_before_action :authenticate_user!, only: [:public_page]
3end

Respond to Different Formats

ruby
1class ArticlesController < ApplicationController
2  def index
3    @articles = Article.all
4    
5    respond_to do |format|
6      format.html  # renders index.html.erb
7      format.json { render json: @articles }
8      format.xml { render xml: @articles }
9      format.csv { send_data @articles.to_csv }
10    end
11  end
12  
13  def show
14    @article = Article.find(params[:id])
15    
16    respond_to do |format|
17      format.html
18      format.json { render json: @article }
19      format.pdf do
20        pdf = ArticlePdf.new(@article)
21        send_data pdf.render, filename: "article.pdf"
22      end
23    end
24  end
25end

Sending Files

ruby
1class DownloadsController < ApplicationController
2  def show
3    # Send a file
4    send_file Rails.root.join('files', 'document.pdf'),
5              filename: 'document.pdf',
6              type: 'application/pdf',
7              disposition: 'attachment'  # or 'inline' to display
8    
9    # Send data directly
10    send_data generate_pdf,
11              filename: 'report.pdf',
12              type: 'application/pdf'
13  end
14end

Streaming

ruby
1class ReportsController < ApplicationController
2  def large_report
3    # Stream large data
4    self.response.headers['Content-Type'] = 'text/csv'
5    self.response.headers['Content-Disposition'] = 'attachment; filename="report.csv"'
6    
7    self.response_body = Enumerator.new do |yielder|
8      Article.find_each do |article|
9        yielder << article.to_csv_row
10      end
11    end
12  end
13end

Controllers are the orchestrators of your Rails app - keep them clean and focused!