|
1 | 1 | defmodule PolymorphicEmbed do |
2 | 2 | use Ecto.ParameterizedType |
3 | 3 |
|
| 4 | + require Logger |
| 5 | + |
4 | 6 | alias Ecto.Changeset |
5 | 7 |
|
6 | 8 | defmacro polymorphic_embeds_one(field_name, opts) do |
| 9 | + opts = Keyword.update!(opts, :types, &expand_alias(&1, __CALLER__)) |
| 10 | + |
7 | 11 | quote do |
8 | 12 | field(unquote(field_name), PolymorphicEmbed, unquote(opts)) |
9 | 13 | end |
10 | 14 | end |
11 | 15 |
|
12 | 16 | defmacro polymorphic_embeds_many(field_name, opts) do |
13 | | - opts = Keyword.merge(opts, default: []) |
| 17 | + opts = |
| 18 | + opts |
| 19 | + |> Keyword.put_new(:default, []) |
| 20 | + |> Keyword.update!(:types, &expand_alias(&1, __CALLER__)) |
14 | 21 |
|
15 | 22 | quote do |
16 | 23 | field(unquote(field_name), {:array, PolymorphicEmbed}, unquote(opts)) |
17 | 24 | end |
18 | 25 | end |
19 | 26 |
|
| 27 | + # Expand module aliases to avoid creating compile-time dependencies between the |
| 28 | + # parent schema that uses `polymorphic_embeds_one` or `polymorphic_embeds_many` |
| 29 | + # and the embedded schemas. |
| 30 | + defp expand_alias(types, env) when is_list(types) do |
| 31 | + Enum.map(types, fn |
| 32 | + {type_name, type_opts} when is_list(type_opts) -> |
| 33 | + {type_name, Keyword.update!(type_opts, :module, &do_expand_alias(&1, env))} |
| 34 | + |
| 35 | + {type_name, module} -> |
| 36 | + {type_name, do_expand_alias(module, env)} |
| 37 | + end) |
| 38 | + end |
| 39 | + |
| 40 | + # If it's not a list or a map, it means it's being defined by a reference of some kind, |
| 41 | + # possibly via module attribute like: |
| 42 | + # @types [twilio: PolymorphicEmbed.Channel.TwilioSMSProvider] |
| 43 | + # # ... |
| 44 | + # polymorphic_embeds_one(:fallback_provider, types: @types) |
| 45 | + # which means we can't expand aliases |
| 46 | + defp expand_alias(types, env) do |
| 47 | + Logger.warning(""" |
| 48 | + Aliases could not be expanded for the given types in #{inspect(env.module)}. |
| 49 | +
|
| 50 | + This likely means the types are defined using a module attribute or another reference |
| 51 | + that cannot be expanded at compile time. As a result, this may lead to unnecessary |
| 52 | + compile-time dependencies, causing longer compilation times and unnecessary |
| 53 | + re-compilation of modules (the parent defining the embedded types). |
| 54 | +
|
| 55 | + Ensure that the types are specified directly within the macro call to avoid these issues, |
| 56 | + or refactor your code to eliminate references that cannot be expanded. |
| 57 | + """) |
| 58 | + types |
| 59 | + end |
| 60 | + |
| 61 | + defp do_expand_alias({:__aliases__, _, _} = ast, env) do |
| 62 | + Macro.expand(ast, %{env | function: {:__schema__, 2}}) |
| 63 | + end |
| 64 | + |
| 65 | + defp do_expand_alias(ast, _env) do |
| 66 | + ast |
| 67 | + end |
| 68 | + |
20 | 69 | @impl true |
21 | 70 | def type(_params), do: :map |
22 | 71 |
|
|
0 commit comments