128 lines
3.7 KiB
Elixir
128 lines
3.7 KiB
Elixir
|
defprotocol Phoenix.Param do
|
||
|
@moduledoc """
|
||
|
A protocol that converts data structures into URL parameters.
|
||
|
|
||
|
This protocol is used by URL helpers and other parts of the
|
||
|
Phoenix stack. For example, when you write:
|
||
|
|
||
|
user_path(conn, :edit, @user)
|
||
|
|
||
|
Phoenix knows how to extract the `:id` from `@user` thanks
|
||
|
to this protocol.
|
||
|
|
||
|
By default, Phoenix implements this protocol for integers, binaries, atoms,
|
||
|
and structs. For structs, a key `:id` is assumed, but you may provide a
|
||
|
specific implementation.
|
||
|
|
||
|
Nil values cannot be converted to param.
|
||
|
|
||
|
## Custom parameters
|
||
|
|
||
|
In order to customize the parameter for any struct,
|
||
|
one can simply implement this protocol.
|
||
|
|
||
|
However, for convenience, this protocol can also be
|
||
|
derivable. For example:
|
||
|
|
||
|
defmodule User do
|
||
|
@derive Phoenix.Param
|
||
|
defstruct [:id, :username]
|
||
|
end
|
||
|
|
||
|
By default, the derived implementation will also use
|
||
|
the `:id` key. In case the user does not contain an
|
||
|
`:id` key, the key can be specified with an option:
|
||
|
|
||
|
defmodule User do
|
||
|
@derive {Phoenix.Param, key: :username}
|
||
|
defstruct [:username]
|
||
|
end
|
||
|
|
||
|
will automatically use `:username` in URLs.
|
||
|
|
||
|
When using Ecto, you must call `@derive` before
|
||
|
your `schema` call:
|
||
|
|
||
|
@derive {Phoenix.Param, key: :username}
|
||
|
schema "users" do
|
||
|
|
||
|
"""
|
||
|
|
||
|
@fallback_to_any true
|
||
|
|
||
|
@spec to_param(term) :: String.t
|
||
|
def to_param(term)
|
||
|
end
|
||
|
|
||
|
defimpl Phoenix.Param, for: Integer do
|
||
|
def to_param(int), do: Integer.to_string(int)
|
||
|
end
|
||
|
|
||
|
defimpl Phoenix.Param, for: Float do
|
||
|
def to_param(float), do: Float.to_string(float)
|
||
|
end
|
||
|
|
||
|
defimpl Phoenix.Param, for: BitString do
|
||
|
def to_param(bin) when is_binary(bin), do: bin
|
||
|
end
|
||
|
|
||
|
defimpl Phoenix.Param, for: Atom do
|
||
|
def to_param(nil) do
|
||
|
raise ArgumentError, "cannot convert nil to param"
|
||
|
end
|
||
|
|
||
|
def to_param(atom) do
|
||
|
Atom.to_string(atom)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
defimpl Phoenix.Param, for: Map do
|
||
|
def to_param(map) do
|
||
|
raise ArgumentError,
|
||
|
"maps cannot be converted to_param. A struct was expected, got: #{inspect map}"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
defimpl Phoenix.Param, for: Any do
|
||
|
defmacro __deriving__(module, struct, options) do
|
||
|
key = Keyword.get(options, :key, :id)
|
||
|
|
||
|
unless Map.has_key?(struct, key) do
|
||
|
raise ArgumentError, "cannot derive Phoenix.Param for struct #{inspect module} " <>
|
||
|
"because it does not have key #{inspect key}. Please pass " <>
|
||
|
"the :key option when deriving"
|
||
|
end
|
||
|
|
||
|
quote do
|
||
|
defimpl Phoenix.Param, for: unquote(module) do
|
||
|
def to_param(%{unquote(key) => nil}) do
|
||
|
raise ArgumentError, "cannot convert #{inspect unquote(module)} to param, " <>
|
||
|
"key #{inspect unquote(key)} contains a nil value"
|
||
|
end
|
||
|
|
||
|
def to_param(%{unquote(key) => key}) when is_integer(key), do: Integer.to_string(key)
|
||
|
def to_param(%{unquote(key) => key}) when is_binary(key), do: key
|
||
|
def to_param(%{unquote(key) => key}), do: Phoenix.Param.to_param(key)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def to_param(%{id: nil}) do
|
||
|
raise ArgumentError, "cannot convert struct to param, key :id contains a nil value"
|
||
|
end
|
||
|
def to_param(%{id: id}) when is_integer(id), do: Integer.to_string(id)
|
||
|
def to_param(%{id: id}) when is_binary(id), do: id
|
||
|
def to_param(%{id: id}), do: Phoenix.Param.to_param(id)
|
||
|
|
||
|
def to_param(map) when is_map(map) do
|
||
|
raise ArgumentError,
|
||
|
"structs expect an :id key when converting to_param or a custom implementation " <>
|
||
|
"of the Phoenix.Param protocol (read Phoenix.Param docs for more information), " <>
|
||
|
"got: #{inspect map}"
|
||
|
end
|
||
|
|
||
|
def to_param(data) do
|
||
|
raise Protocol.UndefinedError, protocol: @protocol, value: data
|
||
|
end
|
||
|
end
|