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

Add :by_module option to :types to allow passing in the embedded_struct in the type_field. #65

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,9 @@ changeset

### PolymorphicEmbed Ecto type

The `:types` option for the `PolymorphicEmbed` custom type contains a keyword list mapping an atom representing the type
(in this example `:email` and `:sms`) with the corresponding embedded schema module.
The `:types` option for the `PolymorphicEmbed` custom type is used to determine the type of the polymorphic embed. The value is either a keyword list mapping an atom representing the type (in this example `:email` and `:sms`) with the corresponding embedded schema module, or the value :by_module which indicates that the struct will be passed in at runtime.

There are two strategies to detect the right embedded schema to use:
There are three strategies to detect the right embedded schema to use:

1.
```elixir
Expand All @@ -137,6 +136,16 @@ parameter is then no longer required.
Note that you may still include a `__type__` parameter that will take precedence over this strategy (this could still be
useful if you need to store incomplete data, which might not allow identifying the type).

3.
```elixir
polymorphic_embeds_many :attachment,
types: :by_module,
on_replace: :update
```

The `:by_module` value indicates that this field will receive an embedded_struct in the `type_field` (by default `__type__`) which is used to cash the embed.
If you specify `types: :by_module`, the `"__type__"` (or `:__type__`) parameter should contain the fully qualified and reachable module name of the embedded schema, such as "MyApp.Channel.Email". This is equivalent to specifying

#### List of polymorphic embeds

Lists of polymorphic embeds are also supported:
Expand Down
81 changes: 61 additions & 20 deletions lib/polymorphic_embed.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,29 @@ defmodule PolymorphicEmbed do
types_metadata =
opts
|> Keyword.fetch!(:types)
|> Enum.map(fn
{type_name, type_opts} when is_list(type_opts) ->
{type_name, type_opts}

{type_name, module} ->
{type_name, module: module}
end)
|> Enum.map(fn
{type_name, type_opts} ->
%{
type: type_name,
module: Keyword.fetch!(type_opts, :module),
identify_by_fields:
type_opts |> Keyword.get(:identify_by_fields, []) |> Enum.map(&to_string/1)
}
end)
|> case do
:by_module ->
%{lookup_type_fun: :by_module}

types when is_list(types) ->
types
|> Enum.map(fn
{type_name, type_opts} when is_list(type_opts) ->
{type_name, type_opts}

{type_name, module} ->
{type_name, module: module}
end)
|> Enum.map(fn
{type_name, type_opts} ->
%{
type: type_name,
module: Keyword.fetch!(type_opts, :module),
identify_by_fields:
type_opts |> Keyword.get(:identify_by_fields, []) |> Enum.map(&to_string/1)
}
end)
end

%{
default: Keyword.get(opts, :default, nil),
Expand All @@ -65,6 +72,7 @@ defmodule PolymorphicEmbed do
%{array?: array?, types_metadata: types_metadata} = field_options

required = Keyword.get(cast_options, :required, false)

with = Keyword.get(cast_options, :with, nil)

changeset_fun = fn
Expand All @@ -84,6 +92,13 @@ defmodule PolymorphicEmbed do
fun ->
apply(fun, [struct, params])
end

struct, params when is_tuple(with) and tuple_size(with) == 3 ->
{module, function_name, args} = with
apply(module, function_name, [struct, params | args])

struct, params when is_function(with) ->
apply(with, [struct, params])
end

(changeset.params || %{})
Expand Down Expand Up @@ -339,12 +354,25 @@ defmodule PolymorphicEmbed do
# Enum.count(contained -- container) == 0
# contained -- container == []
types_metadata
|> Enum.filter(&([] != &1.identify_by_fields))
|> Enum.find(&([] == &1.identify_by_fields -- Map.keys(attrs)))
|> (&(&1 && Map.fetch!(&1, :module))).()
|> case do
%{lookup_type_fun: :by_module} ->
nil

_ ->
types_metadata
|> Enum.filter(&([] != &1.identify_by_fields))
|> Enum.find(&([] == &1.identify_by_fields -- Map.keys(attrs)))
|> (&(&1 && Map.fetch!(&1, :module))).()
end
end
end

defp do_get_polymorphic_module_for_type(type, %{lookup_type_fun: :by_module}) do
type = to_string(type)

Module.safe_concat([type])
end

defp do_get_polymorphic_module_for_type(type, types_metadata) do
get_metadata_for_type(type, types_metadata)
|> (&(&1 && Map.fetch!(&1, :module))).()
Expand All @@ -355,6 +383,14 @@ defmodule PolymorphicEmbed do
do_get_polymorphic_type(module_or_struct, types_metadata)
end

defp do_get_polymorphic_type(%module{}, %{lookup_type_fun: :by_module}),
do: do_get_polymorphic_type(module, %{lookup_type_fun: :by_module})

defp do_get_polymorphic_type(module, %{lookup_type_fun: :by_module}) do
module = to_string(module)
Module.safe_concat([module])
end

defp do_get_polymorphic_type(%module{}, types_metadata),
do: do_get_polymorphic_type(module, types_metadata)

Expand All @@ -372,7 +408,7 @@ defmodule PolymorphicEmbed do
"""
def types(schema, field) do
%{types_metadata: types_metadata} = get_field_options(schema, field)
Enum.map(types_metadata, &(&1.type))
Enum.map(types_metadata, & &1.type)
end

defp get_metadata_for_module(module, types_metadata) do
Expand Down Expand Up @@ -412,6 +448,11 @@ defmodule PolymorphicEmbed do
end
end

defp convert_map_keys_to_string(%_{} = struct) do
Map.from_struct(struct)
|> convert_map_keys_to_string()
end

defp convert_map_keys_to_string(%{} = map),
do: for({key, val} <- map, into: %{}, do: {to_string(key), val})

Expand Down
Loading