Search code examples
elixirphoenix-frameworkecto

Shall I use changeset when update existing data in Ecto?


I have function in changeset pipeline to encrypt token.

def changeset(user, params \\ %{}) do
  user
  |> cast(params, [:id, :token]
  |> encrypt(:token)
end

Since this function is new, the old data is still not encrypted and I need to do it manually. Then I met the problem.

If I pass the original token to changeset, it will treat the value as if no changes. The encrypt in pipeline is not working and value not update.

If I encrypted the token and pass to changeset, it marked as changes, and encrypt function applied. However, it encrypted twice.

One clumsy way is to add a checking on encrypt to check if the token is encrypted or not. But remember before we passed new token to changeset, we still need to check if the token is encrypted. Which means, we do the checking twice.

So I'm looking for a simple solution if someone have the idea.

Cheers


Solution

  • Since this is a run-once action, I would go with a migration, that directly calls Ecto.Repo.update/2 (unfortunately I did not find a way to call Ecto.Repo.update_all/3 here unless you can do encrypt directly in the database.)

    defmodule My.Repo.Migrations.EncryptTokens do
      @moduledoc false
      use Ecto.Migration
    
      def up do
        for unencrypted <- from(...) |> Repo.all() do
          unencrypted
          |> Ecto.Changeset.change(token: encrypt(unencrypted.token))
          |> Repo.update()
        end
      end
      def down, do: raise "unrevertable"
    end
    

    Sidenote: you might also take a look at Ecto.Changeset.force_change/3 fwiw.