The only way to have any is if you can build it on top of a ecto primitive type. Using :binary and term_to_binary for example. Otherwise how should ecto be able to convert the data to something the database adapter can convert to something the db understands.
I suppose the same way it converts keys and values in any arbitrary map that we give it for a :map field. i.e. if we have:
embeds_one :object, Object do
field :value, :map
end
And save %Object{value: %{string: "42", number: 42}} it has no issues saving the string as a string and the number as a number, even though we don’t specify their types. It just maps them to JSON types I suppose? What I’m aiming for here is having the same type-tolerant behavior for a schema field (that’s already in an embedded object).
I guess it’s easier for :map because the outer shape is encoded in the type; it’s always a map. The different values are always at least one level nested within the containing column. This constraint is not present for :any unless it’s only available to embeds. By now there are no primitive types limited like that.
I’d also still suggest to look into custom types. Iirc the type/0 callback is not really used for embeds and with ecto 3.2 there’s even better support for encoding embedded data for storage.
For posterity, something like this does seem to work:
defmodule Ecto.Type.Any do
use Ecto.Type
def type, do: :map
def cast(value)
when is_map(value) or
is_list(value) or
is_number(value) or
is_binary(value) or
is_boolean(value), do: {:ok, value}
def cast(value), do: {:error, "cannot cast #{inspect value}"}
def load(value)
when is_map(value) or
is_list(value) or
is_number(value) or
is_binary(value) or
is_boolean(value), do: {:ok, value}
def load(value), do: {:error, "cannot load #{inspect value}"}
def dump(value)
when is_map(value) or
is_list(value) or
is_number(value) or
is_binary(value) or
is_boolean(value), do: {:ok, value}
def dump(value), do: {:error, "cannot dump #{inspect value}"}
end
defmodule EctoTypeAny do
alias Ecto.Type
@behaviour Type
@impl Type
def type, do: :any
@impl Type
def cast(value), do: Type.cast(:any, value)
@impl Type
def load(value), do: Type.load(:any, value)
@impl Type
def dump(value), do: Type.dump(:any, value)
end