Recently I launched my first Phoenix app Elixir Match and along the way I found a handful of situations that didn't offer any useful search results. I've since decided to pull off an item from my backlog and blog about it weekly to help fill the void and push something useful to the top for anyone who might follow in my footsteps.
The problem
Today I'll be showing how to write a custom validation for Ecto 3 and Phoenix 1.4. In my specific example the signup form required an "invite code" that players would use to create an account. I wanted an excuse to learn more about Ecto so I decided to write this logic as part of the changeset. The unit tests below shows how this changeset will function when the invite code is both correct and incorrect.
defmodule Match.UsersTest do
use Match.DataCase, async: true
alias Match.User
@id "A4E3400CF711E76BBD86C57CA"
@valid_attrs %{id: @id, username: "toran", password: "abcd1234"}
test "changeset is fine with correct invite code" do
attrs = @valid_attrs |> Map.put(:invite, "elixir2019")
changeset = User.changeset(%User{}, attrs)
assert changeset.valid?
assert Map.get(changeset, :errors) == []
end
test "changeset is invalid if invite is wrong" do
attrs = @valid_attrs |> Map.put(:invite, "foo")
changeset = User.changeset(%User{}, attrs)
refute changeset.valid?
assert Map.get(changeset, :errors) == [invite: {"invalid invite code", []}]
end
end
Reading the documentation combined with a little trial and error helped me find a solution that wasn't all that different than what I found looking at previous versions of Elixir and Ecto but enough that I wanted to blog about it for anyone else scratching their head like I was.
defmodule Match.User do
use Ecto.Schema
import Ecto.Changeset
def changeset(user, params \\ %{}) do
user
|> cast(params, [:id, :username, :password, :icon, :invite, :hash])
|> validate_required([:username, :password])
|> validate_invite
end
defp validate_invite(%Ecto.Changeset{} = changeset) do
value = Map.get(changeset.changes, :invite)
case value == "elixir2019" do
true -> changeset
false -> add_error(changeset, :invite, "invalid invite code")
end
end
end
You can find the full source code for this custom validation on github