mathieuprog / polymorphic_embed

Polymorphic embeds in Ecto
Apache License 2.0
335 stars 62 forks source link

How to refactor for "Aliases could not be expanded for the given types" #99

Closed MrYawe closed 5 months ago

MrYawe commented 5 months ago

Since polymorphic_embed 3.0.7 I get this warning:

[warning] Aliases could not be expanded for the given types in [MY STRUCT].

This likely means the types are defined using a module attribute or another reference
that cannot be expanded at compile time. As a result, this may lead to unnecessary
compile-time dependencies, causing longer compilation times and unnecessary
re-compilation of modules (the parent defining the embedded types).

Ensure that the types are specified directly within the macro call to avoid these issues,
or refactor your code to eliminate references that cannot be expanded.

I understand that I must refactor my code to avoid unnecessary compile-time dependencies but I'm not sure how.

Here is how I use polymorphic_embed with a fictional example:

defmodule MyApp.Reminder do
  use Ecto.Schema
  import Ecto.Changeset
  import PolymorphicEmbed
  alias MyApp.Reminder.Channels

  schema "reminders" do
    polymorphic_embeds_one :channel,
      types: Channels.all(),
      on_type_not_found: :raise,
      on_replace: :update
  end
end

defmodule MyApp.Reminder.Channels do
  def all do
    [
      sms: MyApp.Channel.SMS,
      email: MyApp.Channel.Email
    ]
  end
end

The reason why we use a separate module to list all polymorphic_embed types is because we are using it in a lot of different schemas. Can I refactor my code while keeping a single source of truth with all types?

mathieuprog commented 5 months ago

Hi. What does

mix xref graph --label compile-connected --fail-above 0

say for your current code?

MrYawe commented 5 months ago

Thanks for the quick response ❤️

I've found a way to fix it using macros:

defmodule MyApp.Reminder do
  use Ecto.Schema
  import Ecto.Changeset
  import PolymorphicEmbed
  import MyApp.Reminder.Channels

  schema "reminders" do
    embeds_one_channel(:channel)
  end
end

defmodule MyApp.Reminder.Channels do
  @channels [
      sms: MyApp.Channel.SMS,
      email: MyApp.Channel.Email
    ]

  defmacro embeds_one_channel(field_name) do
    quote do
      polymorphic_embeds_one(unquote(field_name),
        types: unquote(@channels),
        type_field: :type,
        on_type_not_found: :raise,
        on_replace: :delete
      )
    end
  end
end
mathieuprog commented 5 months ago

@MrYawe Cool:) I'll suggest that snippet to others. Do you confirm though that your compilation was suboptimal through

mix xref graph --label compile-connected --fail-above 0

and that it made a difference?

In other words, is the warning message even right?

MrYawe commented 5 months ago

@mathieuprog From what I'm seeing, there is no difference.

Every schemas (let's call them Reminder1, Reminder2, Reminder3) that has the same polymorphic_embeds_one field with channels types has as compile time dependency to MyApp.Reminder.Channels. Thats seems expected If I understand compilation correctly.

mix xref graph --label compile-connected --fail-above 0

Results before and after:

...
lib/my_app/reminder_1.ex
└── lib/my_app/channels.ex (compile)
lib/my_app/reminder_2.ex
└── lib/my_app/channels.ex (compile)
lib/my_app/reminder_3.ex
└── lib/my_app/channels.ex (compile)
...
mathieuprog commented 5 months ago

It might have been me not understanding the compile-time dependencies: https://github.com/mathieuprog/polymorphic_embed/issues/85

In which case you refactored for nothing and made your code more complicated for nothing.

I'll make a test project and try to understand better. I added the warning too fast.