Elixir Function Succeeding but not Returning Value

Hi all, I’m not clear why this function is not returning a value:


  def confirm_email!(user = %User{email_confirmed: false}) do
    confirm = User.changeset_confirm_email(user, true)
    case Repo.update(confirm) do
      # Updated with success
      {:ok, updated_user} ->
        Logger.debug("update success")
        {:ok, updated_user}
      {:error, _, reason, _} ->
        Logger.debug("update error")
        Logger.error(reason)
        {:error, reason}
    end
    Logger.debug("end of confirm_email")
  end

I see in the log:

[debug] QUERY OK db=7.2ms queue=5.3ms idle=633.5ms
UPDATE "users" SET "email_confirmation_token" = $1, "email_confirmed" = $2, "updated_at" = $3 WHERE "id" = $4 [nil, true, ~U[2020-08-19 02:18:18Z], 1]
[debug] update success
[debug] end of confirm_email
[info] Sent 204 in 85ms

The side-effect is that the page is not routing from the caller:

  def confirm_email(conn, %{"token" => token}) do
    Logger.debug("confirm_email token: #{token}")
    case Accounts.confirm_email!(token) do
      {:ok, updated_user} ->
        Logger.debug("updated_user: #{updated_user}")
        conn
        |> Guardian.Plug.sign_in(updated_user)
        |> put_flash(:info, gettext("Welcome %{user_name}!", user_name: updated_user.name))
        |> redirect(to: Routes.page_path(conn, :index))
      {:error, reason} ->
        Logger.debug("error reason: #{reason}")
        json(put_status(conn, 404), %{error: "invalid_token"})
    end
  end

It’s just not clear to me why it doesn’t appear to be returning the tuple I expect.
Michael

You are returning the result of the last Logger.debug expression which is :ok

If you really wanted the “end of confirm_email” logging you need to return the result after.

  def confirm_email!(user = %User{email_confirmed: false}) do
    confirm = User.changeset_confirm_email(user, true)
    result = case Repo.update(confirm) do
      # Updated with success
      {:ok, updated_user} ->
        Logger.debug("update success")
        {:ok, updated_user}
      {:error, _, reason, _} ->
        Logger.debug("update error")
        Logger.error(reason)
        {:error, reason}
    end
    Logger.debug("end of confirm_email")
    result
  end
3 Likes

The last statement in your function is Logger.debug call, so it’s return will be your return value

1 Like

I updated it as you suggested and there was no change.

And you have recompiled and reloaded the module?

Can you try running your Accounts.confirm_email!/1 function in the repl and make sure it returns what it is supposed to?

That is weird, you should at least hit one of the case or get an error.
Do you have any other confirm_email function that is calling Accounts.confirm_email()?

When running in the repl it seems to return the result:

iex(3)> result = Accounts.confirm_email!("Z-0KiKgE6s2-iL5FXQMZj8G_PH5RalMc") 
[debug] QUERY OK source="users" db=6.2ms queue=2.7ms idle=147.2ms
SELECT u0."id", u0."username", u0."email", u0."encrypted_password", u0."name", u0."uuid", u0."picture_url", u0."reputation", u0."today_reputation_gain", u0."locale", u0."achievements", u0."newsletter", u0."newsletter_subscription_token", u0."is_publisher", u0."completed_onboarding_steps", u0."fb_user_id", u0."email_confirmed", u0."email_confirmation_token", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."email_confirmation_token" = $1) ["Z-0KiKgE6s2-iL5FXQMZj8G_PH5RalMc"]
%Db.Schema.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  achievements: [1],
  actions: #Ecto.Association.NotLoaded<association :actions is not loaded>,
  completed_onboarding_steps: [],
  email: "yu@iop.com", 
  email_confirmation_token: "Z-0KiKgE6s2-iL5FXQMZj8G_PH5RalMc",
  email_confirmed: false,
  encrypted_password: "$2b$12$dCiQzhUm/KJhkGR39Ll1uuMzh0mpLCsBuouIlo7/ldfWY5hWKmAi6",
  fb_user_id: nil,
  id: 2,
  inserted_at: ~U[2020-08-19 13:14:36Z],
  is_publisher: false,
  locale: "en",
  name: "qwertyu",
  newsletter: true,
  newsletter_subscription_token: "c4bf6c119d412d5bb096efee470910cb",
  orgs: #Ecto.Association.NotLoaded<association :orgs is not loaded>,
  password: nil,
  picture_url: %{file_name: "2.png", updated_at: ~N[2020-08-19 13:14:38]},
  reputation: 0,
  today_reputation_gain: 0,
  updated_at: ~U[2020-08-19 13:14:38Z],
  username: "qwertyu",
  uuid: "116fafb9-d20d-4e1a-b19a-8c4a462cdd3a"
}
[debug] changeset_confirm_email: true
[debug] QUERY OK db=6.9ms queue=6.3ms idle=176.9ms
UPDATE "users" SET "email_confirmation_token" = $1, "email_confirmed" = $2, "updated_at" = $3 WHERE "id" = $4 [nil, true, ~U[2020-08-19 13:16:14Z], 2]
[debug] update success
{:ok,
 %Db.Schema.User{
   __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   achievements: [1],
   actions: #Ecto.Association.NotLoaded<association :actions is not loaded>,
   completed_onboarding_steps: [],
   email: "yu@iop.com",
   email_confirmation_token: nil,
   email_confirmed: true,
   encrypted_password: "$2b$12$dCiQzhUm/KJhkGR39Ll1uuMzh0mpLCsBuouIlo7/ldfWY5hWKmAi6",
   fb_user_id: nil,
   id: 2,
   inserted_at: ~U[2020-08-19 13:14:36Z],
   is_publisher: false,
   locale: "en",
   name: "qwertyu",
   newsletter: true,
   newsletter_subscription_token: "c4bf6c119d412d5bb096efee470910cb", 
   orgs: #Ecto.Association.NotLoaded<association :orgs is not loaded>,
   password: nil,
   picture_url: %{file_name: "2.png", updated_at: ~N[2020-08-19 13:14:38]},
   reputation: 0,
   today_reputation_gain: 0,
   updated_at: ~U[2020-08-19 13:16:14Z],
   username: "qwertyu",
   uuid: "116fafb9-d20d-4e1a-b19a-8c4a462cdd3a"
 }}

I should mention there are two other confirm_email() functions, here are them all:

  def confirm_email!(token) when is_binary(token),
    do: confirm_email!(Repo.get_by(User, email_confirmation_token: token))

  def confirm_email!(user = %User{email_confirmed: true}),
    do: user

  def confirm_email!(user = %User{email_confirmed: false}) do
    confirm = User.changeset_confirm_email(user, true)
    result = case Repo.update(confirm) do
      # Updated with success
      {:ok, updated_user} ->
        Logger.debug("update success")
        {:ok, updated_user}
      {:error, _, reason, _} ->
        Logger.debug("update error")
        Logger.error(reason)
        {:error, reason}
    end
    result
  end

[EDIT]
adding referred functions(User):

  def changeset_confirm_email(model, is_confirmed) do
    IO.inspect(model)
    Logger.debug("changeset_confirm_email: #{is_confirmed}")

    model
    |> change(email_confirmed: is_confirmed)
    |> generate_email_verification_token(is_confirmed)
  end
  @token_length 32
  defp generate_email_verification_token(changeset, false),
    do:
      put_change(
        changeset,
        :email_confirmation_token,
        Db.Utils.TokenGenerator.generate(@token_length)
      )

  defp generate_email_verification_token(changeset, true),
    do: put_change(changeset, :email_confirmation_token, nil)

That might be the case, see my response to @cmkarlsson

The confirm_email function you showed is under Accounts. What about the confirm_email function under the controller?

I deleted my response by mistake.

You were right, the root problem was the router. I was calling
get("/confirm_email/:token", UserController, :confirm_email) # for json api

and not:

get("/confirm_email/:token", SignupController, :confirm_email)

SignupController:

  def confirm_email(conn, %{"token" => token}) do
    Logger.debug("confirm_email token: #{token}")
    case Accounts.confirm_email!(token) do
      {:ok, updated_user} ->
        conn
        |> Guardian.Plug.sign_in(updated_user)
        |> put_flash(:info, gettext("Welcome %{user_name}!", user_name: updated_user.name))
        |> redirect(to: Routes.page_path(conn, :index))
      {:error, reason} ->
        Logger.debug("error reason: #{reason}")
        json(put_status(conn, 404), %{error: "invalid_token"})
    end
  end

Works fine. Thanks for the help. Guilty of programming while tired.
Leaving this here so others might benefit.

3 Likes