在 Elixir 类型规范的类型定义中指定一个字符串值



是否可以按如下方式定义类型:

defmodule Role do
  use Exnumerator, values: ["admin", "regular", "restricted"]
  @type t :: "admin" | "regular" | "restricted"
  @spec default() :: t
  def default() do
    "regular"
  end
end

为了更好地分析代码,例如:

@type valid_attributes :: %{optional(:email) => String.t,
                            optional(:password) => String.t,
                            optional(:role) => Role.t}
@spec changeset(User.t, valid_attributes) :: Ecto.Changeset.t
def changeset(%User{} = user, attrs = %{}) do
  # ...
end
# ...
User.changeset(%User{}, %{role: "superadmin"}) |> Repo.insert()

我知道我可以将这种类型定义为 @type t :: String.t ,但是,Dialyzer 不会抱怨使用不同的值(从应用的角度来看可能是(。

我在Typespecs的文档中没有看到有关此用例的任何提示,但也许我错过了一些东西。

无法以所述方式使用二进制值。但是,使用原子和 - 在我的例子中 - 自定义 Ecto 类型可以实现类似的行为:

defmodule Role do
  @behaviour Ecto.Type
  @type t :: :admin | :regular | :restricted
  @valid_binary_values ["admin", "regular", "restricter"]
  @spec default() :: t
  def default(), do: :regular
  @spec valid_values() :: list(t)
  def valid_values(), do: Enum.map(@valid_values, &String.to_existing_atom/1)
  @spec type() :: atom()
  def type(), do: :string
  @spec cast(term()) :: {:ok, atom()} | :error
  def cast(value) when is_atom(value), do: {:ok, value}
  def cast(value) when value in @valid_binary_values, do: {:ok, String.to_existing_atom(value)}
  def cast(_value), do: :error
  @spec load(String.t) :: {:ok, atom()}
  def load(value), do: {:ok, String.to_existing_atom(value)}
  @spec dump(term()) :: {:ok, String.t} | :error
  def dump(value) when is_atom(value), do: {:ok, Atom.to_string(value)}
  def dump(_), do: :error
end

它允许使用以下代码:

defmodule User do
  use Ecto.Schema
  import Ecto.Changeset
  @type t :: %User{}
  @type valid_attributes :: %{optional(:email) => String.t,
                              optional(:password) => String.t,
                              optional(:role) => Role.t}
  @derive {Poison.Encoder, only: [:email, :id, :role]}
  schema "users" do
    field :email, :string
    field :password, :string, virtual: true
    field :password_hash, :string
    field :role, Role, default: Role.default()
    timestamps()
  end
  @spec changeset(User.t, valid_attributes) :: Ecto.Changeset.t
  def changeset(%User{} = user \ %User{}, attrs = %{}) do
  # ...
end

这样,透析器将捕获无效用户的角色:

User.changeset(%User{}, %{role: :superadmin}) |> Repo.insert()

不幸的是,它强制在应用程序中使用原子代替字符串。如果我们已经有一个很大的代码库,或者我们需要大量可能的值(系统中原子的限制以及它们没有被垃圾回收的事实(,这可能会有问题。

相关内容

  • 没有找到相关文章

最新更新