By following the practice in Ecto.Schema docs to set schema attributes globally (Ecto.Schema — Ecto v3.7.2), I realized that transitive compile time dependencies are created when defining a protocol implementation for an ecto schema.
Please check this example, it’s an empty phoenix project with this:
Ecto.Schema wrapper:
defmodule ProtoComp.Schema do
defmacro __using__(_) do
quote do
use Ecto.Schema
@timestamps_opts type: :utc_datetime_usec
end
end
end
Schema:
defmodule ProtoComp.Things.Chair do
use ProtoComp.Schema
import Ecto.Changeset
schema "chairs" do
field :attributes, :string
field :color, :string
timestamps()
end
@doc false
def changeset(chair, attrs) do
chair
|> cast(attrs, [:color, :attributes])
|> validate_required([:color, :attributes])
end
end
Protocol:
defprotocol ProtoCompWeb.UseCases.GrabWith do
def accessories(thing)
end
Implementation:
defimpl ProtoCompWeb.UseCases.GrabWith, for: ProtoComp.Things.Chair do
def accessories(_), do: "use your hands"
end
And nothing else. Now, when trying to check the compile-linked dependencies with mix xref graph --label compile-connected --fail-above 0
, the result is that the Chair schema is a transitive dependency for the protocol implementation.
What I don’t understand is why this happens, and why it’s not happening when I remove the Ecto.Schema wrapper and use it directly in the Chair schema, like this:
defmodule ProtoComp.Things.Chair do
use Ecto.Schema
import Ecto.Changeset
@timestamps_opts type: :utc_datetime_usec
schema "chairs" do
field :attributes, :string
...
end
What’s making this difference? I think this approach shouldn’t raise a transitive dependency in any of those circumstances.