Skip to content

Commit e351580

Browse files
committed
Avoid compile-time dependencies between parent and
polymorphic embedded schemas
1 parent b573058 commit e351580

File tree

2 files changed

+51
-1
lines changed

2 files changed

+51
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
* Default value for polymorphic list of embeds is `[]` instead of `nil` (following `embeds_many/3`)
66
* Support Phoenix HTML 4.0
7+
* Avoid compile-time dependencies between parent and polymorphic embedded schemas
78

89
### Migration from 2.x to 3.x
910

lib/polymorphic_embed.ex

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,71 @@
11
defmodule PolymorphicEmbed do
22
use Ecto.ParameterizedType
33

4+
require Logger
5+
46
alias Ecto.Changeset
57

68
defmacro polymorphic_embeds_one(field_name, opts) do
9+
opts = Keyword.update!(opts, :types, &expand_alias(&1, __CALLER__))
10+
711
quote do
812
field(unquote(field_name), PolymorphicEmbed, unquote(opts))
913
end
1014
end
1115

1216
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__))
1421

1522
quote do
1623
field(unquote(field_name), {:array, PolymorphicEmbed}, unquote(opts))
1724
end
1825
end
1926

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+
2069
@impl true
2170
def type(_params), do: :map
2271

0 commit comments

Comments
 (0)