Maybe What to do when you have to add a new parameter based on the result you get from a Service? would help. tl;dr I tend to avoid it.
Trying to create a Parent changeset and inserting it doesn’t work
Have you tried cast_assoc/3
?
defmodule Models.Parent do
use Ecto.Schema
import Ecto.Changeset
schema "parents" do
field(:description, :string)
has_one(:child, Models.Child)
timestamps()
end
@required_fields [:description]
def changeset(parent, params \\ :empty) do
parent
|> cast(params, @required_fields)
|> cast_assoc(:child) # <---
|> validate_required(@required_fields)
|> validate_length(:description, min: 5)
end
end
It would cast data for "child"
params
key and try and insert it into "child"
table with parent’s foreign key set.
Question 2: is there a better way?
I’d definitely try to avoid casting foreign keys from outside data (params
). See casting foreign keys in ecto changesets · Issue #29 · nccgroup/sobelow · GitHub. tl;dr it might make your app vulnerable.
I think all your problems should be handled by cast_assoc/3
…
Off-topic
defmodule Models.Child do
...
@required_fields [:name, :state]
@assoc_fields [:another_model_id]
def changeset(child, params \\ :empty) do
child
|> cast(params, @required_fields ++ @assoc_fields)
|> validate_required(@required_fields)
|> validate_inclusion(:state, ~w[new old])
end
end
You can concat the list of castable fields at compile time and avoid that work during run time
defmodule Models.Child do
...
@required_fields [:name, :state]
@assoc_fields [:another_model_id]
@castable_fields @required_fields ++ @assoc_fields
def changeset(child, params \\ :empty) do
child
|> cast(params, @castable_fields)
|> validate_required(@required_fields)
|> validate_inclusion(:state, ~w[new old])
end
end
I don’t think :empty
in params is being used anymore, it’s can replaced with an empty map.
def changeset(child, params \\ %{}) do