Overriding module results in "duplicated modules" error

I overrode the Ecto NotLoaded association because in my controller the json(conn, ad_campaign) would fail if any relation of ad_campaign was nil, and I really didn’t want to manually craft every single response and would prefer to use the json/2 method.

So I did this:

defimpl Jason.Encoder, for: Ecto.Association.NotLoaded do
  def encode(_, opts) do
    Jason.Encode.map(%{}, opts)
  end
end

This solved the problem well enough, but then when I tried to release the app, I get this error:

** (Mix) Duplicated modules: 
        'Elixir.Jason.Encoder.Ecto.Association.NotLoaded' specified in amplify and ecto

I understand this error - is there a way I can override nicely?

There is not. Duplicate protocol implementations are considered a problem to be fixed by elixir, not one to be worked around.

Imo the proper way to solve this is to be explicit about which data should be included in the json format and not include incorrect data – not loaded could be an existing association or no existing association.

Protocols are also a problematic way of doing encoding for APIs, because a single datatype might need to encode to multiple distinct json representations: Phoenix Views for JSON APIs | Benjamin Milde

4 Likes

There is a way

defmodule Overrider do
  @on_load :override

  def override do
    defmodule :"Elixir.COMPLETE MODULE NAME HERE" do
      ...
    end
    :ok
  end
end

You can safely inner defmodule with defimpl, but you have to disable protocol consolidation in project configuration.

This is a dark magic, but please, embrace it, make everybody get goosebumps from this