Override Protocol implementation without warning?

ecto

#1

My usecase is, I want to override Ecto.Association.NotLoaded's Jason.Encoder implementation, so it returns nil instead of raising. It works if I simply just do it, but not without warnings.

defimpl Jason.Encoder, for: Ecto.Association.NotLoaded do
  def encode(_struct, _opts), do: "null"
end
warning: redefining module Jason.Encoder.Ecto.Association.NotLoaded (current version loaded from _build/dev/lib/ecto/ebin/Elixir.Jason.Encoder.Ecto.
Association.NotLoaded.beam)
  lib/app_web/views/not_loaded.ex:1
warning: this clause cannot match because a previous clause at line 1 always matches
  deps/jason/lib/encoder.ex:1

So the question is, is it possible to do this without warnings?


#2

You can set the compiler option :ignore_module_conflict. You can do that in your mix file by setting it in the project settings under the :elixirc_options key or by setting it in the code with Code.compiler_options/1. With the latter, you may be able to disable it temporarily to allow your one use.

However, I don’t think you’ll be able to get past the second warning.

Also, depending on how elixir handles consolidating protocols, it could be the case that your clause will be the one that never matches, because it already matched the pre-existing one.


#3

My clause actually matches fine.

I guess I was more like wondering whether this is a bad thing to do. Or is there a proper way to do this. Or should protocol implementations be allowed to be overridden.

EDIT: forgot to say thanks!


#4

Having your database schemas automatically serialized to JSON can be convenient. This is technically leaking an implementation detail though. If you change your database, you may not want to change your API. That may not matter for every API though.

One way to address the issue you’re having is to derive a JSON encoding for your schema, that excludes the fields/relationships you’re not going to load.

defmodule User do
  use Ecto.Schema
  @derive {Jason.Encoder, except: [:association_im_not_loading]}

  schema "users" do
  end
end

If you sometimes render the schema in different ways, it’s probably better to do the transformation in your views. This would also be the way you’d use if you want to protect your API from changes to your database. You can do something like:

user
|> Map.from_struct()
|> Map.drop([:field1, :field2])

# or

Map.take(user, [:field1, :field2])

Maybe?


#5

I do need the associations from time to time, so they can’t simply be excluded. I actually explicitly include them in the derive.

This would have to be done at view layer.
Preferably my view layer should not care about what data is preloaded and what is not. (so I won’t have to do this specifically for every different case)