Search code examples
elixirphoenix-frameworkecto

How to duplicate a row via Ecto if a model has associations?


I have a model which has several "belongs_to" and "has_many" I want to duplicate it with a newly generated "id" and with a new value of some other field.

m1 = Repo.get(MyModel, 123)
|> Map.delete(:id)

m2 = Repo.insert!(m1, %{my_field: "aaa"})
# or
# m2 = Repo.insert!(MyModel.changeset(m1, %{my_field: "aaa"}))

and error:

(RuntimeError) attempting to cast or change association `my_assoc1` from `MyModel` that was not loaded. 
Please preload your associations before manipulating them through changesets

I don't want to have to preload each association. Besides, why would I have to if I want a duplicated model instance to have my_assoc1_id, which is an integer, identical to the original instance, that is, whether or not the association is loaded shouldn't matter.

Neither would I want to have to remove each association from a changset.

How to do it?


Solution

  • You cannot manipulate/cast associations like belongs_to or has_many directly. These associations are an abstraction layer so that you can manipulate data easier.

    For example:

    schema "my_model" do
        belongs_to :my_field, Example
    end
    

    If you take a look in the database or migration, you can notice that a field called my_field_id was created, containing the foreign key pointing to the other table.

    If you want to clone a entity with all associations, you just create a changeset that makes sure that foreign keys get casted too:

    def changeset(my_model, params \\ %{}) do
        user
        |> cast(params, [:my_field_id])
        |> validate_required([:my_field_id])
        |> foreign_key_constraint(:my_field_id)
      end