- Read Tutorial
- Watch Guide Video
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.