Writing a Blog Engine in Phoenix and Elixir: Errata 1

Introduction

Brandon Richey
7 min readNov 9, 2015

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

(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 message regarding the role

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 if it’s not already there. Then, we’ll modify the new and edit actions:

The new action in web/controllers/user_controller.ex
The edit action in web/controllers/user_controller.ex
The create action in web/controllers/user_controller.ex
The update action in web/controllers/user_controller.ex

Notice for all of these selected all of the roles via Repo.all(Role) and added those to the list of assigns we sent out to the view (including in our render statements in the error cases).

We will also need to implement a select box, so let’s take a look at the documentation for selects using the Phoenix.Html form helpers (taken from https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html#select/4):

select(form, field, values, opts \\ [])
Generates a select tag with the given values.

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:

web/views/user_view.ex

We added a roles_for_select function that just takes in a collection of roles. Let’s explore what this function does line-by-line. We start off with our collection and then pipe it into the next line:

Enum.map(&["#{&1.name}": &1.id])

Again, remember that &/&1 is shorthand syntax for an anonymous function, so if we threw away the pipe operation and the shorthand, we would see this function rewritten as:

Enum.map(roles, fn role -> ["#{role.name}": role.id] end)

We’re running the map operation to return us a list of smaller keyword lists where the name of the role is the key and the id of the role is the value.

Given a particular starting value for roles of:

roles = [%Role{name: "Admin Role", id: 1}, %Role{name: "User Role", id: 2}]

This map call would return:

[["Admin Role": 1], ["User Role": 2]]

Which we then pipe into the last call, List.flatten which compresses this down to a nice handy list instead of a list of lists. So our end result is:

["Admin Role": 1, "User Role": 2]

Which just happens to be the format the select form helper is expecting! We can’t pat ourselves on the backs just yet; we still need to modify the templates for web/templates/user/new.html.eex and web/templates/user/edit.html.eex:

Adding the reference to @roles in our form render call

The above is just web/templates/user/new.html.eex, but you’ll want to do the same thing in edit. 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:

<div class=”form-group”>
<%= label f, :role_id, “Role”, class: “control-label” %>
<%= select f, :role_id, roles_for_select(@roles), class: “form-control” %>
</div>

And now, if you try to add a new user or edit an existing user, you’ll be able to assign a role to that person! That’s one bug off our list!

Running our seeds multiple times duplicates data

Right now, if we run our seeds multiple times we’ll end up erroneously duplicating data, which is no good. Let’s implement a few helper find_or_create anonymous functions:

Implementing some find_or_create functions and using them appropriately

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:

> mix run priv/repo/seeds.exs
[debug] SELECT r0.”id”, r0.”name”, r0.”admin”, r0.”inserted_at”, r0.”updated_at” FROM “roles” AS r0 WHERE ((r0.”name” = $1) AND (r0.”admin” = $2)) [“User Role”, false] OK query=81.7ms queue=2.8ms
[debug] BEGIN [] OK query=0.2ms
[debug] INSERT INTO “roles” (“admin”, “inserted_at”, “name”, “updated_at”) VALUES ($1, $2, $3, $4) RETURNING “id” [false, {{2015, 11, 6}, {19, 35, 49, 0}}, “User Role”, {{2015, 11, 6}, {19, 35, 49, 0}}] OK query=0.8ms
[debug] COMMIT [] OK query=0.4ms
[debug] SELECT r0.”id”, r0.”name”, r0.”admin”, r0.”inserted_at”, r0.”updated_at” FROM “roles” AS r0 WHERE ((r0.”name” = $1) AND (r0.”admin” = $2)) [“Admin Role”, true] OK query=0.4ms
[debug] BEGIN [] OK query=0.2ms
[debug] INSERT INTO “roles” (“admin”, “inserted_at”, “name”, “updated_at”) VALUES ($1, $2, $3, $4) RETURNING “id” [true, {{2015, 11, 6}, {19, 35, 49, 0}}, “Admin Role”, {{2015, 11, 6}, {19, 35, 49, 0}}] OK query=0.4ms
[debug] COMMIT [] OK query=0.3ms
[debug] SELECT u0.”id”, u0.”username”, u0.”email”, u0.”password_digest”, u0.”role_id”, u0.”inserted_at”, u0.”updated_at” FROM “users” AS u0 WHERE ((u0.”username” = $1) AND (u0.”email” = $2)) [“admin”, “admin@test.com”] OK query=0.7ms
[debug] BEGIN [] OK query=0.3ms
[debug] INSERT INTO “users” (“email”, “inserted_at”, “password_digest”, “role_id”, “updated_at”, “username”) VALUES ($1, $2, $3, $4, $5, $6) RETURNING “id” [“admin@test.com”, {{2015, 11, 6}, {19, 35, 49, 0}}, “$2b$12$.MuPBUVe/7/9HSOsccJYUOAD5IKEB77Pgz2oTJ/UvTvWYwAGn/L.i”, 2, {{2015, 11, 6}, {19, 35, 49, 0}}, “admin”] OK query=1.2ms
[debug] COMMIT [] OK query=1.1ms

The first time we run it, see a bunch of insert statements! Fantastic! Just to be totally sure it’s all working, let’s run it one more time and verify that we don’t see any inserts:

> mix run priv/repo/seeds.exs
Role: User Role already exists, skipping
[debug] SELECT r0.”id”, r0.”name”, r0.”admin”, r0.”inserted_at”, r0.”updated_at” FROM “roles” AS r0 WHERE ((r0.”name” = $1) AND (r0.”admin” = $2)) [“User Role”, false] OK query=104.8ms queue=3.6ms
Role: Admin Role already exists, skipping
[debug] SELECT r0.”id”, r0.”name”, r0.”admin”, r0.”inserted_at”, r0.”updated_at” FROM “roles” AS r0 WHERE ((r0.”name” = $1) AND (r0.”admin” = $2)) [“Admin Role”, true] OK query=0.6ms
User: admin already exists, skipping
[debug] SELECT u0.”id”, u0.”username”, u0.”email”, u0.”password_digest”, u0.”role_id”, u0.”inserted_at”, u0.”updated_at” FROM “users” AS u0 WHERE ((u0.”username” = $1) AND (u0.”email” = $2)) [“admin”, “admin@test.com”] OK query=0.8ms

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

Updating all the things

The first thing we’ll do is figure out which of our packages is out of date. If you run the command:

> mix hex.outdated
Outdated packages:
* comeonin (1.4.0 > 1.2.2)

You’ll get some output like the above telling you what is out of date and what isn’t. Let’s update everything that we can! We’ll run the next command:

mix deps.update --all

Which should work just fine and update anything within the confines of what we’ve specified in our mix.exs file. Finally, run:

mix compile

And everything should compile nicely, and we’re now running on the latest versions of everything! We’ll finish up by running our tests to make sure we haven’t found anything weird (via mix test). In my case, everything was green, so if you’ve been following along I hope yours is as well!

The newest versions of our major libraries are: Comeonin: 1.4.0, Ecto: 1.0.6

Everything should now be updated, passing tests, and everything. The branch to use on the repo to see the summary of work done is under the “errata_1” branch.

--

--