Deep into Ecto - on_conflict, insert_or_update?


I was writing code like this:

  def create(conn, %{"device" => device_params}) do
    device = Devices.get_device_by_id(device_params["device_id"])

    case device do
      nil ->
        with {:ok, %Device{} = device} <- Devices.create_device(device_params) do
          |> put_status(:created)
          |> render("show.json", device: device)

      _ ->
        device_params = Map.merge(device_params, %{"session_count" => device.session_count + 1})

        with {:ok, %Device{} = device} <- Devices.update_device(device, device_params) do
          render(conn, "show.json", device: device)

Thanks to Phoenix Tests it failed due to a security issue.

 ** (ArgumentError) comparison with nil is forbidden as it is unsafe. If you want to check if a value is nil, use is_nil/1 instead
     code: conn = post(conn, Routes.device_path(conn, :create), device: @invalid_attrs)

My purpose is using one single route (POST) for client side, when the client app runs, it always makes POST call doesn’t have a PUT logic on client side for some reasons so I needed this approach even I don’t like it.

So, I did some research about the problem and I saw Jose Valim’s comments on GitHub issues and try to solve it via on_conflict feature of Ecto. Then I learnt (from another GitHub issue) that can’t be done in PostgreSQL adapter, we have to use fragment.

This is going to be very common usage for the app, I’ll eventually need to create or update data(s) such way. So, what’s the best practice without creating vulnerabilities?

Thank you all!

Use the ecto upsert funcionality