zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

enum.ex (6933B)


      1 defmodule Ecto.Enum do
      2   @moduledoc """
      3   A custom type that maps atoms to strings or integers.
      4 
      5   `Ecto.Enum` must be used whenever you want to keep atom values in a field.
      6   Since atoms cannot be persisted to the database, `Ecto.Enum` converts them
      7   to a string or an integer when writing to the database and converts them back
      8   to atoms when loading data. It can be used in your schemas as follows:
      9 
     10       # Stored as strings
     11       field :status, Ecto.Enum, values: [:foo, :bar, :baz]
     12 
     13   or
     14 
     15       # Stored as integers
     16       field :status, Ecto.Enum, values: [foo: 1, bar: 2, baz: 5]
     17 
     18   Therefore, the type to be used in your migrations for enum fields depend
     19   on the choice above. For the cases above, one would do, respectively:
     20 
     21       add :status, :string
     22 
     23   or
     24 
     25       add :status, :integer
     26 
     27   Some databases also support enum types, which you could use in combination
     28   with the above.
     29 
     30   Composite types, such as `:array`, are also supported which allow selecting
     31   multiple values per record:
     32 
     33       field :roles, {:array, Ecto.Enum}, values: [:author, :editor, :admin]
     34 
     35   Overall, `:values` must be a list of atoms or a keyword list. Values will be
     36   cast to atoms safely and only if the atom exists in the list (otherwise an
     37   error will be raised). Attempting to load any string/integer not represented
     38   by an atom in the list will be invalid.
     39 
     40   The helper function `mappings/2` returns the mappings for a given schema and
     41   field, which can be used in places like form drop-downs. For example, given
     42   the following schema:
     43 
     44       defmodule EnumSchema do
     45         use Ecto.Schema
     46 
     47         schema "my_schema" do
     48           field :my_enum, Ecto.Enum, values: [:foo, :bar, :baz]
     49         end
     50       end
     51 
     52   You can call `mappings/2` like this:
     53 
     54       Ecto.Enum.mappings(EnumSchema, :my_enum)
     55       #=> [foo: "foo", bar: "bar", baz: "baz"]
     56 
     57   If you want the values only, you can use `Ecto.Enum.values/2`, and if you want
     58   the dump values only, you can use `Ecto.Enum.dump_values/2`.
     59 
     60   ## Embeds
     61 
     62   `Ecto.Enum` allows to customize how fields are dumped within embeds through the
     63   `:embed_as` option. Two alternatives are supported: `:values`, which will save
     64   the enum keys (and not their respective mapping), and `:dumped`, which will save
     65   the dumped value. The default is `:values`. For example, assuming the following
     66   schema:
     67 
     68       defmodule EnumSchema do
     69         use Ecto.Schema
     70 
     71         schema "my_schema" do
     72           embeds_one :embed, Embed do
     73             field :embed_as_values, Ecto.Enum, values: [foo: 1, bar: 2], embed_as: :values
     74             field :embed_as_dump, Ecto.Enum, values: [foo: 1, bar: 2], embed_as: :dump
     75           end
     76         end
     77       end
     78 
     79   The `:embed_as_values` field value will save `:foo | :bar`, while the
     80   `:embed_as_dump` field value will save as `1 | 2`.
     81   """
     82 
     83   use Ecto.ParameterizedType
     84 
     85   @impl true
     86   def type(params), do: params.type
     87 
     88   @impl true
     89   def init(opts) do
     90     values = opts[:values]
     91 
     92     {type, mappings} =
     93       cond do
     94         is_list(values) and Enum.all?(values, &is_atom/1) ->
     95           validate_unique!(values)
     96           {:string, Enum.map(values, fn atom -> {atom, to_string(atom)} end)}
     97 
     98         type = Keyword.keyword?(values) and infer_type(Keyword.values(values)) ->
     99           validate_unique!(Keyword.keys(values))
    100           validate_unique!(Keyword.values(values))
    101           {type, values}
    102 
    103         true ->
    104           raise ArgumentError, """
    105           Ecto.Enum types must have a values option specified as a list of atoms or a
    106           keyword list with a mapping from atoms to either integer or string values.
    107 
    108           For example:
    109 
    110               field :my_field, Ecto.Enum, values: [:foo, :bar]
    111 
    112           or
    113 
    114               field :my_field, Ecto.Enum, values: [foo: 1, bar: 2, baz: 5]
    115           """
    116       end
    117 
    118     on_load = Map.new(mappings, fn {key, val} -> {val, key} end)
    119     on_dump = Map.new(mappings)
    120     on_cast = Map.new(mappings, fn {key, _} -> {Atom.to_string(key), key} end)
    121 
    122     embed_as =
    123       case Keyword.get(opts, :embed_as, :values) do
    124         :values ->
    125           :self
    126 
    127         :dumped ->
    128           :dump
    129 
    130         other ->
    131           raise ArgumentError, """
    132           the `:embed_as` option for `Ecto.Enum` accepts either `:values` or `:dumped`,
    133           received: `#{inspect(other)}`
    134           """
    135       end
    136 
    137     %{
    138       on_load: on_load,
    139       on_dump: on_dump,
    140       on_cast: on_cast,
    141       mappings: mappings,
    142       embed_as: embed_as,
    143       type: type
    144     }
    145   end
    146 
    147   defp validate_unique!(values) do
    148     if length(Enum.uniq(values)) != length(values) do
    149       raise ArgumentError, """
    150       Ecto.Enum type values must be unique.
    151 
    152       For example:
    153 
    154           field :my_field, Ecto.Enum, values: [:foo, :bar, :foo]
    155 
    156       is invalid, while
    157 
    158           field :my_field, Ecto.Enum, values: [:foo, :bar, :baz]
    159 
    160       is valid
    161       """
    162     end
    163   end
    164 
    165   defp infer_type(values) do
    166     cond do
    167       Enum.all?(values, &is_integer/1) -> :integer
    168       Enum.all?(values, &is_binary/1) -> :string
    169       true -> nil
    170     end
    171   end
    172 
    173   @impl true
    174   def cast(nil, _params), do: {:ok, nil}
    175 
    176   def cast(data, params) do
    177     case params do
    178       %{on_load: %{^data => as_atom}} -> {:ok, as_atom}
    179       %{on_dump: %{^data => _}} -> {:ok, data}
    180       %{on_cast: %{^data => as_atom}} -> {:ok, as_atom}
    181       _ -> :error
    182     end
    183   end
    184 
    185   @impl true
    186   def load(nil, _, _), do: {:ok, nil}
    187 
    188   def load(data, _loader, %{on_load: on_load}) do
    189     case on_load do
    190       %{^data => as_atom} -> {:ok, as_atom}
    191       _ -> :error
    192     end
    193   end
    194 
    195   @impl true
    196   def dump(nil, _, _), do: {:ok, nil}
    197 
    198   def dump(data, _dumper, %{on_dump: on_dump}) do
    199     case on_dump do
    200       %{^data => as_string} -> {:ok, as_string}
    201       _ -> :error
    202     end
    203   end
    204 
    205   @impl true
    206   def equal?(a, b, _params), do: a == b
    207 
    208   @impl true
    209   def embed_as(_, %{embed_as: embed_as}), do: embed_as
    210 
    211   @doc "Returns the possible values for a given schema and field"
    212   @spec values(module, atom) :: [atom()]
    213   def values(schema, field) do
    214     schema
    215     |> mappings(field)
    216     |> Keyword.keys()
    217   end
    218 
    219   @doc "Returns the possible dump values for a given schema and field"
    220   @spec dump_values(module, atom) :: [String.t()] | [integer()]
    221   def dump_values(schema, field) do
    222     schema
    223     |> mappings(field)
    224     |> Keyword.values()
    225   end
    226 
    227   @doc "Returns the mappings for a given schema and field"
    228   @spec mappings(module, atom) :: Keyword.t()
    229   def mappings(schema, field) do
    230     try do
    231       schema.__changeset__()
    232     rescue
    233       _ in UndefinedFunctionError ->
    234         raise ArgumentError, "#{inspect(schema)} is not an Ecto schema"
    235     else
    236       %{^field => {:parameterized, Ecto.Enum, %{mappings: mappings}}} -> mappings
    237       %{^field => {_, {:parameterized, Ecto.Enum, %{mappings: mappings}}}} -> mappings
    238       %{^field => _} -> raise ArgumentError, "#{field} is not an Ecto.Enum field"
    239       %{} -> raise ArgumentError, "#{field} does not exist"
    240     end
    241   end
    242 end