Ecto.CastError Mixed Keys Issue

In a typical business development task, having a function in a context module which accepts attributes of map type and passes them to Ecto for a validated Changeset is a common case.

  def create_foo(%{} = attrs) do
    attrs
    |> Foo.create_changeset()
    |> Repo.insert()
  end

Those functions may be called from everywhere; Normally, it is quite common to express the keys by atom while calls from Phoenix Controllers and LiveViews have keys in string. So these business functions should support both types of keys and thanks to the cast method, they do by default.

But, what if by some requirement it is needed to change the passed in attributes before passing them to Ecto for changeset or validations? By which data type the attribute keys should be addressed? string or atom? Suppose the following function:

  def create_bar_foo(%{} = attrs) do
    attrs
    |> Map.put(bar_case: :default_or_computed_value)
    |> Foo.create_changeset()
    |> Repo.insert()
  end

If it called from a test with normal atom keys, every thing would be fine while if it called from a LiveView with string keys, there will be a raise:

(Ecto.CastError) expected params to be a map with atoms or string keys, got a map with mixed keys ...

So the keys should be normalized before processing and this article suggest some solution. Also this post addresses it somehow.

There can be lots of other solutions to this problem, for example one can simply cast passed attributes before manipulation them and so on. But I think this can be included in Ecto.Changeset.cast method because:

  • Ecto.Changeset.cast is already supporting both atom and binary keys and also touching the case by doing conversion from string keys to atom ones. Also it sounds feasible to support mix of both types.
  • There is no harm to existing codes as actually a new capability is being added.
  • It encourages having some business implemented in context before using Ecto directly or having lots of ###_changeset functions per specific business case.