For simple maps and schemas, I understand how to convert maps into Ecto schemas.
Now, I want to convert a map into a Ecto struct which does not have the same fields, and have custom types.
Example:
The Schema I have
defmodule AuthInfo do
use Ecto.Schema
import Ecto.Changeset
@primary_key false
schema "UserTable" do
field :user_id, :string, source: :pk
field :info, ProviderAccountID, source: :sk
end
end
defmodule ProviderAccountID do
@moduledoc """
Struct that holds provider and its account_id.
"""
@encode_prefix "AUTHINFO"
@type provider :: :twitter | :facebook | :google
@type t :: %__MODULE__{provider: provider, id: String.t()}
defstruct [:provider, :id]
@spec new(any, any) :: {:ok, t} | {:error, reason :: String.t()}
def new(provider, _id) when not (provider in [:twitter]), do: {:error, "unacceptable provider: #{provider}"}
def new(provider, id), do: {:ok, %__MODULE__{provider: provider, id: id |> to_string()}}
use Ecto.Type
def type, do: :string
def cast(%__MODULE__{} = data), do: {:ok, data}
def cast({provider, id}), do: new(provider, id)
def cast(nil), do: nil
def cast(_), do: :error
def load(@encode_prefix <> "#" <> body) do
case body |> String.split("#") do
[provider | [id | _]] -> new(provider |> String.to_existing_atom(), id)
_ -> :error
end
end
def load(""), do: nil
def load(nil), do: nil
def dump(%__MODULE__{provider: p, id: id}), do: {:ok, "#{@encode_prefix}##{p}##{id}"}
def dump(nil), do: {:ok, ""}
def dump(_), do: :error
end
and what I want to do:
iex> %{pk: "user_1", sk: "AUTHINFO#twitter#12345678"}
...> |> convert_to_struct()
%AuthInfo{__meta__: #Ecto.Schema.Metadata<:built, "UserTable">,
info: %ProviderAccountID{provider: :twitter, id: "123456878"},
user_id: "user_1"}
iex> %AuthInfo{__meta__: #Ecto.Schema.Metadata<:built, "UserTable">,
info: %ProviderAccountID{provider: :twitter, id: "123456878"},
user_id: "user_1"}
...> |> convert_to_map()
%{pk: "user_1", sk: "AUTHINFO#twitter#12345678"}
or maybe convert Ecto.Changeset into maps like above directly.
What I get currently
def parse(map) do
%AuthInfo{}
|> cast(map, [:pk, :sk])
|> validate_required([:user_id, :info])
|> apply_changes()
end
iex> .AuthInfo.parse(%{pk: "user_1", sk: "AUTHINFO#twitter#12345678"})
** (ArgumentError) unknown field `:pk` given to cast. Either the field does not exist or it is a :through association (which are read-only). The known fields are: :info, :user_id
(ecto 3.5.8) lib/ecto/changeset.ex:551: Ecto.Changeset.cast_type!/2
(ecto 3.5.8) lib/ecto/changeset.ex:520: Ecto.Changeset.process_param/7
(elixir 1.11.3) lib/enum.ex:2193: Enum."-reduce/3-lists^foldl/2-0-"/3
(ecto 3.5.8) lib/ecto/changeset.ex:506: Ecto.Changeset.cast/6
lib/auth_info.ex:37: AuthInfo.parse/1
It’s okay to implement the whole conversion myself (maybe by using __schema__(:field_source, field)
), but if I’m missing predefined features or any open source products, I’m happy to know.
thanks,