Default value for Ecto schemas on load

Hi,

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.

Thanks

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. :slight_smile:

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.

2 Likes

In recent versions of ecto you can make ecto types use cast/load even for embedded data.

Right, but if the underlying value is nil, load is not even called on the custom type. I guess I’ll have to go with doing it on every read.