I have a situation in which, when loading an Ecto.Schema from the database and a certain field is nil, I want to set it to a default value. Note that this is different than setting a default value when it’s being inserted, which I can do with the default keyword on the field macro on directly on the database itself.
I’ve tried to create a custom type for this, but hit a wall, since Ecto will not run the load function of the custom Ecto.Type if the value from the database is nil. I’m assuming this is because it was deemed unnecessary.
Is there a workaround for this, or will I have to add the default after fetching the schema on every call to the database? That’s what I was trying to avoid to do.
Just use a module that does the loading for you and implements your desired behaviour. Assuming your model is Order and the field you want to have a non-DB default value for is country, then I’d create an Orders context module like so:
defmodule MyApp.Orders do
def get(id) when is_integer(id) do
case Repo.get(Order, id) do
nil -> nil
order -> put_defaults(order)
end
end
defp put_defaults(%Order{country: nil} = order) do
%Order{order | country: "your_default"}
end
defp put_defaults(%Order{country: _country} = order), do: order # non-nil country
end
Then everywhere you need this behaviour you will just use Orders.get(id) (after you do an alias MyApp.Orders of course).
Don’t seek magic in Elixir. You can create it yourself.
Right, I know I can do that, but at this point that’s going to be some work, plus it depends on the developers to remember not to use the Repo directly, which I also wanted to avoid. As a last resort I’m willing to do it, but was wondering if there are other options.
AFAIK, it was a conscious choice. Such very niche conveniences like in your case come at a huge price of implied magic and invisible complexity for everybody else who doesn’t need them.
It’s best if you basically copy/paste the code above and train your team to use contexts. Direct usages of Repo should be scoped only to a single directory of source files (or separate app in an umbrella) anyway.