Association isn't loading using Ecto.Multi

Hi, I’m totally new on Elixir.
I’m building an Rest using Elixir and Phoenix, in this app I have some tables that has reference to another.
“User” has an “Account” that has “Balance”. I’m trying to activate this account updating some informations about account and creating balance. After some researches I’ve found Ecto.Multi and it seems to fit my purpose: to insert and to update protecting the process with an transaction. Despite the information are beeing insert and update no DB, I’m receiving an error on load association. I’ve tried to use preload, but I may be missing something 'cause it wasn’t worked. How it’s the proper way to doing associations on these cases?

Error message:
" {:ok, %{account_balance: %Bank.Account.AccountBalance{meta: #Ecto.Schema.Metadata<:loaded, “account_balance”>, account_register: #Ecto.Association.NotLoaded, account_register_id: 58, balance_amount: #Decimal<1000>, id: 86, inserted_at: ~U[2019-11-28 23:12:33.476000Z], updated_at: ~U[2019-11-28 23:12:33.476000Z]}, account_register: %Bank.Account.AccountRegister{meta: #Ecto.Schema.Metadata<:loaded, “account_register”>, account_balance: #Ecto.Association.NotLoaded, account_number: “08335843”, active: true, agency_number: “3487”, id: 58, inserted_at: ~U[2019-11-28 23:12:28.841000Z], opening_date: ~D[2019-11-28], updated_at: ~U[2019-11-28 23:12:33.489000Z], user: #Ecto.Association.NotLoaded, user_id: 1}}})"

#Methods that deal with DB

def get_account_register!(id), do: Repo.get!(AccountRegister, id)
def activate_account_register(%AccountRegister{} = account_register) do
    actual_date = Date.utc_today()

    account_register = Bank.Repo.get!(AccountRegister, account_register.id)

    balance_register = %AccountBalance{}

    **Ecto.Multi.new()**
    **|> Ecto.Multi.insert**(
      :account_balance,
      AccountBalance.changeset(balance_register, %{
        account_register_id: account_register.id,
        balance_amount: 1000
      })
      # |> Repo.preload(:user)
    )
    **|> Ecto.Multi.update**(
      :account_register,
      AccountRegister.changeset(account_register, %{
        active: true,
        opening_date: actual_date,
        user_id: account_register.user_id
      })
      # |> Repo.preload(:account_balance)
    )
    **|> Repo.transaction()**
  end

#User schema - Bank.Auth.User

schema "users" do
    field :email, :string
    field :is_active, :boolean, default: false
    field :password, :string, virtual: true
    field :password_hash, :string
    has_many :auth_tokens, Bank.AuthToken
    has_one :account_register, Bank.Account.AccountRegister
    timestamps(type: :utc_datetime_usec)
end

#Account schema - Bank.Account.AccountRegister

schema "account_register" do
    belongs_to :user, Bank.Auth.User
    field :account_number, :string
    field :active, :boolean, default: false
    field :agency_number, :string
    field :opening_date, :date
    has_one :account_balance, AccountBalance

    timestamps()
end

#Balance schema - Bank.Account.AccountBalance

schema "account_balance" do
    belongs_to :account_register, Bank.Account.AccountRegister
    field :balance_amount, :decimal

    timestamps()
end

#Controller

def activate(conn, %{"account_id" => account_id}) do
    account_register = Account.get_account_register!(account_id)

    with {:ok, %AccountRegister{} = account_register} <-
           Account.activate_account_register(account_register) do
      send_resp(conn, :created, "")
    end
 end

Updating:

I’ve made some changes on controller and after transaction, and it worked!
I must always treat transactions like this (checking the return)?

#Controller

def activate(conn, %{"account_id" => account_id}) do
    account_register = Account.get_account_register!(account_id)

    with {:ok, %AccountRegister{} = account_register} <-
           Account.activate_account_register(account_register) do
      render(conn, "show.json", account_register: account_register)
    end
  end
end

#Method that deal with DB

def activate_account_register(%AccountRegister{} = account_register) do
    actual_date = Date.utc_today()

    account_register = Bank.Repo.get!(AccountRegister, account_register.id)

    balance_register = %AccountBalance{}

    Ecto.Multi.new()
    |> Ecto.Multi.insert(
      :account_balance,
      AccountBalance.changeset(balance_register, %{
        account_register_id: account_register.id,
        balance_amount: 1000
      })
    )
    |> Ecto.Multi.update(
      :account_register,
      AccountRegister.changeset(account_register, %{
        active: true,
        opening_date: actual_date,
        user_id: account_register.user_id
      })
    )
    |> Repo.transaction()
    |> case do
      {:ok, %{account_register: account_register}} ->
        {:ok, account_register}

      {:error, _, reason, _} ->
        {:error, reason}
    end
  end

Not sure I understand your question exactly. But yes, you must always feed the Ecto.Multi to Repo.transaction (after you are done constructing the multi). From the official docs:

Ecto.Multi makes it possible to pack operations that should be performed in a single database transaction and gives a way to introspect the queued operations without actually performing them.

Ecto.Multi is an accumulator of DB operations. They must be executed together in a single DB transaction.

1 Like

Thanks, @dimitarvp. The problem was that I didn’t do that check covered on case clause, so the return to controller was broken.