Quần Cam Blog

Refactor Controller - Extract LoggedIn-Controller

Sometimes your controllers actions require authentication and it makes your code look messy. This post is going to show you the idea of extracting them into LoggedInController

What is the idea?

I bet you have seen something like this

class ProductsController < ApplicationController
  before_filter :authenticate_user!, except: :index

  protected
  def authenticate_user!
    redirect_to user_login_url, notice: 'Hey man! Log in first please!' unless user_logged_in?
  end
end

These lines of code are to filter users who are not logged in yet, redirect them and give them a nice alert. But the problem here is to which the responsibility of user authentication should belong. It is obviously not the job of ProductsController, as the products controller should not know the knowledge of how to filter an user.

Just shift it to ApplicationController? - “No, please don’t”.

Why ApplicationController is not a good idea?

  • ApplicationController should not know how to authenticate an user.
  • Although we move authenticate_user! to ApplicationController, ProductsController should not know which actions should be filtered and how user is being redirected.

Extract ProductsController bases on authentication context

My idea is to split ProductsController into two controllers: LoggedIn::ProductsController and Public::ProductsController

class PublicController < ApplicationController; end

class LoggedInController < ApplicationController
  before_filter :authenticate_user!

  protected
  def authenticate_user!
    redirect_to user_login_url, notice: 'Hey man! Log in first please!' unless user_logged_in?
  end
end

And this is how your ProductsController looks like

# app/controllers/public/products_controller.rb
class Public::ProductsController < PublicController
  def index
    # Your code here
  end
end

# app/controllers/logged_in/products_controller.rb
class LoggedIn::ProductsController < LoggedInController
  def new
    # Your code here
  end
end

How to test it with RSpec

# spec/controllers/logged_in_controller_spec.rb
require 'rails_helper'

describe LoggedInController do
  # stub an action to the controller
  controller(LoggedInController) do
    def index
      render nothing: true
    end
  end

  describe '#authenticate_user!' do
    let(:user) { create(:user) }

    before { @request.env['devise.mapping'] = Devise.mappings[:user] }

    def do_request
      get :index
    end

    context 'authenticated user' do
      before { sign_in user }
      before { do_request }

      it { is_expected.to_not redirect_to new_user_session_path }
    end

    context 'public user' do
      before { do_request }

      it { is_expected.to redirect_to new_user_session_path }
    end
  end
end

NGUY HIỂM! KHU VỰC NHIỀU GIÓ!
Khuyến cáo giữ chặt bàn phím và lướt thật nhanh khi đi qua khu vực này.
Chức năng này hỗ trợ markdown và các thứ liên quan.

Bài viết cùng chủ đề

Euruko 2017 Notes

Vừa rồi mình đi Euruko 2017 ở Budapest, một số bài nói cũng khá thú vị nên mình sẽ note lại ở đây.

Six confusing features in Ruby

In this post I am trying to point out some Ruby features you might want to use with a lot of caution.

[DIY] Tự viết driver cho Redis trong 30 phút với Rust

Rust là một ngôn ngữ lập trình mới đang khá nổi, vậy ngại gì mà không thử làm một cái gì với nó, như viết driver chẳng hạn