我有以下函数定义,我正试图找到一种方法来简化参数,使它们都统一。
def update_user(%User{} = user, %{password: _} = attrs) do
attrs = Map.put(attrs, :password_reset_token, nil)
update_user(user, attrs, &User.password_changeset/2)
end
def update_user(%User{} = user, %{password_reset_token: _} = attrs), do:
update_user(user, attrs, &User.password_reset_changeset/2)
def update_user(user, attrs), do:
update_user(user, attrs, &User.changeset/2)
def update_user(user, attrs, changeset) do
user
|> changeset.(attrs)
|> Repo.update()
end
我认为程序员可能会把它搞砸,通过传入一个带有字符串键的映射(比如 %{"password"=> value}
),它会与 update_user 匹配/2
。那么也许我应该更新这些功能的参数?或者在 update_user(user, attrs)
上使用守卫以不匹配 w/%{"password"=> value}
?
与下面的对比,它直接从 Controller 函数中获取参数(def create(conn, params)
):
def authenticate_user(%{"email" => email, "access_token" => _}) do
case get_by(%{email: email}) do
%User{} = user ->
{:ok, user}
nil ->
{:error, :non_existent}
end
end
def authenticate_user(%{"email" => email, "password" => password}) do
with %User{} = user <- get_by(%{email: email}),
{:ok} <- verify_password(password, user.password_hash),
do: {:ok, user}
end
这些参数与具有字符串键的映射匹配。正如我提到的那样,我这样做是因为这样我就可以直接从 Controller 传递 params
并与传入的内容进行模式匹配(在本例中为各种登录方法)。是否有解决此类问题的标准 Phoenix 方法?
最佳答案
这是一种使用模式匹配的可能解决方案。
这可能不是最漂亮的解决方案,但我认为同时匹配字符串和原子键从来都不是。
def update_user(%User{} = user, attrs) do
user
|> user_changeset(attrs)
|> Repo.update()
end
def user_changeset(user, attrs) do
case attrs do
%{"password" => _} ->
User.password_changeset(user, Map.put(attrs, "password_reset_token", nil))
%{password: _} ->
User.password_changeset(user, Map.put(attrs, :password_reset_token, nil))
%{"password_reset_token" => _} ->
User.password_reset_changeset(user, attrs)
%{password_reset_token: _} ->
User.password_reset_changeset(user, attrs)
_ ->
User.changeset(user, attrs)
end
end
此外,您可以对所有情况下的函数参数进行模式匹配,但我认为在这种情况下会更加困惑。
另一种选择是首先始终将所有 attrs
映射转换为字符串键(相反,动态生成原子通常被认为是一个坏主意)。
关于elixir - Phoenix : pattern matching params in contexts (atoms and strings),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46588636/