Need help with changeset errors, I have no idea how to approach this problem

Hi,

I have this project that I’m building as I’m learning Phoenix and I got stuck yesterday and have no idea how to fix or even approach this problem.

So, I have services and those services have orders. After the order is completed, both the seller and buyer can leave reviews. Reviews are in the same db row.

First, there is a template which contains a form

<.form :let={f} for={@review_changeset} action={ ~p"/orders/#{@order.slug}" } as="order" method="PATCH">
 <%= label f, :review %>
 <%= textarea f, :review %>
 <%= error_tag f, :review %

  <%= submit "Submit" %>
</.form>

on submit, update function is run which takes the review and then, if the user submitting the review is buyer, it updates attrs with review_buyer, and it does the same for the seller. The reason for that is that the review schema contains review_buyer and review_seller fields

def update_order(%User{} = user, %Order{} = order, %{"order" => %{"review" => review}} = attrs) do
    attrs =
      cond do
        user.id == order.buyer_id ->
          Map.put(attrs, "review", %{"review_buyer" => review})
        user.id == order.seller_id ->
          Map.put(attrs, "review", %{"review_seller" => review})
        true ->
          attrs
        end

    order
    |> Order.review_changeset(attrs)
    |> Repo.update()

Changeset

def changeset(%Review{} = review, attrs) do
    review
    |> cast(attrs, [:review_buyer, :review_seller])
    |> validate_review()
  end

  defp validate_review(changeset) do
    cond do
      get_change(changeset, :review_buyer) ->
        changeset
        |> validate_length(:review_buyer, min: 10, max: 250)

      get_change(changeset, :review_seller) ->
        changeset
        |> validate_length(:review_seller, min: 10, max: 250)

      true ->
        changeset
      end
  end

Now, this all works, but the problem is showing errors. If there is a changeset error, error is set to review_buyer or review_seller field which does not exist in the form. Form has only review field. How do I transform errors to show for review field? Or should I take a completely different approach here?

I think your gut is onto something here.

Since you already know whether the logged in user is a buyer or seller, it makes sense to just directly render the appropriately specified textarea input.

# assuming a `review_type` assign is set on LiveView/Component `mount` to either `:review_buyer` or `:review_seller`

<.form :let={f} for={@review_changeset} action={ ~p"/orders/#{@order.slug}" } as="order" method="PATCH">
 <%= label f, @review_type %>
 <%= textarea f, @review_type %>
 <%= error_tag f, @review_type %

  <%= submit "Submit" %>
</.form>
2 Likes

Yeah, I did it that way in the end, it’s the only way I could make it work properly. Thanks

In this case the solution presented is simple enough, but in more complex forms (or forms where you have to massage the data a lot before saving) you can use a changeset for the form where you can handle the errors and another changeset for the persistence layer.

  def form_changeset(%Review{} = review, attrs) do
    review
    |> cast(attrs, [:review])
    |> validate_length(:review, min: 10, max: 250)
  end

<.form :let={f} for={@review_changeset} action={ ~p"/orders/#{@order.slug}" } as="order" method="PATCH">
 <%= label f, :review %>
 <%= textarea f, :review %>
 <%= error_tag f, :review %

  <%= submit "Submit" %>
</.form>

Should show the errors in the form when validating, then, before you save you should get if the user is seller or buyer and update the map received from the form accordingly to pass to the changeset that will validate it before saving.

2 Likes