In my CRUD app I would like to add a duplicate feature, in which an existing object’s data is used to prepopulate the form. The naive way I started with was:
def duplicate(conn, %{"id" => item_id}) do
item = Repo.get!(Item, item_id)
changeset = Ecto.Changeset.change(item)
render(conn, "new.html", changeset: changeset)
end
where new.html renders a form with form_for pointing to the POST method.
What is the idiomatic way of duplicating a Changeset that refers to an actual record, so that a new entry is created, without having to manually remove the __meta__ field from the changeset?
I suspect any function on Ecto.Changeset is going to wind up doing things you don’t want - in this case, I’d recommend taking the simplest possible route and writing a function in Item that takes an Item and returns an unsaved Ecto.Changeset:
def duplicate(item) do
changeset(%Item{}, %{title: item.title})
end
Reasoning:
you likely don’t want to copy some fields (ID, naturally; also probably inserted_at etc)
you might want to modify specific data - for instance, adding " (copy)" or similar to a title
you may need to do additional work to copy some fields (has_many associations, for instance)
I’ve written a library that provides sugar for this, but I do this:
def duplicate(conn, %{"id" => item_id}) do
item = Repo.get!(Item, item_id)
changeset = EctoMorph.generate_changeset(item, Item)
render(conn, "new.html", changeset: changeset)
end
Essentially the generate_changeset function allows you to pass in a struct as the data that you want to cast. That means I can use the same function everywhere. And if you pass in the struct that you want to duplicate as the data to be casted you get duplicate for free.