In Ecto I can easily do this:
Repo.get!(Kaderi.Forum, params["id"])
To look up a record by primary key, which by default is id
.
However I'm looking at implementing pretty URLs with a slug instead of an ID. I have a slug
field in my model and I can easily use it to generate URLs in Phoenix like so:
defmodule Kaderi.Forum do
use Kaderi.Web, :model
@derive {Phoenix.Param, key: :slug}
...
But there doesn't seem to be a simple way of automatically looking up records by the slug
field.
I can do something like the following:
Repo.get_by!(Kaderi.Forum, slug: params["id"])
But it seems like there should be some nice way of configuring it in the model, so that I can automatically generate URLs to, and then lookup records by slugs without having to touch the controller. If I change how I generate pretty URLs in the future, I shouldn't have to go update the controller again.
Is there some neat Ecto/Phoenix trick I'm missing to do this easily?
You can do this if your slug includes the id (as in 3-my-page
instead of just my-page
.)
This is an example from the Ecto.Type documentation copied here for convenience:
defmodule Permalink do
@behaviour Ecto.Type
def type, do: :integer
# Provide our own casting rules.
def cast(string) when is_binary(string) do
case Integer.parse(string) do
{int, _} -> {:ok, int}
:error -> :error
end
end
# We should still accept integers
def cast(integer) when is_integer(integer), do: {:ok, integer}
# Everything else is a failure though
def cast(_), do: :error
# When loading data from the database, we are guaranteed to
# receive an integer (as databases are strict) and we will
# just return it to be stored in the model struct.
def load(integer) when is_integer(integer), do: {:ok, integer}
# When dumping data to the database, we *expect* an integer
# but any value could be inserted into the struct, so we need
# guard against them.
def dump(integer) when is_integer(integer), do: {:ok, integer}
def dump(_), do: :error
end
You can then override the primary key for your schema:
defmodule Post do
use Ecto.Schema
@primary_key {:id, Permalink, autogenerate: true}
schema "posts" do
...
end
end
Now when you call Repo.get(Page, "3-my-page")
the string "3-my-page" will be cast to the integer 3 (which is the primary key for the model) and the page will be returned.
If you don't have the integer in your slug then there is currently no simple way to do this, you would be best to continue using Repo.get_by
.