Using BDD to Build a Permission Structure
In this guide we are going leverage BDD to start building out our permission structure.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

In this guide we are going leverage BDD to start building out our permission structure.

We'll start by opening the post_spec.rb file, located in spec/features and we'll update the a number of the specs, including adding a new one to the edit block. The full code that we'll implement in below:

# spec/features/post_spec.rb

require 'rails_helper'

describe 'navigate' do
  before do
    @user = FactoryGirl.create(:user)
    login_as(@user, :scope => :user)
  end

  describe 'index' do
    before do
      visit posts_path
    end

    it 'can be reached successfully' do
      expect(page.status_code).to eq(200)
    end

    it 'has a title of Posts' do
      expect(page).to have_content(/Posts/)
    end

    it 'has a list of posts' do
      post1 = FactoryGirl.build_stubbed(:post)
      post2 = FactoryGirl.build_stubbed(:second_post)
      visit posts_path
      expect(page).to have_content(/Rationale|content/)
    end
  end

  describe 'new' do
    it 'has a link from the homepage' do
      visit root_path

      click_link("new_post_from_nav")
      expect(page.status_code).to eq(200)
    end
  end

  describe 'delete' do
    it 'can be deleted' do
      @post = FactoryGirl.create(:post)
      visit posts_path

      click_link("delete_post_#{@post.id}_from_index")
      expect(page.status_code).to eq(200)
    end
  end

  describe 'creation' do
    before do
      visit new_post_path
    end

    it 'has a new form that can be reached' do
      expect(page.status_code).to eq(200)
    end

    it 'can be created from new form page' do
      fill_in 'post[date]', with: Date.today
      fill_in 'post[rationale]', with: "Some rationale"
      click_on "Save"

      expect(page).to have_content("Some rationale")
    end

    it 'will have a user associated it' do
      fill_in 'post[date]', with: Date.today
      fill_in 'post[rationale]', with: "User Association"
      click_on "Save"

      expect(User.last.posts.last.rationale).to eq("User Association")
    end
  end

  describe 'edit' do
    before do
      @edit_user = User.create(first_name: "asdf", last_name: "asdf", email: "asdfasdf@asdf.com", password: "asdfasdf", password_confirmation: "asdfasdf")
      login_as(@edit_user, :scope => :user)
      @edit_post = Post.create(date: Date.today, rationale: "asdf", user_id: @edit_user.id)
    end

    it 'can be edited' do
      visit edit_post_path(@edit_post)

      fill_in 'post[date]', with: Date.today
      fill_in 'post[rationale]', with: "Edited content"
      click_on "Save"

      expect(page).to have_content("Edited content")
    end

    it 'cannot be edited by a non authorized user' do
      logout(:user)
      non_authorized_user = FactoryGirl.create(:non_authorized_user)
      login_as(non_authorized_user, :scope => :user)

      visit edit_post_path(@edit_post)

      expect(current_path).to eq(root_path)
    end
  end
end

In these test cases we are verifying that non-authorized users cannot access pages we don't want to give them access to. We are going to start by creating a non-authorized user using FactoryGirl. Before we run this, go to users.rb where we have our FactoryGirl implementation for users. Here, let's add another type of user called non-authorized user. The factory code looks like this:

# spec/factories/users.rb

factory :non_authorized_user, class: "User" do
  first_name 'Non'
  last_name 'Authorized'
  email { generate :email }
  password "asdfasdf"
  password_confirmation "asdfasdf"
end

Now let's look back at our post_spec.rb. Here, we'll logout the authorized user, create the new non-authorized user and log that user in. From there we want the test to visit the edit page and expect the current page to be equal to the root_path.

In analyzing the file you'll see that we've also altered the sign in order of a number of the calls to work with the new permission structure. Before our code was allowing any/all signed in users to access the application. This code takes the new authorization rules into account and creates updated expectations.

The only piece of implementation code we'll need to add to get the tests passing is to redirect non authorized users, and we can do that in the ApplicationController:

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  include Pundit

  protect_from_forgery with: :exception
  before_action :authenticate_user!

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  private

  def user_not_authorized
    flash[:alert] = "You are not authorized to perform this action."
    redirect_to(root_path)
  end
end

Here we're leveraging the Pundit::NotAuthorizedError module provided by Pundit that will catch non authorized users and call the user_not_authorized method. In our case I'm rendering an alert and redirecting the user to the homepage.

We flipped the BDD workflow upside down and we're adding tests that should pass. This is different than the traditional Red, Green, Refactor TDD process. I did this because whenever I'm implementing a new library, such as Pundit, I want to first focus on getting it working, and then I build the tests.

In the video that accompanies this guide I struggled with the code configuration. In this guide I skipped ahead to showing the working configuration right away since it would be confusing to follow a number of failed attempts to properly structure the tests. If you want to see how I worked through the mistakes I recommend you watch the screencast.

If you run rspec, all of our tests are passing.

Resources