Reputation: 7418
What if I have the following struct:
%Registration{email: "[email protected]", first_name: "John", last_name: "Doe"}
And I want to create two different structs from it:
%Account{email: "[email protected]", password: nil, ...}
%Profile{first_name: "John", last_name: "Doe", age: nil, ...}
I have already taken a look at this question, but if I do
registration = %Registration{email: "[email protected]", first_name: "John", last_name: "Doe"}
struct(Account, registration)
I receive protocol Enumerable not implemented for %Registration{...
So I have to use Map.from_struct
registration = %Registration{email: "[email protected]", first_name: "John", last_name: "Doe"}
struct(Account, Map.from_struct(registration))
But I think it is not very clean. So I was just thinking is there a better way to do this? This should be quite a common problem, right?
Upvotes: 1
Views: 1376
Reputation: 123
You can do it easily, but in a bit hacky way - just delegate protocol functions to Enumerable.Map
:
defmodule Contact do
defstruct [:name, group: "", version: "", status: :latent]
defimpl Enumerable, for: Contact do
defdelegate([count(e), member?(e, m), reduce(e, a, f)],
to: Enumerable.Map)
end
end
or in a bit more proper way like
defmodule Contact do
defstruct [:name, group: "", version: "", status: :latent]
defimpl Enumerable do
alias Enumerable, as: E
def count(c), do: Map.from_struct(c) |> E.count()
def member?(c, m), do: Map.from_struct(c) |> E.member?(m)
def reduce(c, a, f), do: Map.from_struct(c) |> E.reduce(a, f)
end
end
Enumerable.count
implementation can be generated with macro to return struct members count which is already known at compile time.
Enumerable.member?
can also be generated with macro or written by hand or delegated again to Enumerable.Map.member?
to remove runtime penalty of Map.from_struct
.
All the code above is generic so it can be placed to use
-able module like
defmodule StructEnumerable do
defmacro __using__(opts \\ []) do
quote do
defimpl Enumerable do
alias Enumerable, as: E
def count(c), do: Map.from_struct(c) |> E.count()
def member?(c, m), do: Map.from_struct(c) |> E.member?(m)
def reduce(c, a, f), do: Map.from_struct(c) |> E.reduce(a, f)
end
# you can also add following code
# to provide Fetch behaviour implementation
# for your structs
defdelegate([
fetch(t, k),
get_and_update(t, k, l)
], to: Map)
end
end
end
and embed it like
defmodule Contact do
defstruct [:name, group: "", version: "", status: :latent]
use StructEnumerable
end
Upvotes: 1
Reputation: 2313
As per the documentation:
Converts a struct to map. It accepts the struct module or a struct itself and simply removes the struct field from the struct.
The function exists for a reason. I don't see the way you did it as being a problem really. If it is a common problem I haven't seen it before. If you want to have a more expressive or cleaner code you may try:
registration = %Registration{email: "[email protected]", first_name: "John", last_name: "Doe"} |> Map.from_struct
struct(Account, registration)
or other equivalent code by checking the docs here.
Upvotes: 2