Reputation: 3350
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?
Upvotes: 1
Views: 731
Reputation: 121000
You are after Ecto.Changeset.cast_assoc/3
.
Instead of updating the values directly, you should define a changeset for data
and allow ecto to do the rest for you.
Somewhat like this would do:
Ecto.Changeset.cast_assoc(
setting, :data, with: &SettingData.changeset/2
)
Upvotes: 1