Getting throwing “No function clause matching in TransactionController.create/2” error

Hi, I am new to Phoenix/Elixir and I am in the process of creating a simple API and I am trying POST, but I am getting a 400 bad request response. Phoenix throws the “no function clause matching in PoorNoMoreWeb.TransactionController.create/2” error. Delete is working, index is working, show is working.

I have checked out other solutions and they do not seem to be working. So any help would be much appreciated. Below is my create in my controller:

  def create(conn, %{"transaction" => transaction_params}) do
    case Expenses.create_transaction(transaction_params) do
      {:ok, _transaction} ->

        transactions = Expenses.list_transactions()
        render conn, "index.json", transactions: transactions

    end
  end

Here are my routes:

  scope "/api", PoorNoMoreWeb do
    pipe_through :api

    get "/transactions", TransactionController, :index
    get "/transactions/:id", TransactionController, :show
    post "/transactions", TransactionController, :create
    put "/transactions/:id", TransactionController, :update
    delete "/transactions/:id", TransactionController, :delete
  end

  scope "/", PoorNoMoreWeb do
    pipe_through :browser

    get "/*path", PageController, :index
  end

if anything else is needed, let me know!

This means it found the function, but no clause matched.

This means no implicit arguments, so it must be passed in the POST parameters.

This means it is mandating a parameter named "transaction".

However, we don’t know what’s being sent, so we need to see the form that is sending the POST request…
— OR —
Add a new catch-all clause for now and throw the params to see what they ‘actually’ are, like this create after your above create:

def create(conn, params), do: throw params

And see what gets logged. ^.^

In general right now though it is acting as programmed, it is expecting a "transaction" POST field (usually the form name) but it is not getting it, so it is rightfully saying that the client request is in error. :slight_smile:

4 Likes

Interesting okay. Below is the axios post I did:

  handleSubmit = (event) => {
    axios.post("http://localhost:4000/api/transactions", {
      headers: {"Content-Type": "application/json"},
        transactions: {
          category: this.state.newCategory,
          name: this.state.newName,
          amount: this.state.newAmount,
          merchant: this.state.newMerchant,
          description: this.state.newDescription,
        }
    })
    .then(response => {
      this.setState((prevState) => ({
        transactions: response.data.transactions,
        newCategory: "",
        newName: "",
        newAmount: "",
        newMerchant: "",
        newDescription: "",
        })
      )
    })
  };

Which is passed via prop to child component that has this form:

<Form onSubmit={handleFormSubmit} size="small">
        <Form.Dropdown
          value={category}
          placeholder="Category"
          selection
          closeOnChange
          options={options}
          onChange={handleCreateCategory}
        />
        <Form.Input
          value={name}
          fluid
          placeholder='Transaction Name'
          onChange={handleCreateName}
        />
        <Form.Input
          value={amount}
          fluid
          type="number"
          placeholder='Amount'
          onChange={handleCreateAmount}
        />
        <Form.Input
          value={merchant}
          fluid
          placeholder='Merchant'
          onChange={handleCreateMerchant}
        />
        <Form.Input
          value={description}
          fluid
          placeholder='Description'
          onChange={handleCreateDescription}
        />
        <Button
          color="green"
          type="submit"
          inverted
          fluid
        >
          Add 
        </Button>

That’s an API I’m not familiar with. ^.^;

I’m not seeing where these are getting names or ID’s, or if that is even valid HTML as it is? o.O

At this point I’d say add that catch-all handler and see what it is actually being given (be sure to remove it once debugging is complete). ^.^;

1 Like

okay thanks for taking the time to assist. So this is what postman throws out when I add that create in under my current create:

unhandled throw at POST /api/transactions
%{"transactions" => [%{"amount" => "5.00", "category" => "Test", "description" => "test", "merchant" => "Test", "name" => "Test"}]}

There’s your problem!

def create(conn, %{"transaction" => transaction_params}) do

You are expecting "transaction", but you are sending it "transactions", one or the other is incorrect. :slight_smile:

2 Likes

interesting! okay. when I change it to transaction, which makes sense that it needs to match, I get:

expected params to be a :map, got: `[%{"amount" => "5.00", "category" => "Test", "description" => "test", "merchant" => "Test", "name" => "Test"}]`

That sounds like a bug coming from the case Expenses.create_transaction(transaction_params) do line, might need to see that function! :slight_smile:

If possible supply the stacktraces too, they help to track down the issues to the areas that are needed to be seen. :slight_smile:

2 Likes

Where might I find the stacktraces so I can provide those?

If you are in debug mode using the debug page that pops up (the orange and light tan one) it’s on the rightside to the right of the related source code for the clicked-on part of the stacktrace. :slight_smile:

/me is getting off work now so may not be able to respond until tomorrow, depending

2 Likes

Thanks for your help, this is the stacktrace:

[debug] ** (Ecto.CastError) expected params to be a :map, got: `[%{"amount" => "5.00", "category" => "Test", "description" => "test", "merchant" => "Test", "name" => "Test"}]`
    (ecto) lib/ecto/changeset.ex:513: Ecto.Changeset.cast/6
    (poor_no_more) lib/poor_no_more/expenses/transaction.ex:17: PoorNoMore.Expenses.Transaction.changeset/2
    (poor_no_more) lib/poor_no_more/expenses.ex:54: PoorNoMore.Expenses.create_transaction/1
    (poor_no_more) lib/poor_no_more_web/controllers/transaction_controller.ex:18: PoorNoMoreWeb.TransactionController.create/2
    (poor_no_more) lib/poor_no_more_web/controllers/transaction_controller.ex:1: PoorNoMoreWeb.TransactionController.action/2
    (poor_no_more) lib/poor_no_more_web/controllers/transaction_controller.ex:1: PoorNoMoreWeb.TransactionController.phoenix_controller_pipeline/2
    (phoenix) lib/phoenix/router.ex:280: Phoenix.Router.__call__/2
    (poor_no_more) lib/poor_no_more_web/endpoint.ex:1: PoorNoMoreWeb.Endpoint.plug_builder_call/2
    (poor_no_more) lib/plug/debugger.ex:122: PoorNoMoreWeb.Endpoint."call (overridable 3)"/2
    (poor_no_more) lib/poor_no_more_web/endpoint.ex:1: PoorNoMoreWeb.Endpoint.call/2
    (phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:33: Phoenix.Endpoint.Cowboy2Handler.init/2
    (cowboy) /Users/talonhughes/Desktop/poor_no_more/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
    (cowboy) /Users/talonhughes/Desktop/poor_no_more/deps/cowboy/src/cowboy_stream_h.erl:296: :cowboy_stream_h.execute/3
    (cowboy) /Users/talonhughes/Desktop/poor_no_more/deps/cowboy/src/cowboy_stream_h.erl:274: :cowboy_stream_h.request_process/3
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
2 Likes

Ah cool, in that case needs the line of and the lines above and below for context of lib/poor_no_more/expenses/transaction.ex:17, it sounds like a list might be getting passed into a jsonb or map or embed or so?

1 Like

here is the whole file:

defmodule PoorNoMore.Expenses.Transaction do
  use Ecto.Schema
  import Ecto.Changeset

  schema "transactions" do
    field :amount, :decimal
    field :category, :string
    field :description, :string
    field :merchant, :string
    field :name, :string

    timestamps()
  end

  def changeset(transaction, attrs) do
    transaction
    |> cast(attrs, [:name, :category, :merchant, :amount, :description])
    |> validate_required([:name, :category, :merchant, :amount, :description])
  end
end

1 Like

Ah, I’m noticing that the maps inside the list map to the transaction, so I’m guessing it’s being passed as the list to attrs, in that case what is lib/poor_no_more/expenses.ex:54 and the surrounding lines?

1 Like

Also interestingly, your POST javascript stuff is sending:

        transactions: {
          category: this.state.newCategory,
          name: this.state.newName,
          amount: this.state.newAmount,
          merchant: this.state.newMerchant,
          description: this.state.newDescription,
        }

And yet it’s sending this object inside a list, is it that framework doing it? Why is it wrapping the object in a list?

1 Like

This is lines 52-56 of lib/poor_no_more/expenses.ex:54

  def create_transaction(attrs \\ %{}) do
    %Transaction{}
    |> Transaction.changeset(attrs)
    |> Repo.insert()
  end
1 Like

Yep, so it’s just passing it straight in.

I’m betting there’s a bug in that framework that is wrapping the object into the list, fixing that would be the ‘Proper’ fix, however as a workaround you can just change your function head from this:

def create(conn, %{"transaction" => transaction_params}) do

into:

def create(conn, %{"transaction" => [transaction_params]}) do

And I bet it would work. :slight_smile:

1 Like

It’s really really weird that the javascript lib is wrapping the transaction object into a list though, that really should be looked into… Maybe it is using FormData instead of json? If so can you switch it to a JSON mode?

1 Like

You’re right, that worked. I really appreciate you taking the time to help! I will looking into the framework side patch it on that end. Again, thank you!

2 Likes

Awesome! Glad to hear! :slight_smile:

2 Likes