Elixir, Ecto pattern matching conditional with db

2019-09-16 02:07发布

问题:

If the record does not exist, I would expect this conditional to create it, but it does not.... nil is returned.

case Repo.get_by(User, %{email: "hulk@hogan.com"}) do
    struct ->
      struct
    nil ->
      params = Map.merge(%{email: "hulk@hogan.com"}, %{password: "password"})
      Repo.insert!(User.changeset(User.__struct__, params))
end

# returns nil.... huwutt???

However, if I change the ordering of the condition, it works. What am I missing here?

case Repo.get_by(User, %{email: "hulk@hogan.com"}) do
    nil ->
      params = Map.merge(%{email: "hulk@hogan.com"}, %{password: "password"})
      Repo.insert!(User.changeset(User.__struct__, params))
    struct ->
      struct
end

# returns a set of 24" pythons, brother.... huzah!

回答1:

According to the documentation

case allows us to compare a value against many patterns until we find a matching one:

In another word, the first matching case will run and case will not proceed further.

In your first example, the first case will always be matched since you are not providing any guards, and as such, struct will bind to nil. Your second approach solves the problem because you're pattern matching a specific case first, and then defaulting to a general case by binding the evaluation of case to struct.

Also note that you can use guards in your first case to make sure that the value of struct is a map as outlined here.

case Repo.get_by(User, %{email: "hulk@hogan.com"}) do
    struct when is_map(struct) ->
      struct
    nil ->
      params = Map.merge(%{email: "hulk@hogan.com"}, %{password: "password"})
      Repo.insert!(User.changeset(User.__struct__, params))
end