1

Hi Im very new to Elixir/Phoenix. What Im trying to do is very simple. I have a table in my database with a jsonb column. I am trying to update the json data through Ecto. My model looks like this:

defmodule Setting.Setting do
  use Setting.Web, :model

  alias Setting.{VesselState, User, SettingData, FollowUp, LogEntry, VesselEvent, RiskModel, LegalExtract}

  schema "settings" do
    belongs_to :vessel_state, VesselState
    field :request_date, :date
    belongs_to :requester, User
    field :approved_date, :date
    belongs_to :manager, User
    field :analyst_feedback, :string
    belongs_to :analyst, User
    field :date, :utc_datetime
    field :status, :string
    field :points, :integer
    field :risk, :string
    field :verdict, :string
    embeds_one :data, SettingData, on_replace: :delete
    field :remarks, {:array, :string}
    embeds_many :follow_ups, FollowUp, on_replace: :delete
    embeds_many :log, LogEntry
    field :uuid, :string

    has_one :vessel_event, VesselEvent
    belongs_to :risk_model, RiskModel

    has_many :legal_extracts, LegalExtract

    timestamps()
  end

The data column in the schema is json and you see it is embedding another model (SettingData). If I fetch one record from the Setting table it will look like the follwing:

%Setting.Setting{
  __meta__: #Ecto.Schema.Metadata<:loaded, "settings">,
  analyst: #Ecto.Association.NotLoaded<association :analyst is not loaded>,
  analyst_feedback: "Approved",
  analyst_id: nil,
  approved_date: nil,
  data: %Setting.SettingData{
    id: "f25b6080-0751-4a65-8214-36e2810ea716",
    v1: %Setting.SettingDataV1{
      age_risk: %Setting.AgeRisk{
        age: 5.15,
        cif: false,
        id: "70b249f6-cef9-4473-a0ed-780e84cf6407",
      },
      class_society_risk: %Setting.ClassSocietyRisk{
        class_society: "Lloyds Register",
        iacs_member: true,
        id: "248bc975-5c39-4f92-afb6-a2b2365afa53",
        original_points: nil,
        overdue_class_published: false,
      }
   },
  date: ~U[2016-12-16 00:00:00.000000Z],
  follow_ups: [
    %Setting.FollowUp{
      action: nil,
      attachments: [],
      comment: nil,
      created_at: nil,
      file_reference: nil,
      id: "314b4445-b162-49f3-a1ca-f39a24af14bb",
    }
  ],
  id: 8,
  inserted_at: ~U[2017-01-02 08:25:58.913674Z],
  legal_extracts: #Ecto.Association.NotLoaded<association :legal_extracts is not loaded>,
  log: [],
  manager: #Ecto.Association.NotLoaded<association :manager is not loaded>,
  manager_id: nil,
  points: 15,
  remarks: [""],
  request_date: nil,
  requester: #Ecto.Association.NotLoaded<association :requester is not loaded>,
  requester_id: nil,
  risk: "Standard",
  risk_model: #Ecto.Association.NotLoaded<association :risk_model is not loaded>,
  risk_model_id: nil,
  status: "Completed",
  updated_at: ~U[2017-02-01 15:27:53.158000Z],
  uuid: nil,
  verdict: "Approved",
  vessel_event: #Ecto.Association.NotLoaded<association :vessel_event is not loaded>,
  vessel_state: #Ecto.Association.NotLoaded<association :vessel_state is not loaded>,
  vessel_state_id: 8
}

I simply want to change data.v1.age_risk.cif from false to true. I initially tried to create a change_set like,

cs = %{
  data: %{
    v1: %{
      age_risk: %{
        cif: true
      }
    }
  }
}

Setting.Setting.changeset(setting, cs) |> Setting.Repo.update()

This updates cif to true from false, but it sets all other elements in the json data to nil. So I figured Ill have to fetch the whole json, then update the element I want and then update the database with the new json. So basically I did something like this:

cs_new = %Setting.Setting{
  __meta__: #Ecto.Schema.Metadata<:loaded, "settings">,
  analyst: #Ecto.Association.NotLoaded<association :analyst is not loaded>,
  analyst_feedback: "Approved",
  analyst_id: nil,
  approved_date: nil,
  data: %Setting.SettingData{
    id: "f25b6080-0751-4a65-8214-36e2810ea716",
    v1: %Setting.SettingDataV1{
      age_risk: %Setting.AgeRisk{
        age: 5.15,
        cif: true,     #changed this to true
        id: "70b249f6-cef9-4473-a0ed-780e84cf6407",
      },
      class_society_risk: %Setting.ClassSocietyRisk{
        class_society: "Lloyds Register",
        iacs_member: true,
        id: "248bc975-5c39-4f92-afb6-a2b2365afa53",
        original_points: nil,
        overdue_class_published: false,
      }
   },
  date: ~U[2016-12-16 00:00:00.000000Z],
  follow_ups: [
    %Setting.FollowUp{
      action: nil,
      attachments: [],
      comment: nil,
      created_at: nil,
      file_reference: nil,
      id: "314b4445-b162-49f3-a1ca-f39a24af14bb",
    }
  ],
  id: 8,
  inserted_at: ~U[2017-01-02 08:25:58.913674Z],
  legal_extracts: #Ecto.Association.NotLoaded<association :legal_extracts is not loaded>,
  log: [],
  manager: #Ecto.Association.NotLoaded<association :manager is not loaded>,
  manager_id: nil,
  points: 15,
  remarks: [""],
  request_date: nil,
  requester: #Ecto.Association.NotLoaded<association :requester is not loaded>,
  requester_id: nil,
  risk: "Standard",
  risk_model: #Ecto.Association.NotLoaded<association :risk_model is not loaded>,
  risk_model_id: nil,
  status: "Completed",
  updated_at: ~U[2017-02-01 15:27:53.158000Z],
  uuid: nil,
  verdict: "Approved",
  vessel_event: #Ecto.Association.NotLoaded<association :vessel_event is not loaded>,
  vessel_state: #Ecto.Association.NotLoaded<association :vessel_state is not loaded>,
  vessel_state_id: 8
}

Setting.Setting.changeset(setting, cs_new) |> Setting.Repo.update()

This is now giving me error:

** (Ecto.CastError) expected params to be a :map, got: `%Setting.Setting ...

I tried converting this to map using Map.from_struct/1, but that only changes the outer layer. How should I fix this? or is there a different (and better) way of achieving what im trying to do?

1
  • can you add SettingData and SettingDataV1 code? Commented Dec 4, 2020 at 8:32

1 Answer 1

1

You are after Ecto.Changeset.cast_assoc/3.

Instead of updating the values directly, you should define a changeset for data and allow to do the rest for you.


Somewhat like this would do:

Ecto.Changeset.cast_assoc(
  setting, :data, with: &SettingData.changeset/2
)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.