(KeyError) key :id not found in: %{}

Bit of a beginner here. Pardon the length of my question.

I’m able to successfully insert my data but am lost as to what :id is being expected in my create_appraisal_event that I’m unable to provide. My item_id is present and looks correct in the parameters I’m sending to my AppraisalController.ex.

Here are logs from the POST through to the 500 error returned (the 28 and 240 are estate_id and item_id respectively:

[info] POST /api/estates/28/items/240/appraisals
appraisal_controller create started
Create Appraisal Reached
[debug] Processing with EstateClarityWeb.AppraisalController.create/2
  Parameters: %{"appraisal" => %{"appraisal_images" => [], "appraised_value" => "12312", "date" => "1993-01-01"}, "estate_id" => "28", "item_id" => "240"}
  Pipelines: [:api, :require_auth]
[debug] QUERY OK db=0.2ms
begin []
[debug] QUERY OK db=2.4ms
INSERT INTO "appraisals" ("appraised_value","date","inserted_at","updated_at") VALUES ($1,$2,$3,$4) RETURNING "id" [12312.0, {1993, 1, 1}, {{2020, 5, 13}, {7, 9, 18, 186180}}, {{2020, 5, 13}, {7, 9, 18, 186185}}]
Create Appraisal Event Reached
[debug] QUERY OK db=0.3ms
rollback []
[info] Sent 500 in 16ms
[error] #PID<0.580.0> running EstateClarityWeb.Endpoint (cowboy_protocol) terminated
Server: 0.0.0.0:4000 (http)
Request: POST /api/estates/28/items/240/appraisals
** (exit) an exception was raised:
    ** (KeyError) key :id not found in: %{"appraisal_images" => [], "appraised_value" => "12312", "date" => "1993-01-01"}

Create_appraisal runs fine but the create_appraisal_event is expecting an id that I don’t know how to provide properly.

My appraisal controller seems to be working fine. Here’s the create function inside of the appraisal controller:

def create(conn, %{"appraisal" => appraisal_params, "item_id" => item_id}) do
      IO.puts("appraisal_controller create started")
      current_user = conn.assigns.current_user

      result =
        appraisal_params
        |> Estates.create_appraisal(appraisal_params)
        |> Map.put("item_id", item_id)
        |> Map.put("heir_id", current_user.id)

      case result do
        {:ok, appraisal} ->
          appraisal = Estates.get_appraisal(appraisal.id)

          Estates.notify_appraisal_created(appraisal)
          render(conn, "show.json", appraisal: appraisal)

        errors ->
          errors
      end
    end

Then in the Estates.ex I have the create_appraisal function which calls create_appraisal_event… and that’s when I get lost. The create_appraisal_event function creates a blob which stores relevant aspects of the creation so we can show the end users a shorthand version of activity in the application… but what id is missing here?

def create_appraisal(params, user) do
    IO.puts("Create Appraisal Reached")
    changeset =
      %Appraisal{}
      |> Appraisal.changeset(params)

    result =
      Multi.new()
      |> Multi.insert(:appraisal, changeset)
      |> Multi.run(:event, &create_appraisal_event(changeset, user, &1))
      |> Repo.transaction()

    case result do
      {:ok, %{appraisal: appraisal}} ->
        {:ok, appraisal}

      {:error, :appraisal, changeset, _} ->
        {:error, changeset}
    end
  end

  defp create_appraisal_event(changeset, user, %{appraisal: appraisal}) do
    IO.puts("Create Appraisal Event Reached")

    params = %{
      type: "appraisal",
      action: "create",
      item_id: appraisal.id,
      data_id: appraisal.id,
      user_id: user.id,
      changes: new_changes(changeset)
    }

    create_event(params)
  end

Any help would be greatly appreciated. Thank you.

This looks highly dubious, as You pass the same parameter twice… and the second params should be a user.

And I guess the error is in

&create_appraisal_event(changeset, user, &1)

because user is not a user here.

2 Likes

Can you show your create_event function?

2 Likes

It fails before… it fails here

To avoid such mistake, You could pattern match your parameters.

%User{} = user
2 Likes

Oh, I always do that. I love Elixir but I’m using any static typing that I can, everywhere. Really easy to make a mistake in a dynamic language.

3 Likes

Here’s my create_event function:

  def create_event(params) do
    %Event{}
    |> Event.changeset(params)
    |> Repo.insert()
  end

And the Event.ex schema:

defmodule EstateClarity.Event do
  use Ecto.Schema
  import Ecto.Changeset, warn: false
  alias EstateClarity.{Item, User, Appraisal}

  @type t :: %__MODULE__{}
  schema "events" do
    field :type, :string
    field :action, :string
    field :data_id, :integer
    field :changes, {:array, :map}
    belongs_to :item, Item
    belongs_to :user, User
    timestamps()
  end

  def changeset(item, attrs) do
    item
    |> cast(attrs, [:type, :action, :data_id, :changes, :item_id, :user_id])
    |> validate_required([:type, :action, :data_id, :changes, :item_id, :user_id])
  end
end

I don’t know if this is relevant, but I have my AppraisalController nested as follows in Router.ex:

resources "/estates", EstateController, only: [:create, :show, :index, :update] do
      resources "/items", ItemController, only: [:create, :show, :update] do
        resources("/appraisals", AppraisalController, only: [:create, :show, :update])
        resources("/comments", ItemCommentController, only: [:create])
      end
    end

Have you tried @kokolegorille’s suggestion?

2 Likes

This was the solution! I didn’t reexamine what I was passing to create_appraisal. Passing the user data fixed it! This forum is so helpful! Thank you.

1 Like