Ecto.Repo.update! is successful but returns changeset instead of struct, is it a bug or missing doc?

Hey guys, I finally got the green light to upgrade our Phoenix/Elixir stack and I’m in the middle of updating what needs to be updated.

The issue I found is that moving from Ecto 3.3.3 to 3.10.1 (didn’t think it was a big jump without being a major version change when upgrading Phoenix) I see parts of the logic broken because the code expects Ecto.Repo.update!/2 to return the struct (as I see it says on the docs) but instead I see it returns an Ecto Changeset. I see this is expected on a bunch of places on the code so I’d like to know if there was a change in behavior and Changeset is the new return and the docs are outdated (which means I need to update a bunch of places in the code) or if this is a bug and I should report it and change to a lower version of Ecto?

Thanks in advance.

Hey @stoic.alchemist can you show code that exhibits this behavior, and the IO.inspect output of the result? I have never seen this myself.

Sure thing, can’t show real code but I’ll emulate what’s happening:

iex> current_struct = %MyApp.MyModel{ key1: "value1"}
iex> updated_struct = 
         current_struct
         |> Ecto.Changeset.change(key: "value2")
         |> Ecto.Repo.update!
         |> IO.inspect

#Ecto.Changeset<
  action: nil,
  changes: %{key: "value2"},
  errors: [],
  data: #MyApp.MyModel<>,
  valid?: true
>

Please let me know if this is good enough, this is just a simplification of what’s happening, the real code goes around to a bunch of files (I’m also skipping the Model definition and such, I don’t think it’s important but I may be wrong)

Hey @stoic.alchemist is Ecto.Repo.update! a typo? That function is not callable directly. Not being able to show real code is definitely going to be a bit tricky.

If you can’t show your code, can you show an example that reproduces this behavior?

2 Likes

You are not supposed to call Ecto.Repo directly, you have to derive it via a macro in your own project. Then you use it like so: MyApp.Repo.update!.

1 Like

Most definitely, we do have our own Repo definition, I’ll try to show as much as I can:
Repo definition:

defmodule MyApp.Repo do
  use Ecto.Repo,
    otp_app: :my_app,
    adapter: Ecto.Adapters.Postgres
end

Model definition:

defmodule MyApp.MyModel do
  use Ecto.Schema
  import Ecto.Changeset
  alias MyApp.MyModel
  require Ecto.Query

  schema "my_models" do
    field :key, :string
    @timestamps_opts [type: :utc_datetime_usec]
    timestamps()
  end

  @fields [
    :key
  ]
 # A bunch of methods, around 200 lines or so
end

Now the Model it’s already initialized somewhere in the code:

# Somewhere inside a GenServer
my_model_struct
|> Ecto.Changeset.change(key: "value2")
|> MyApp.Repo.update!
|> IO.inspect() # Added to debug

This results in a:

#Ecto.Changeset<
  action: nil,
  changes: %{key: "value2"},
  errors: [],
  data: #MyApp.MyModel<>,
  valid?: true
>

Being that I’m doing the upgrade I thought this was a change in behavior but the docs say otherwise

:man_facepalming: Ok, I found the reason why… sorry for all the fuzz… tests are failing because the unit test file has a setup to use a “fake” Repo that returns the given data, which in this case is an Ecto.Changeset, so… sorry for the noise :sweat:

3 Likes

The docs indicate that this shouldn’t be possible, the return type of Ecto.Repo — Ecto v3.11.1 is Ecto.Schema.t(). Without code we can run this is pretty tricky to sort out, I’ve not seen anything like that before.

1 Like

That’s a pretty weird and IMO unproductive test setup. I recommend you remove that fake repo.

Yes, I agree, I’ll figure out why that’s there and remove it if it’s not needed, this can hide bugs (or in this case waste time searching for not-bugs )

2 Likes