万能药水地图与混合键

Mar*_*l.V 3 elixir ecto phoenix-framework

在Phoenix应用程序中,我有一个函数可以获取两个地图,并通过Ecto.Changeset在数据库中创建两个条目。

def create_user_with_data(user_attrs, data_attrs) do
    name = cond do
        data_attrs["name"] ->
            data_attrs["name"]
        data_attrs[:name] ->
            data_attrs[:name]
        true -> nil
    end
    Ecto.Multi.new()
    |> Ecto.Multi.insert(:user, User.registration_changeset(%User{}, Map.put(user_attrs, :name, name)))
    |> Ecto.Multi.run(:user_data, fn(%{user: user}) ->
        %MyApp.Account.UserData{}
        |> MyApp.Account.UserData.changeset(Map.put(data_attrs, :user_id, user.id))
        |> Repo.insert()
    end)
    |> Repo.transaction()
end
Run Code Online (Sandbox Code Playgroud)

因为这些图中的键既可以是原子也可以是线,所以我必须检查这些键。

但是表达

Map.put(user_attrs, :name, name)
Run Code Online (Sandbox Code Playgroud)

会导致错误

** (Ecto.CastError) expected params to be a map with atoms or string keys, got a map with mixed keys: %{:name => "John", "email" => "m@gmail.com"}
Run Code Online (Sandbox Code Playgroud)

如果键是字符串。

处理此问题是否有最佳实践?

Ale*_*kin 5

使用以下命令将所有键显式转换为字符串Kernel.to_string/1

data_attrs = for {k, v} <- data_attrs,
               do: {to_string(k), v}, into: %{}
Run Code Online (Sandbox Code Playgroud)


Dog*_*ert 5

我首先将所有键转换为原子,然后在各处使用原子。

def key_to_atom(map) do
  Enum.reduce(map, %{}, fn
    {key, value}, acc when is_atom(key) -> Map.put(acc, key, value)
    # String.to_existing_atom saves us from overloading the VM by
    # creating too many atoms. It'll always succeed because all the fields
    # in the database already exist as atoms at runtime.
    {key, value}, acc when is_binary(key) -> Map.put(acc, String.to_existing_atom(key), value)
  end)
end
Run Code Online (Sandbox Code Playgroud)

然后,通过此函数转换所有此类映射:

user_attrs = user_attrs |> key_to_atom
data_attrs = data_attrs |> key_to_atom
Run Code Online (Sandbox Code Playgroud)

现在您可以Map.put随时原子键。