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
因为这些地图中的键可以是原子和线,所以我必须检查这些键。
但表达式
Map.put(user_attrs, :name, name)
会导致错误
** (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"}
如果键是字符串。
处理这个问题有什么最佳做法吗?
答案 0 :(得分:4)
使用Kernel.to_string/1
明确地将所有键投射到字符串:
data_attrs = for {k, v} <- data_attrs,
do: {to_string(k), v}, into: %{}
答案 1 :(得分:3)
我首先将所有键转换为原子,然后在任何地方使用原子。
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
然后,转换通过此函数传递所有此类地图:
user_attrs = user_attrs |> key_to_atom
data_attrs = data_attrs |> key_to_atom
现在,您可以随时Map.put
原子键。
答案 2 :(得分:0)
@Dogberts解决方案很棒。但是,如果要扩展它并转换多维地图,可以使用一些递归:
def map_keys_to_atom(map) when is_map(map) do
Enum.reduce(map, %{}, fn
{key, value}, acc when is_atom(key) ->
Map.put(acc, key, map_keys_to_atom(value))
{key, value}, acc when is_binary(key) ->
Map.put(acc, String.to_existing_atom(key), map_keys_to_atom(value))
end)
end
def map_keys_to_atom(map), do: map
答案 3 :(得分:0)
我使用了此递归解决方案。适用于嵌套地图,也适用于混合内容(原子键和字符串键混合使用)
defmodule SomeProject.MapHelper do
def to_params(map) do
Enum.map(map, &process_pair/1) |> Enum.into(%{})
end
defp process_pair({k, v}) do
{"#{k}", process_value(v)}
end
defp process_value(v) when is_map(v) do
to_params(v)
end
defp process_value(v), do: v
end
这是测试
defmodule CrecerInversiones.MapHelperTest do
use CrecerInversiones.DataCase
alias CrecerInversiones.MapHelper
describe "to_params" do
test "convert atom keys to strings" do
params = MapHelper.to_params(%{a: "hi params"})
assert params == %{"a" => "hi params"}
end
test "convert nested maps" do
params = MapHelper.to_params(%{a: "hi params", b: %{z: "nested map"}})
assert params == %{"a" => "hi params", "b" => %{"z" => "nested map"}}
end
test "accept mixed content" do
params = MapHelper.to_params(%{"a" => "hi params", "b" => %{z: "nested map"}})
assert params == %{"a" => "hi params", "b" => %{"z" => "nested map"}}
end
end
end