Ecto Changeset and Form validation and update using PUT

I have managed to list the records from my DB to the front end but not able to update , insert and apply form validation error please suggest

http://localhost:4000/admin/income-cards/1/edit

Has form as shown below:

<%= form_for @changeset, “/admin/income-cards/#{card.id}”, [method: “put”], fn f -> %>

<%= textarea f, :“card_description”, id: “card_description”, value: “#{card.card_description}”, rows: “30” , columns: “30” %>
<%= error_tag f, :card_description %>

<%= text_input f, :“card_income”, id: “card_income”, placeholder: “Name”, value: “#{card.card_income}” %>
<%= error_tag f, :card_income %>

<%= text_input f, :“card_expense”, id: “card_expense”, placeholder: “Name”, value: “#{card.card_expense}” %>
<%= error_tag f, :card_expense %>

<% end %>

The function to shown above form in my controller is

def edit_income_card(conn, %{“id” => id}) do
IO.inspect id
query = from c in GameOfFortune.Cards.Card,
where: c.id == 1,
order_by: [
asc: c.id
],
select: struct(
c,
[:id, :card_type_id, :card_description, :card_income, :card_expense]
)
cards = GameOfFortune.Repo.all(query)
IO.inspect cards
changeset = Card.changeset(Enum.at(cards, 0), %{} )
render(conn, “editcard.html”, [cards: cards, changeset: changeset])
end

def update_income_card(conn, %{“id”=>id,“card” => card_params}) do
IO.inspect “TESTING 123”
IO.inspect card_params
IO.inspect id
#IO.inspect cards_params
conn |> put_flash(:error, “Oops some validation error.”) |> redirect(to: Routes.admin_path(conn, :edit_income_card, id))
end

My routes are as shown below:

show_income_cards_path GET /admin/income-cards
GameOfFortuneWeb.AdminController :show_income_cards
admin_path GET /admin/income-cards/:id/edit
GameOfFortuneWeb.AdminController :edit_income_card
admin_path PUT /admin/income-cards/:id
GameOfFortuneWeb.AdminController :update_income_card
admin_path PATCH /admin/income-cards/:id
GameOfFortuneWeb.AdminController :update_income_card

What changeset should I pass in my edit_income_card function so that if user left any field any field blank like “description” or “income” or “expense” then I want to show validation error also if no validation error then I have to pass update the value to my database using the function update_income_card() which using PUT method as shown in my routes above.

Not clear what code should I write to validate and to update data to the database

Please suggest how to apply changeset for update action, insert action and for validation

I think people will see clearlier your code if you use the forum code formater (Ctrl+Shift+C on selected text).

In my humble opinion, your code would gain also a lot of readability if you use Phoenix contexts. If you don’t already followed the official guide, please try it and you’ll find how Contexts are really useful.

Also the routing guide as well as phoenix generators like Context generator, html genrator may be helpful. The generators are good prototype tools to generate code that you can cheat on to write your own. ^^

I have successfully updated my code but now getting one issue in case of edit if user does not follow any validation, then I throw a validation error but how to manage the form old value due to which he failed validation. Lets for example in case of edit we show data from DB for every form field and in one field if user clears the form field content and submit the form I show him error message like “can’t be blank” but on the form user again see the data from DB not the value the client has entered how to show the last form data user has entered while validating the form

<%= text_input f, :“card_expense”, value: “{card.card_expense}” %>

Isn’t it because you force the input to be prepopulated with the value of card.card_expense?

This is okay for new form, but for edit form` if you don’t want the behaviour you’re describing, you should not pre-populate the input value attr. If you create your update changeset like shown on most guides, you won’t have to pre-populate by hand your form inputs.

1 Like

I would definitely look at the methods that is used on the form data. Because, its more secure to use POST methods instead of PUT, and I didn’t think cowboy supports that method. GET method is ok, for things that have not been stored by a user. POST method is the safest, as xml based hacks are all 100% urlencoded. If you don’t use a web server method. Its better to disable the unused methods so no one uses it as a attack vector.

The one thing I like about this, is that someone can’t mount the same attack like they can on ASP or PHP because there isn’t a destination they can attack to. So the only issues is xss and injection vectors from user input only while the consumer is using the application.

Its neat that there is a web based program programming language. But people need to get out of the thinking of “Website” and think along the lines of programming an electronic appliance like a Roku box.

So you have any references about this claim?

As far as I remember, both are pretty equivalent from HTTP point of view, just a different verb for otherwise equivalent requests. The main difference between both is the semantic…

Even though both procedures are considered unsafe, along with DELETE method, because they alter data, the use of an HTTP PUT method versus an HTTP POST method should be based
on the idempotent aspect of that operation. That is, if the operation is idempotent, then use the HTTP POST method. If the operation is non idempotent, then use the HTTP PUT method. But overall, things that end up on the url line in the browser can be manipulated. So its better to hide that communication from the url line. Technically, PUT, should be only used if the destination for the data can be reached through the url. So you wouldn’t want to use PUT for database entry. I remembered an issue where we blacklisted an e-commerce software on mine and other ISP where PUT and GET methods was used, and the combination of the WordPress XML-PHP module ended up with a security flaw that allowed anyone to go to the site, make and run a special XML query by opening up the browser’s HTML inspector, and get the database’s credentials and data.

Beyond this, I’m wonder how it handles the different error responses, and if its linked to the application crashes.

Mainly because one of the error catches of PUT is if the destination is not there, is to create the resource item. Post automatically assumes its a new item/resource, and doesn’t throw this error internally.

Sorry, I don’t get it. What does make one or the other more unsafe than the other?

Both require you (usually) to specify the resource in the URL and the data to change/create in the request body. None of the actual data should be in the URL.

Also your backend should properly authorise requests like those.

So whatever you are talking about is not an issue of HTTP verbs but the applications backend.

ok, I’ll try to simplify it. When PUT, POST, and DELETE was added, It was understood that PUT and DELETE was to be used for updating. Like an existing web page object rendered, or editing the HTML file itself. POST was to be used for adding Data. Like uploading files, and adding text to a database.

So which method is best? And how can someone sanitize a PUT object when its not a resource until it throws its own internal error?

Let me say again, whatever insecurity you see in any of the verbs (you still haven’t said what makes them insecure), its not there, at least not because of the verb itself. Any insecurities are by the backends implementation.

2 Likes

Well I don’t know how insecure this is, but when you use resources macro to add routes in your router, you can see the routes by running mix phx.routes, and PUT is one the methods you’ll see. So I think cowboy supports it.

The resources macro is so handful that you can specify only some actions, or exclude some, and it will generates the routes with the appropriate methods for you.

Example from the routing docs:

scope "/", HelloWeb do
  pipe_through :browser

  get "/", PageController, :index
  resources "/users", UserController
end

Then go to the root of your project, and run mix phx.routes

You should see something like the following:

user_path  GET     /users           HelloWeb.UserController :index
user_path  GET     /users/:id/edit  HelloWeb.UserController :edit
user_path  GET     /users/new       HelloWeb.UserController :new
user_path  GET     /users/:id       HelloWeb.UserController :show
user_path  POST    /users           HelloWeb.UserController :create
user_path  PATCH   /users/:id       HelloWeb.UserController :update
           PUT     /users/:id       HelloWeb.UserController :update
user_path  DELETE  /users/:id       HelloWeb.UserController :delete

Edit:
Then if we use Phoenix form_for helper to generate a form, we don’t have to set the method, just setting the action will be enough for the helper to add the appropriate method to the form. For example Routes.user_path(@conn, :edit, @user) as action will result in a form with POST as method. So personally I don’t worry about this detail. I let Phoenix handle it for me.

Oops the action should be :update here instead of :edit (which is just a GET request to show the form).

Post method is best in certain cases where you don’t want a url query string on the location bar, because it hides the internal parameters. This is an application. Not a website. So those XML web developers need to know that xml is a transport language only in applications. So there is no destinations to remote query. I imagine you could get the jsnode to generate something, but it could lead to security exploits.

In case of add new card I am able to insert the card new details also able to show validation message like for currency if some one tries to enter text input form willl not get submit and shows invalid data error. But in my form all values filled by user got clear instead of showing old values.

Please suggest how can I get form values filled by user instead of showing all fields blank this will help to reduce user rework from filling all values again and again

Well you have already the path set here:

Example of User controller:

  def new(conn, _params) do
    changeset = Accounts.change_user(%User{})
    render(conn, "new.html", changeset: changeset)
  end

  def create(conn, %{"user" => user_params}) do
    case Accounts.create_user(user_params) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "User created successfully.")
        |> redirect(to: Routes.user_path(conn, :show, user))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end

  def edit(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)
    changeset = Accounts.change_user(user)
    render(conn, "edit.html", user: user, changeset: changeset)
  end

  def update(conn, %{"id" => id, "user" => user_params}) do
    user = Accounts.get_user!(id)

    case Accounts.update_user(user, user_params) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "User updated successfully.")
        |> redirect(to: Routes.user_path(conn, :show, user))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "edit.html", user: user, changeset: changeset)
    end
  end

Then the Accounts Context

  def create_user(attrs \\ %{}) do
    %User{}
    |> User.changeset(attrs)
    |> Repo.insert()
  end

  def update_user(%User{} = user, attrs) do
    user
    |> User.changeset(attrs)
    |> Repo.update()
  end

  def change_user(%User{} = user) do
    User.changeset(user, %{})
  end

For the forms only the action and the page title should be different.
You set Routes.user_path(@conn, :create) for new form
and You set Routes.user_path(@conn, :update, @user) for edit form.

If you’re facing the same issue even though you follow this logic maybe it is because you’re nesting complex level of parents and childrens associations in your form ? In this case maybe the below thread I created myself some times ago could help you? Good luck. ^^