Guide to Implementing Petergate in a Rails 5 Application
In this guide we'll walk through a step by step guide for implementing authorization into our application by fully integrating the Petergate gem. We'll also analyze what steps to take in order to get Petergate and Devise to work with the null object pattern for non logged in users.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

Looking at our last task, the last thing we need to do is implement code for authorization. We have everything that we need installed and configured in order to get this working. We need to implement the various methods and the tools provided from the petergate gem.

Now I went through this originally and I wanted to craft the best way to do it and found that there is a little bit of a conflict with the current setup that we have, not with the rest of the app but specifically with the null object pattern.

Remember how we built the concept of a guest user, (in the current_user_concern.rb file) this works in most cases. However the petergate gem has some reliance and some connectivity to using active record type models. When I was going through this I ran into some pretty interesting little bugs. I wanted to let you know in case you ever run into this in the future, if you ever have some type of a gem that has a strongly opinionated kind of approach where it expects to be communicating with a specific type of object. Petergate expects to be able to communicate with active record type models

If we need something more flexible then openstruct, what we can actually implement something that mimics a true user. This mimics a user in terms of data but it doesn't have the various components such as being able to have the all the methods built in with active record.

If you click on models and click new file, we're going to save this file as guest_user.rb this guest user is in our models file and it is going to be a user even though we're not going to have a table for it. It is going to be user and it's going to inherit from our user class. Right here (user.rb) user class inherits from ApplicationRecord, it's a devise user so it has all of the devise methods built into it.

Ww can say (in guest_user.rb):

class GuestUser < User
  attr_accessor :name, :first_name, :last_name, :email
end

Inside of this all we have to do is put some after accessor methods. If you are new to the Ruby side of the world, a "attr_accessor" provides the getters and setters. It provides the ability for this class to have data.

Now we can come back to our current user and create a new guest user

def guest_user
  guest = GuestUser.new
  guest.name = "Guest User"
  guest.first_name = "Guest"
  guest.last_name = "User"
  guest.email = "guest@example.com"
  guest
end

Let's make sure that this is working, switching back to Google Chrome, it looks like everything on the site still works. When you perform a refactor, ensure that you have not lost any functionality or added any new bugs into the system. Now that our guest user is a true user that inherits from devise, we can actually implement petergate.

One thing, let's update the applications_helper.rb so guest users don't see the logout link. Since they are not users, they shouldn't see that link

module ApplicationHelper
  def login_helper
    if current_user.is_a?(GuestUser)
      (link_to "Register", new_user_registration_path) +
      "<br>".html_safe +
      (link_to "Login", new_user_session_path)
    else
      link_to "Logout", destroy_user_session_path, method: :delete
    end
  end

Come back and hit refresh, you can see that this is working.

Let's talk about what we need to do to implement petergate. Make sure to follow the instructions from the rubygem.org documentation.

We are going to go to the user.rb file and put in what we want a site admin to be petergate(roles: [:site_admin], multiple: false)

Now in the blogs controller, near the top add access all: [:show, :index], user: {except: [:destroy, :new, :create, :update, :edit]}, site_admin: :all

There are three components here,

  • all means we want everyone to access the show and index.
  • user can comment and see blogs. We don't want them to be able to delete/create blogs
  • site admin can do basically everything

I'm going to open up the rails console and we need to edit our users. Type User.first this will show the current user. Type User.first.update!(roles: "site_admin") and hit return, this worked. Retype User.first and you can see this user is now a site_admin.

Let's start the rails server up and see what our user has the ability to do.

I'm going to come back to the site and refresh, everything's working and we can create new blogs.

If I click log out, now I'm a guest user, if I go to new, it says permission denied.

If I come and sign in as a regular user, you can see that I cannot create a new blog.

If I scroll all the way down, I have the link to create a new blog. If I click that it says permission denied, however, it's a bad practice to have links to things that users are not allowed to do.

In the documentation you can see there is an example of how to hide this link. Open index.html.erb and update the last line of code to

<%= link_to 'New Blog', new_blog_path if logged_in(:site_admin) %>

Come back to the site and hit refresh, you can see that link is gone.

We also need to do this in the blog partial, update the edit and delete actions to this

  <td><%= link_to 'Edit', edit_blog_path(blog) if logged_in?(:site_admin) %></td> 
  <td><%= link_to 'Delete Post', blog, method: :delete, date: { confirm: 'Are you sure?' } if logged_in?(:site_admin) %></td> 

Come back and hit refresh, the links should be gone now.

We do need to make some changes to our portfolio controller, add this line of code near the top

access all: [:show, :index, :angular], user: {except: [:destroy, :new, :create, :update, :edit]}, site_admin: :all

Click save now if you come to portfolios, you can see that you cannot create a portfolio item if you aren't signed in as site admin.

Update show.html.erb (for blogs) and update the edit action to this

<%= link_to 'Edit', edit_blog_path(@blog) if logged_in?(:site_admin) %>

Update index.html.erb

<%= link_to "Create New Item", new_portfolio_url if logged_in?(:site_admin) %>

Update angular.html.erb create new item, edit and delete actions to

<%= link_to "Create New Item", new_portfolio_url if logged_in?(:site_admin) %>
<%= link_to "Edit", edit_portfolio_path(portfolio_item) if logged_in?(:site_admin) %>
<%= like_to 'Delete Portfolio Item', portfolio_path(portfolio_item), method: :delete, data: { confirm: 'Are you sure?' } if logged_in?(:site_admin) %>

Update the _portfolio_item.html.erb edit and delete actions

<%= link_to "Edit", edit_portfolio_path(portfolio_item) if logged_in?(:site_admin) %>
<%= link_to "Delete Portfolio Item", portfolio_path(portfolio_item), method: :delete, data: { confirm: 'Are you sure?' } if logged_in?(:site_admin) %>

Let's test this out, if you are logged in as a user you should not see those links. If you are logged in as a site admin you should have access to those links.

You have now fully implemented a full authorization engine.

  • git status
  • git add .
  • git commit -m "'Implemented authorization engine for portfolio items and blogs"
  • git push origin authorization

I'm going to merge locally.

  • git checkout master
  • git merge authorization
  • git push

Let's close out the tasks on pivotal tracker.

We now have a full authorization engine live. In the next section we are finally getting get into how we can start styling our application.

Resources