Skip
Arish's avatar

35. Controller Filters and Callbacks


Controller Filters

Filters are methods that run before, after, or around controller actions. They help you share common logic across actions and keep your controllers DRY.

Before Action

Run code before an action executes:

ruby
1class ArticlesController < ApplicationController
2  before_action :authenticate_user!
3  before_action :set_article, only: [:show, :edit, :update, :destroy]
4  before_action :authorize_author, only: [:edit, :update, :destroy]
5  
6  def show
7    # @article is already set
8  end
9  
10  def edit
11    # @article is set and user is authorized
12  end
13  
14  def update
15    if @article.update(article_params)
16      redirect_to @article
17    else
18      render :edit
19    end
20  end
21  
22  private
23  
24  def set_article
25    @article = Article.find(params[:id])
26  end
27  
28  def authorize_author
29    unless @article.user == current_user
30      redirect_to articles_path, alert: "Not authorized"
31    end
32  end
33end

Filter Options

ruby
1class ArticlesController < ApplicationController
2  # Only for specific actions
3  before_action :authenticate_user!, only: [:new, :create, :edit, :update, :destroy]
4  
5  # Except for specific actions
6  before_action :authenticate_user!, except: [:index, :show]
7  
8  # With conditions
9  before_action :check_premium, if: :premium_content?
10  before_action :log_request, unless: :admin_user?
11  
12  # With lambda
13  before_action :track_view, if: -> { request.format.html? }
14  
15  private
16  
17  def premium_content?
18    @article&.premium?
19  end
20  
21  def admin_user?
22    current_user&.admin?
23  end
24end

After Action

Run code after an action executes:

ruby
1class ArticlesController < ApplicationController
2  after_action :track_view, only: [:show]
3  after_action :log_response, if: -> { Rails.env.development? }
4  
5  def show
6    @article = Article.find(params[:id])
7  end
8  
9  private
10  
11  def track_view
12    @article.increment!(:views_count)
13  end
14  
15  def log_response
16    Rails.logger.info "Response: #{response.status}"
17  end
18end

Around Action

Wrap an action with before and after logic:

ruby
1class ArticlesController < ApplicationController
2  around_action :with_timing
3  around_action :catch_exceptions
4  
5  def index
6    @articles = Article.all
7  end
8  
9  private
10  
11  def with_timing
12    start = Time.current
13    yield  # Execute the action
14    duration = Time.current - start
15    Rails.logger.info "Action took #{duration}s"
16  end
17  
18  def catch_exceptions
19    yield
20  rescue StandardError => e
21    Rails.logger.error "Error: #{e.message}"
22    redirect_to root_path, alert: "Something went wrong"
23  end
24end

Skip Filters

Override parent controller filters:

ruby
1class ApplicationController < ActionController::Base
2  before_action :authenticate_user!
3end
4
5class PublicController < ApplicationController
6  skip_before_action :authenticate_user!
7end
8
9class ArticlesController < ApplicationController
10  skip_before_action :authenticate_user!, only: [:index, :show]
11end

Prepend and Append

Control the order of filters:

ruby
1class ApplicationController < ActionController::Base
2  before_action :set_locale
3  before_action :authenticate_user!
4end
5
6class ArticlesController < ApplicationController
7  # Runs BEFORE parent filters
8  prepend_before_action :check_maintenance_mode
9  
10  # Runs AFTER parent filters (default behavior)
11  before_action :set_article
12  # Same as: append_before_action :set_article
13end

Common Filter Patterns

Authentication

ruby
1class ApplicationController < ActionController::Base
2  before_action :authenticate_user!
3  
4  private
5  
6  def authenticate_user!
7    unless current_user
8      store_location
9      redirect_to login_path, alert: "Please log in"
10    end
11  end
12  
13  def current_user
14    @current_user ||= User.find_by(id: session[:user_id])
15  end
16  
17  def store_location
18    session[:return_to] = request.fullpath if request.get?
19  end
20end

Authorization

ruby
1class ArticlesController < ApplicationController
2  before_action :set_article, only: [:show, :edit, :update, :destroy]
3  before_action :require_author, only: [:edit, :update, :destroy]
4  
5  private
6  
7  def require_author
8    unless @article.user == current_user || current_user.admin?
9      redirect_to @article, alert: "You can't edit this article"
10    end
11  end
12end

Setting Instance Variables

ruby
1class ArticlesController < ApplicationController
2  before_action :set_article, only: [:show, :edit, :update, :destroy]
3  before_action :set_categories, only: [:new, :edit, :create, :update]
4  
5  private
6  
7  def set_article
8    @article = Article.find(params[:id])
9  rescue ActiveRecord::RecordNotFound
10    redirect_to articles_path, alert: "Article not found"
11  end
12  
13  def set_categories
14    @categories = Category.all
15  end
16end

Request Format Handling

ruby
1class ApiController < ApplicationController
2  before_action :set_default_format
3  before_action :authenticate_api_key!
4  
5  private
6  
7  def set_default_format
8    request.format = :json unless params[:format]
9  end
10  
11  def authenticate_api_key!
12    api_key = request.headers["X-API-Key"]
13    head :unauthorized unless valid_api_key?(api_key)
14  end
15end

Tracking and Analytics

ruby
1class ApplicationController < ActionController::Base
2  after_action :track_action
3  
4  private
5  
6  def track_action
7    Analytics.track(
8      user_id: current_user&.id,
9      action: "#{controller_name}##{action_name}",
10      path: request.path,
11      referrer: request.referrer
12    )
13  end
14end

Caching Headers

ruby
1class ArticlesController < ApplicationController
2  before_action :set_cache_headers, only: [:show]
3  
4  private
5  
6  def set_cache_headers
7    response.headers["Cache-Control"] = "public, max-age=3600"
8  end
9end

Filters keep your controller actions focused on their core responsibilities!