Providing a customisable schema from a dependency

I’m writing an app, ExtDep, that’s intended to be used as a dependency in other apps. It defines some schemas, but I’m struggling to find a design that lets callers (extend|compose|customise) those schemas—like defining a custom primary_key.

The out of the box experience is as you’d expect from a typical callback-module app. There’s some runtime configuration, a boundary context in ExtDep that’s invoked from YourApp, and some work that happens in the configured repo.

deps/ext_dep/lib/schemas.ex
defmodule ExtDep.SomeSchema do
  use Ecto.Schema

  schema "some_schemas" do
    field :some_column, :string
  end
end

defmodule ExtDep.SomeSchemaAssoc do
  use Ecto.Schema

  schema "some_schemas_assocs" do
    field :assoc_column, :string
    belongs_to :some_schema, ExtDep.SomeSchema
  end
end
deps/ext_dep/lib/ext_dep.ex
defmodule ExtDep do
  ...
  def bar(args) do
    schema = create_schema(args)
    config().repo.insert(schema)
  end
end
config.exs
config :your_app, ExtDep,
  repo: YourApp.Repo
lib/your_app/some_module.ex
defmodule YourApp.SomeModule do
  def foo(args) do
    ExtDep.bar(args)
  end
end

Ecto’s docs provide an example for defining a custom schema to override the default primary_key and foreign_key_type attributes in schemas you control:

lib/your_app/schema.ex
defmodule YourApp.Schema do
  defmacro __using__(_) do
    quote do
      use Ecto.Schema
      @primary_key {:id, :binary_id, autogenerate: true}
      @foreign_key_type :binary_id
    end
  end
end

I thought about rewriting ExtDep's schemas to leverage a __using__ macro, something like:

defmodule ExtDep.SomeSchema do
  defmacro __using__(opts) do
    quote bind_quoted: [opts: opts] do
        use Keyword.get(opts, :schema, Ecto.Schema)

        schema ..., do: ...
    end
  end
end

defmodule YourApp.SomeSchema do
  use ExtDep.SomeSchema, schema: YourApp.Schema
end

But that’s not going to work because use expects a module that’s available at compile time.

The other consideration I have is that if the calling application provides its own module, the belongs_to :some_schema, ExtDep.SomeSchema field is not going to work.

Is the idea of customising a dependencies schema sane?

1 Like