Elixir Ecto URI Types

· grantwest13's blog


If you are storing URIs with Ecto you might get tired of converting back and forth to strings and trying to remmeber which function is supposed to accept what. We can simplify by creating an Ecto type that simply stores URIs as strings in the database. And we can write it so all of the changeset functions and query inputs can accept URI structs or strings.

 1defmodule Ecto.URIAsString do
 2  use Ecto.Type
 3
 4  def type, do: :string
 5
 6  def cast(%URI{} = uri), do: {:ok, uri}
 7  def cast(uri) when is_binary(uri), do: URI.new(uri)
 8
 9  def load(uri) do
10    case URI.new(uri) do
11      {:ok, uri} -> {:ok, uri}
12      _ -> :error
13    end
14  end
15
16  def dump(%URI{} = uri), do: {:ok, URI.to_string(uri)}
17
18  def dump(uri) when is_binary(uri) do
19    case URI.new(uri) do
20      {:ok, _} -> {:ok, uri}
21      _ -> :error
22    end
23  end
24end

Because our dump/1 and cast/1 functions both accept string and URI we can pass either to all of our interactions with ecto.

Setup your schema like this:

 1defmodule MyApp.Products.Product do
 2  use Ecto.Schema
 3  import Ecto.Changeset
 4
 5  schema "product" do
 6    field :uri, Ecto.URIAsString
 7  end
 8
 9  @doc false
10  def changeset(product, attrs) do
11    product
12    |> cast(attrs, [:uri])
13    |> validate_required([:uri])
14  end
15end

Now all of this just works:

1Product.changeset(%Product{}, uri: "http://hello.com")
2Product.changeset(%Product{}, uri: URI.new!("http://hello.com"))
3
4uris = ["http://one.com"] # or it could be a list of URI structs
5from(p in Product, where: p.uri not in ^uris)
6|> Repo.delete_all()

The Ecto.Type docs outline a more sophisticated way to store a URI as a map field for better querying capability. But if you don't need that for your use case and your just looking for a simple way to store you URIs in your database, just copy the code above. All code in this post is 0BSD licensed and is free to use in any way you wish. For something this simple it makes more sense to just copy it in rather than add a dependency.