Help figuring out why Ecto validation fails in Ecto.Multi transaction

I’ve got a very weird error happening which I hope someone smarter than me can figure out :smile:

The thing is that I’m having trouble inserting some simple data on my production environment.

Here’s the code

  defp do_create_pull_request(remote_pull_request, board) do
    Ecto.Multi.new
    |> insert_pull_request(remote_pull_request, board)
    |> Repo.transaction
  end

  defp insert_pull_request(multi, remote_pull_request, board) do
    Ecto.Multi.run(multi, :pull_request, fn(_) ->
      attrs = %{
        title: remote_pull_request.title,
        description: remote_pull_request.description,
        remote_id: remote_pull_request.id
      }
      git_repository = Pace.GitRepositories.find_by_uuid_in_board(
        remote_pull_request.repository_uuid,
        board
      )

      Pace.PullRequest.changeset(%Pace.PullRequest{}, attrs)
      |> Ecto.Changeset.put_assoc(:git_repository, git_repository)
      |> Repo.insert
    end)
  end

I’ve removed other operations in the Multi to simplify the code. This code does result in a validation error on the PullRequest.

Here’s the schema for the PullRequest

defmodule Pace.PullRequest do
  use Pace.Web, :model

  @timestamps_opts [type: :utc_datetime]

  schema "pull_requests" do
    field :remote_id, :integer
    field :title, :string
    field :description, :string
    field :state, :string
    belongs_to :git_repository, Pace.GitRepository
    has_many :pull_request_assignees, Pace.PullRequestAssignee
    has_many :assignees, through: [:pull_request_assignees, :board_member]

    has_many :issue_pull_requests, Pace.IssuePullRequest
    has_many :cards, through: [:issue_pull_requests, :card]

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:remote_id, :title, :description, :state])
    |> validate_required([:remote_id, :title])
  end
end

When running the insert code shown in the beginning I get the following validation error:

#Ecto.Changeset<action: :insert,
     changes: %{description: "references #140", git_repository_id: 12, id: "",
       remote_id: 97, title: "new readme test"},
     errors: [issue_pull_requests: "is invalid"], data: #Pace.PullRequest<>,
     valid?: false>

Since I am not doing anything related to the issue_pull_requests this makes no sense. But let me just make things even more weird :smile:

Because it turns out, if I construct an Ecto.Multi chain in iex on production it does work.

So doing the following on production iex:

    multi =  Ecto.Multi.new
    multi = Ecto.Multi.run(multi, :pull_request, fn(_) ->
      attrs = %{
        title: remote_pull_request.title,
        description: remote_pull_request.description,
        remote_id: remote_pull_request.id
      }
      git_repository = Pace.GitRepositories.find_by_uuid_in_board(
        remote_pull_request.repository_uuid,
        board
      )

      Pace.PullRequest.changeset(%Pace.PullRequest{}, attrs)
      |> Ecto.Changeset.put_assoc(:git_repository, git_repository)
      |> Repo.insert
    end)
    Repo.transaction(multi)

Which is the same code. I just removed the named functions. Data is the same and all. Then it works as expected. What is going on? :smile:

Moving the working chain inside a named function of the module does not work though. It gives me a validation error once again.

So a little status update. I’ve managed to reproduce the error on my dev environment…

1 Like

It seems like I’m running into a bug of some sort which is not necessarily caused by my code. The only way for me to reproduce the bug on my dev environment is to do

mix do deps.clean --all, deps.get, deps.compile

After that I can reproduce the bug in iex. But if I make a change to the module and reload it then the validation error disappears and the record is inserted properly.

I’m lost on how to proceed…

The same goes for production environment. If I attach to the running process and reload the module with the code in it (no changes to the code), everything works as expected…

I’m sooo stupid… Had an old module named the same as the model (from back when I was prototyping the idea without a database). I didn’t notice the warning regarding it being defined twice. So removing the duplicate fixed the issue.

3 Likes