Previous Post in this series

Current Versions

As of the time of writing this, the current versions of our applications are:

Elixir: v1.3.1

Phoenix: v1.2.0

Ecto: v2.0.2

Comeonin: v2.5.2

If you are reading this and these are not the latest, let me know and I’ll update this tutorial accordingly.

Where We Left Off

When we left off, we had just finished implementing the concept of roles into our models and created some test helpers to make our lives easier, but now we need to get into the tricky bits of implementing restrictions based on roles into our controllers. We’ll start off by creating a helper to give us a function to use in each of our controllers.

Creating a Role Checker Helper

The next thing we need to do is create a simple way to verify if the user creating the other users is indeed an admin. Create web/models/role_checker.ex and we’ll populate it with the following module:

In both tests we create a role and a user; in one we create an admin role and in the next we do not. Finally, we assert that the is_admin? function returns true for the admin user and false for the non-admin user. Because the RoleChecker’s is_admin? function requires you to supply the user, we can write very simple tests to guarantee our functionality. This is code that we can be confident about! Run these tests and verify your test suite is still green.

Restricting User Creation to Admins

In our User Controller, we never wrote any authorize_user plugs to restrict this, so we’re going to add them now. We will design this out quickly to make sure the actions we’re performing make sense. We’ll allow a user to edit, update, and delete their own accounts, but we’ll only allow admins to see the new user form or create new accounts.

Underneath the scrub_params line in web/controllers/user_controller.ex, add the following:

We create a user role, an admin role, a non-admin user, and an admin user, and then return all of that out to our tests to use in pattern matching. We also need a helper function to log in a user, so we’ll copy our login_user function from our Post Controller.

We’re adding a “@tag admin: true” line above our test to tag it as an “admin” test so that we can just run all of our admin tests instead of the full suite. We’ll run just this test with the following command:

The trouble here is we’re not passing a full user model to RoleChecker.is_admin?; instead, we’re passing the small subset of data that we’re storing in current_user from our Session Controller’s sign_in function. We’ll update that to include the role_id as well. I’ve added the modification in web/controllers/session_controller.ex below:

Allowing Admins to Modify All Posts

Thankfully, we’ve already done almost all of the work to make this last piece of admin functionality work as expected. We’re going to open up web/controllers/post_controller.ex and modify the authorize_user function to also use our RoleChecker.is_admin? helper function to see if the user is an admin. If they are, then we’re going to give them full control over modifying any user’s posts.

Right now, our blog engine is humming along but there are a few bugs, whether due to omissions or things I missed along the way, so let’s identify and address a few bugs. We’ll also upgrade the versions of dependencies to make sure that this is running on the latest and greatest of everything that it can!

Adding new users throws an error about missing roles

This was pointed out to me as an issue on the Pxblog github page (https://github.com/Diamond/pxblog) by nolotus (Thank you!). As of the part_3 branch, if you attempt to create a new user, it will fail due to the lack of a specified role (since we did make role_id required for new user creation). Let’s explore the problem, first, and then we’ll start implementing a fix for it. When we log in as an admin, and go to /users/new, after filling everything out we’ll get the following error:

Our failing user creation error message

Which makes sense. We require the user to enter username, email, password, and password_confirmation, but nothing about the role. Knowing this is the case, we’ll start by passing the list of possible roles to choose from to our controller.

We’ll start by passing in a list of roles to each of the actions that will need to be able to select them, which in our case means the new, create, edit, and update actions. First, throw an alias Pxblog.Role to the top of your User Controller (web/controllers/user_controller.ex) if it’s not already there. Then, we’ll modify the new, edit, create, and update actions:

select boxes, for the values argument, requires either a list or a keyword list, either in the form of [value, value, value] or [displayed: value, displayed: value]. In our case, we want to display the role name but have it carry the id value in our form submit. We can’t just blindly throw @roles in there because it doesn’t adhere to either format, so let’s write a function in our view that will make this simpler:

Finally, in web/templates/user/form.html.eex you’ll want to add in our new select box using our helper and roles assignment. We’ll want to add a select box that contains each of the roles that a user can get moved into. Add the following before our submit button:

The first thing to note is that we’re aliasing our Repo, Role, and User, and we’re also importing the from function from Ecto.Query to use the linq-style querying syntax. Next, we’ll look at the find_or_create_role anonymous function. The function itself just takes a role name and an admin flag as its arguments. Based on that, we then query with Repo.all for those criteria (note those ^ next to each variable in our where clause; we do not want to do any pattern matching or anything here) and toss that into a case statement. If we cannot find anything with Repo.all, we’ll get an empty list back, so if we get an empty list back, we’ll insert the role. Otherwise, we assume we’ve gotten some matching criteria back and we’ll acknowledge it already exists and move on with the rest of the seeds file. find_or_create_user does the same operations, but just looks for different criteria.

Finally, we call out each of these functions (note the . in between the function name and the arguments; this is required for anonymous function calls!). We need to reuse the admin role to create the admin user, so that’s why we’re not prefacing admin_role with an underscore. We may later decide to keep user_role or the admin user for later seeds, so I’ll leave that code in place but preface those with underscores. It keeps the seeds file looking nice and clean. Now that’s done and we’re ready to run our seeds:

Great! Everything is working and much safer! Plus, we got to have a bit of fun with writing our own utility functions for Ecto!

Errors about duplicate admin user on tests

Since we modified the seeds to create a new user, if you reset your test DB at any point you’ll start running into issues since you cannot create a user that already exists. There’s a simple (and temporary) way to fix this. Open up test/support/test_helper.ex and modify the create_user function:

Where Are We Now?

Now, we have green specs, we have users, posts, and roles. We’ve implemented sane functionality for restricting user sign-ups, modifying users and posts, and implemented some helpers to make our lives easier when writing code. In our next few posts, we’ll take some time out to add some cool new features to our blog engine!

I’m really excited to finally be bringing this project to the world! It’s written in the same style as my other tutorials where we will be building the scaffold of a full project from start to finish, even covering some of the trickier topics like file uploads, Twitter/Google OAuth logins, and APIs!