Ecto Changeset add functionality for warnings

I created a fork of ecto repository to extend Ecto.Changeset module with the ability to add warnings to the changeset. I wanted to have an add_warnings/4 function which adds a warning to the changeset as a simple list of warnings with this structure warnings: [{atom, {String.t, Keyword.t}}], similar to errors. The difference between the behavior of warnings and errors is that when an error occurs the data are not persisted, but when a warning occurs the data are persisted.

Ecto.Changeset struct extended with keys warnings and warningless?:

defstruct valid?: false, warningless?: false, data: nil, params: nil, changes: %{}, repo: nil,
    errors: [], warnings: [], validations: [], required: [], prepare: [],
    constraints: [], filters: %{}, action: nil, types: nil,
    empty_values: @empty_values

Ecto functions for casting, changing, processing params, etc. adjusted. Function add_warnings/4 added:

@spec add_warning(t, atom, String.t, Keyword.t) :: t
def add_warning(%{warnings: warnings} = changeset, key, message, keys \\ []) when  is_binary(message) do
  %{changeset | warnings: [{key, {message, keys}}|warnings], warningless?: false}
end 

The result is that I receive changeset with expected keys:

#Ecto.Changeset<action: nil, changes: %{}, data: #Company.Booking<>, errors: [],
valid?: true, warnings: [], warningless?: true>

When I make a change with error and warning I receive:

#Ecto.Changeset<action: nil,
changes: %{pickup_address: #Ecto.Changeset<action: :update,
changes: %{street_name: nil}, data: #Company.Address<>,
errors: [street_name: {"can't be blank", [validation: :required]}],
valid?: false,
warnings: [phone_number: {"This phone number is not common in Netherlands",
   []}], warningless?: false>}, data: #Company.Booking<>, errors: [],
valid?: false, warnings: [], warningless?: true>

So, everything is as expected, as far as warnings are concerned. Then, when I make a change with a warning but without an error, I receive:

#Ecto.Changeset<action: nil,
changes: %{pickup_address: #Ecto.Changeset<action: :update,
changes: %{street_name: "sss"}, data: #Company.Address<>, errors: [],
valid?: true,
warnings: [phone_number: {"This phone number is not common in Netherlands",
  []}], warningless?: false>}, data: #Company.Booking<>, errors: [],
valid?: true, warnings: [], warningless?: true>

Everything is as expected. When I don’t make any changes to the form I still should receive a warning for phone number, but I receive:

#Ecto.Changeset<action: nil, changes: %{}, data: #Company.Booking<>, errors: [],
valid?: true, warnings: [], warningless?: true>

I got a changeset without any warnings as there is no changes key in changeset because the data didn’t change.

The question is as follows, how to implement warnings functionality to always have warnings in the changeset, even if no change was made?

This seems like a classic XY problem. https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem

It sounds like your actual problem is “How do I get nice warnings on a form”. This is a solvable problem without forking ecto. Forking ecto is not a viable strategy in the long term, because now you’re tasked with keeping your fork up to date with all future changes to ecto. This is an incredible maintenance burden for something that can be achieved far more simply.

Why can’t you just manage warnings as an additional value to return from your controller? Why do they need to be bundled into the changeset struct?

2 Likes