Map embed schema for changeset - take data from one structure and save it on a similar structure

Hello everyone,

I’m trying to take data from one structure and save it on a similar structure when creating a record through function.

My data is saved fine as long as I don’t send data from embed schema answers.Can someone please advise me how to properly send data from embed schema please? I thought I could work with answers as another atom, but I can’t.

I have this error: expected changeset data to be a Elixir.MyApp.AssessmentAppPipelines.AssessmentAppPipeline.AssessmentAnswer struct, got: %MyApp.AssessmentPipelines.AssessmentPipeline.AssessmentAnswer{id: "05bcf97a-84ed-478f-a74a-b6da9aa2a28f", title: "T1", score: 2, answ_id: nil, delete: nil}

Structure is:
Schema_A has_many Schema_B embeds_many Embed_Schema_C
create →
Schema_D has_many Schema_E embeds_many Embed_Schema_F

Function:

  def create_assessment_app_pipeline_from_assessment_pipeline(
        %AssessmentPipeline{} = assessment_pipeline,
        attrs \\ %{}
      ) do
    attrs
    |> Map.merge(%{
      "assessment_pipeline_id" => assessment_pipeline.id,
      "questions" =>
        Enum.map(assessment_pipeline.assessment_questions, fn m ->
          m
          |> Map.from_struct()
          |> Map.take([:question, :description, :category, :multiple_choice, :assessment_answers])
        end),
        .
        .
        .
       "count_of_questions" => length(assessment_pipeline.assessment_questions)
    })
    |> create_assessment_app_pipeline()
  end

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

I have these schemas:

Pipeline

  schema "assessment_app_pipelines" do
    field :progress, :integer, default: 0
    field :status, AssessmentAppPipeline.Status, default: :draft
    field :submitted_at, :naive_datetime
    field :evaluation, :integer, default: 0
    field :count_of_evaluations, :integer, default: 0
    field :count_of_questions, :integer, default: 0
    .
    .
    .


    timestamps()

    belongs_to :company, Company
    belongs_to :assessment_pipeline, AssessmentPipeline
    has_many :questions, AssessmentAppPipeline.Question
    .
    .
    .
  end

  def changeset(assessment_app_pipeline, attrs) do
    assessment_app_pipeline
    |> cast(attrs, [
      :company_id,
      :assessment_pipeline_id,
      :count_of_questions
    ])
    |> put_assoc(:questions, Map.get(attrs, "questions"))
    |> validate_required([
      :questions,
      :company_id,
      :assessment_pipeline_id
    ])
    |> unique_constraint(:company_id,
      name: :assessment_app_pipelines_company_id_assessment_pipeline_id_index
    )
  end

Question

  schema "assessment_app_pipeline_questions" do
    field :question, :string
    field :description, :string
    field :category, :string
    field :multiple_choice, :boolean, default: false

    embeds_many :assessment_answers,  MyApp.AssessmentAppPipelines.AssessmentAppPipeline.AssessmentAnswer, on_replace: :delete

    belongs_to :assessment_app_pipeline, MyApp.AssessmentAppPipelines.AssessmentAppPipeline,
      type: :binary_id

    belongs_to :updated_by, MyApp.Users.User, type: :binary_id

    timestamps()
  end

  def changeset(question, attrs) do
    question
    |> cast(attrs, [
      :question,
      :description,
      :category,
      :answer,
      :updated_by_id,
      :multiple_choice
    ])
    |> cast_embed(:assessment_answers, with: &MyApp.AssessmentAppPipelines.AssessmentAppPipeline.AssessmentAnswer.changeset/2, required: true)
    |> validate_required([
    .
    .
    .
    ])
  end

Embed schema

  embedded_schema do
    field :title, :string
    field :score, :integer
    field :selected, :boolean
  end

  def changeset(assessment_answer, attrs) do
    assessment_answer
    |> cast(attrs, [:title, :score, :selected])
    |> validate_required([:title, :score])
  end

If you need more information I will add.

Thanks for any help! :slight_smile:

1 Like

I suspect the assessment_answers here is the root cause: the loaded MyApp.AssessmentPipelines.AssessmentPipeline.AssessmentAnswer structs are being passed along unaltered, while cast_assoc expects either string-keyed maps or MyApp.AssessmentAppPipelines.AssessmentAppPipeline.AssessmentAnswer structs.

2 Likes

Yes, the problem is assessment answers, without this embed schema the data is stored as I wrote.

So in what form should I send the data so that I am able to transfer multiple records of one embed schema to another and still keep the link to the parent record that is being transferred in order? For example, do I have to send a map of string maps?

According to me, I cannot use the MyApp.AssessmentAppPipelines.AssessmentAppPipeline.AssessmentAnswer structure if I take data from MyApp.AssessmentPipelines.AssessmentPipeline.AssessmentAnswer

Just to compare the two schemes:

MyApp.AssessmentPipelines.AssessmentPipeline.AssessmentAnswer:

  embedded_schema do
    field :title, :string
    field :score, :integer
    field :answ_id, :string, virtual: true
    field(:delete, :boolean, virtual: true)
  end

MyApp.AssessmentAppPipelines.AssessmentAppPipeline.AssessmentAnswer:

  embedded_schema do
    field :title, :string
    field :score, :integer
    field :selected, :boolean
  end

Could you please demonstrate the conversion with a short example?

Thank you so much for your time.

It helped me replace this part:

      "questions" =>
        Enum.map(assessment_pipeline.assessment_questions, fn m ->
          m
          |> Map.from_struct()
          |> Map.take([
           :question,
           :description, 
           :category, 
           :multiple_choice,
           :assessment_answers])
        end)

For this part:

      "questions" =>
        Enum.map(assessment_pipeline.assessment_questions, fn m ->
          %{
            assessment_answers: m.assessment_answers
            |> Enum.map(fn ans -> %{id: ans.id, title: ans.title, score: ans.score} end),
            category: m.category,
            description: m.description,
            multiple_choice: m.multiple_choice,
            question: m.question
          }
        end)

Subsequently, the Embed schema structure was also sent in the format I required and saving to the new table is working fine.