gmile

gmile

Getting rid of a transitive compile dependency with custom Ecto type

Hi! I’m continuing my battle (1, 2) against transitive compile-time dependencies on a large code base :slight_smile:

This time I am facing an issue with getting rid of a comp. time dependency coming from a custom Ecto type. The source code repo depicting the issue is available here.

Basically, I have 3 modules: Ecto schema, custom type and a module with helper functions. Heavily simplified versions of them look like this:

defmodule MySchema do
  use Ecto.Schema

  schema "my_table" do
    field :my_field, MyType
  end
end
defmodule MyType do
  use Ecto.Type

  def type, do: :binary

  def cast(value), do: {:ok, MyHelperModule.heavy_casting_work(value)}
  def load(value), do: {:ok, MyHelperModule.heavy_loading_work(value)}
  def dump(value), do: {:ok, MyHelperModule.heavy_dumping_work(value)}
end
defmodule MyHelperModule do
  def heavy_casting_work(value), do: value
  def heavy_loading_work(value), do: value
  def heavy_dumping_work(value), do: value
end

The problem here is that, in case implementation of MyHelperModule changes, both MyType and MySchema modules need to be re-compiled. It can be seen by calling mix xref graph:

mix xref graph --source lib/my_schema.ex

Output:

lib/my_schema.ex
└── lib/my_type.ex (compile)
    └── lib/my_helper_module.ex

The rationale for MyHelperModule’s existence is that it can be quite complicated, and best for it is to exist & be tested on its own. Additionally, it is sometimes used in other contexts in the application, such that don’t really require an Ecto.Type interface.

What would be a strategy to get rid of a transitive dependency here?

It seems to me that Ecto absolutely needs to compile all custom types ahead of compiling the schema, so the only way to ensure there’s no transitive com.-time dependency is to have MyType be absolutely free of dependencies on any other module in the app :frowning:

Marked As Solved

LostKobrakai

LostKobrakai

You can always go and change a compile time dependency to a runtime dependency e.g. by using something like Module.concat to build the module name for your helper module in a function body of a function on the type module.

If the compiler cannot know the module at compile time it cannot react in any way.

Another option would be inverting the dependency. Put the implementations for provided functionality into the type module and let the helper delegate to the implementation on the type.

Also Liked

josevalim

josevalim

Creator of Elixir

:+1: for this particular bit. In this case, it has to be a compile-time dependency, so minimizing the subgraph makes sense.

Where Next?

Popular in Questions Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
mgjohns61585
Could someone help me? I’m making my first elixir program, number guessing game. I can’t figure out how to convert the user’s guess from ...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

Other popular topics Top

hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a > b) do {:ok, "a"} end if (a < b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43806 214
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New

We're in Beta

About us Mission Statement