dlex icon indicating copy to clipboard operation
dlex copied to clipboard

Repo.set creates new node rather than updating existing node

Open goravbhootra opened this issue 5 years ago • 3 comments

Expected behaviour: Existing node should be updated

Actual behaviour: New node is created every time

version: 0.5.1

Same implementation was working fine with earlier version.

Implementation:

file lib/lms/courses/course.ex contains:

defmodule Lms.Courses.Course do
  use Dlex.Node
  @derive {Phoenix.Param, key: :uid}

  import Ecto.Changeset

  schema "courses" do
    field(:description, :string)
    field(:duration_allowed, :integer)
    field(:duration_units, :integer, default: 3)
    field(:position, :integer)
    field(:status, :integer)
    field(:title, :string, index: ["term"])
    field(:guidelines, :string)
    field(:guidelines_title, :string)
    field(:has_learning_path, :uid)

    field(:created_at, :datetime)
    field(:updated_at, :datetime)
  end

  @doc false
  def changeset(course, attrs) do
    course
    |> cast(attrs, [
      :title,
      :description,
      :duration_allowed,
      :duration_units,
      :status,
      :position,
      :guidelines,
      :guidelines_title
    ])
  end
end

and lib/lms/courses.ex contains:

def update_course(%Course{} = course, attrs) do
    course
    |> Course.changeset(attrs)
    |> Repo.set()
  end

Following creates a new node rather than updating the existing node:

id = "0x7533" # its a valid id and returns Course struct
course = Repo.get!(id)
course_params = [
  attrs: %{
    "description" => "",
    "duration_allowed" => "",
    "guidelines" => "",
    "guidelines_title" => "",
    "position" => "",
    "status" => "1",
    "title" => "updated title"
  }
]
Lms.Courses.update_course(course, course_params)

Course.changeset(attrs) produces:

changeset: #Ecto.Changeset<
  action: nil,
  changes: %{status: 1, title: "updated title"},
  errors: [],
  data: #Lms.Courses.Course<>,
  valid?: true
>

and data key contains:

%Lms.Courses.Course{
  created_at: nil,
  description: nil,
  duration_allowed: nil,
  duration_units: 3,
  guidelines: nil,
  guidelines_title: nil,
  has_learning_path: nil,
  position: nil,
  status: 1,
  title: "now we got it",
  uid: "0x7533",
  updated_at: nil
}

Tried replacing Repo.set with Repo.mutate but same issue persists.

goravbhootra avatar Jul 04 '20 19:07 goravbhootra

When updating, are you casting :uid as a field?

I use

struct
    |> cast(params, [
      :uid,
      :title
    ])
    |> validate_required([:uid])

and it works.

martinthenth avatar Jul 09 '20 20:07 martinthenth

To those who are looking for a solution, i.e. to update existing nodes in Dgraph with Ecto changesets:

  1. Cast uid in your changeset.
  2. Validate require uid in your changeset.
  3. Force change uid in your changeset.

For example:

def update_changeset(%Struct{} = struct, attrs) do
  struct
  |> cast(attrs, [:uid])
  |> validate_required([:uid])
  |> force_change(:uid, struct.uid)
end

This will update an existing node in Dgraph based on the given uid.

Perhaps it's possible to indicate to Ecto that uid is a primary key so that uid is automatically added to Ecto.Changeset changes field, but I have not tested this.

martinthenth avatar Feb 26 '21 01:02 martinthenth

Thanks Martin for the solution. I had dropped the implementation, will try to pick it up again.

goravbhootra avatar Mar 01 '21 03:03 goravbhootra