Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support updating (while retaining ids) on cast_polymorphic_embed for polymorphic_embeds_many. #95

Closed
wants to merge 13 commits into from
7 changes: 2 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@ jobs:
name: OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}
strategy:
matrix:
otp: ["24.3", "25.1"]
elixir: ["1.13", "1.14"]
exclude:
- otp: "25.1"
elixir: "1.13"
otp: ["25.3", "26.2"]
elixir: ["1.15", "1.16"]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,12 @@ add(:channel, :map)

* `:required` – if the embed is a required field.

* `:with` – allows you to specify a custom changeset. Either pass an MFA or a function:
* `:with` – allows you to specify a custom changeset.
```elixir
changeset
|> cast_polymorphic_embed(:channel,
with: [
sms: {SMS, :custom_changeset, ["hello"]},
sms: &SMS.custom_changeset/2,
email: &Email.custom_changeset/2
]
)
Expand Down
4 changes: 1 addition & 3 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import Config

config :logger, level: :warn

config :phoenix, :json_library, Jason
config :logger, level: :warning

config :polymorphic_embed,
ecto_repos: [PolymorphicEmbed.Repo]
Expand Down
58 changes: 55 additions & 3 deletions lib/polymorphic_embed.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,60 @@ defmodule PolymorphicEmbed do
use Ecto.ParameterizedType

defmacro polymorphic_embeds_one(field_name, opts) do
opts = Keyword.update!(opts, :types, &expand_alias(&1, __CALLER__))

quote do
field(unquote(field_name), PolymorphicEmbed, unquote(opts))
end
end

defmacro polymorphic_embeds_many(field_name, opts) do
opts = Keyword.merge(opts, default: [])
opts =
opts
|> Keyword.put_new(:default, [])
|> Keyword.update!(:types, &expand_alias(&1, __CALLER__))

quote do
field(unquote(field_name), {:array, PolymorphicEmbed}, unquote(opts))
end
end

# Expand module aliases to avoid creating compile-time dependencies between the
# parent schema that uses `polymorphic_embeds_one` or `polymorphic_embeds_many`
# and the embedded schemas.
defp expand_alias(types, env) when is_list(types) do
Enum.map(types, fn
{type_name, type_opts} when is_list(type_opts) ->
{type_name, Keyword.update!(type_opts, :module, &do_expand_alias(&1, env))}

{type_name, module} ->
{type_name, do_expand_alias(module, env)}
end)
end

defp expand_alias({:%{}, meta, types}, env) do
types = expand_alias(types, env)
{:%{}, meta, types}
end

# if it's not a list or a map, it means it's being defined by a reference of some kind,
# possibly via module attribute like:

# @types [twilio: PolymorphicEmbed.Channel.TwilioSMSProvider]
# # ...
# polymorphic_embeds_one(:fallback_provider, types: @types)

# which means we can't expand aliases
defp expand_alias(types, _env), do: types

defp do_expand_alias({:__aliases__, _, _} = ast, env) do
Macro.expand(ast, %{env | function: {:__schema__, 2}})
end

defp do_expand_alias(ast, _env) do
ast
end

@impl true
def type(_params), do: :map

Expand Down Expand Up @@ -207,6 +248,8 @@ defmodule PolymorphicEmbed do
type_field: type_field
} = field_options

list_data_for_field = Map.fetch!(changeset.data, field)

embeds =
Enum.map(list_params, fn params ->
case do_get_polymorphic_module_from_map(params, type_field, types_metadata) do
Expand All @@ -220,8 +263,17 @@ defmodule PolymorphicEmbed do
:ignore

module ->
embed_changeset = changeset_fun.(struct(module), params)
embed_changeset = %{embed_changeset | action: :insert}
data_for_field =
Enum.find(list_data_for_field, fn datum ->
datum.id != nil and datum.id == params[:id] and datum.__struct__ == module
end)

embed_changeset =
if data_for_field do
%{changeset_fun.(data_for_field, params) | action: :update}
else
%{changeset_fun.(struct(module), params) | action: :insert}
end

case embed_changeset do
%{valid?: true} = embed_changeset ->
Expand Down
181 changes: 0 additions & 181 deletions lib/polymorphic_embed/html/form.ex

This file was deleted.

8 changes: 2 additions & 6 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,12 @@ defmodule PolymorphicEmbed.MixProject do

defp deps do
[
{:ecto, "~> 3.9"},
{:ecto, "~> 3.11"},
{:jason, "~> 1.4"},
{:phoenix_html, "~> 2.14 or ~> 3.2", optional: true},
{:ex_doc, "~> 0.28", only: :dev},
{:ecto_sql, "~> 3.9", only: :test},
{:ecto_sql, "~> 3.11", only: :test},
{:postgrex, "~> 0.16", only: :test},
{:query_builder, "~> 1.0", only: :test},
{:phoenix_ecto, "~> 4.4", only: :test},
{:phoenix_live_view, "~> 0.18", only: :test},
{:floki, "~> 0.33", only: :test},
{:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false},
{:excoveralls, "~> 0.15", only: :test},
{:credo, "~> 1.6", only: [:dev, :test], runtime: false}
Expand Down
Loading