[Project A] Devlog #2: Testing Role Apis

Before we begin

If you want to get up to speed on what this project is and where it all started, kindly read the first article in this series. I will link it somewhere in this post. With that out of the way, let's get started

[Project A] Devlog #1: Testing User Apis


Today was a very uneventful day. Thanks for reading.


Jokes aside, I didn't do much today because I was preoccupied with something I had to do in Project D (I will write about it as well). There are a few endpoints I need to create for the user controller, but I decided to test out the other apis that were generated for me by Phoenix. This was to see if I needed to make any changes. It turns out I needed to make some changes, but they weren't any major changes.

First off, I added the Guardian module to the controllers I wanted to test. This was to ensure that only authenticated users could access those resources. (The foundation for this was laid out in devlog 1)

After making sure that Guardian was properly checking that the user was authenticated, I moved on to testing the Role Apis. The Role struct is very simple, it just has two properties (:name, :code). This implementation may change in the future, but this is sufficient for now.

With my newfound understanding of how the contexts work and having done some work manually creating the User "context", I now had a much clearer picture of what to do in any of the files that I needed to work on.

I fired up the server using `iex` and started testing in Insomnia. For the most part, the code generated by phoenix when I created the context worked pretty well. There were only a few places where I needed to make a few changes.

I had to make a slight tweak to the update/create methods because what was generated wasn't really suited to what I wanted to do.

I'll demonstrate with the update code. Phoenix generated the code below for me.

def update(conn, %{"id" => id, "role" => role_params}) do
    role = Identity.get_role!(id)

    with {:ok, %Role{} = role} <- Identity.update_role(role, role_params) do
      render(conn, "show.json", role: role)
    end
  end

The idea here is that the id will be pattern-matched from the request and then used to get the role from the database. The role object would then also be pattern-matched from the request and that would be used to update the data. This method currently doesn't have any proper error handling, but that will be handled later (I'm still getting used to elixir and its "nature").

When I am sending my data from insomnia, with this method as it is now, it assumes that I will be passing the data like below

{
"role": {
        ...rest of role
    }
}

However, I don't need to nest this since it is the only data I will be sending. So my data looks like this.

{
...role properties
}

The change I needed to make to the method to get this working the way I wanted was fairly straightforward. This is what it looks like now

def update(conn, params) do
    role = Identity.get_role!(params["id"])

    with {:ok, %Role{} = role} <- Identity.update_role(role, params) do
      render(conn, "show.json", role: role)
    end
  end

With this new method, I get the id from the request and pass the params to the method responsible for handling the role update. It will handle the pattern-matching and whatever else needs to be handled.

I also added a method for signup so that I could have new users sign up. This led to a situation where I needed to handle errors and display the validation messages to the user. Again, this was pretty straightforward until I got an error about Jason not being able to parse the data so I had to massage the data a bit. After doing the massaging, I ended up with a situation where a field that I didn't want to be exposed from the changeset was present in the errors (since it is being validated). I found the Enum.reject method with the help of chatgpt and used that to remove the field that I didn't want present in the response. This is the method that formats the errors from the changeset.

defp format_errors(errors) do
    errors
    |> Enum.reject(fn {field, _} -> field == :hidden_field end)
    |> Enum.into(%{}, fn {field, {message, _values}} ->
      {field, message}
    end)
  end

What this method essentially does is, it takes the errors that I get from the changeset, removes the field I want hidden, and then creates a new tuple that contains the field that has the error and the error message so that it can be displayed on the frontend. By the way, if you are wondering what the changeset is, you can find more information about it here.

As I noted earlier, this was a very uneventful day. I will continue to do some more testing tomorrow and the days after. I need to explore file upload and batch processing, but that is a matter for another time.

Until next time, I bid you adieu.