I have schema __options__ that has two embedded schemas, one __currency__ and many __colors__ as shown in the code
schema "options" do
field(:email_notify, :boolean, default: false)
field(:email_verify, :boolean, default: false)
field(:sms_notify, :boolean, default: false)
field(:sms_verify, :boolean, default: false)
field(:subtitle, :string, default: "Invest Properly")
field(:title, :string, default: "IPO Website")
embeds_one(:currency, IPO.Options.Currency, on_replace: :delete)
embeds_many(:colors, IPO.Options.Color, on_replace: :delete)
timestamps()
end
@all_fields ~w(title subtitle email_notify email_verify sms_notify sms_verify)a
@doc false
def changeset(website, attrs) do
website
|> cast(attrs, @all_fields)
|> cast_embed(:colors)
|> cast_embed(:currency)
|> validate_required([
:colors,
:currency,
...
])
end
and here is the embedded schema definition for both schemas
__currency__
defmodule IPO.Options.Currency do
...
embedded_schema do
field(:name, :string)
field(:symbol, :string)
end
@doc false
def changeset(%Currency{} = currency, attrs) do
currency
|> cast(attrs, [:name, :symbol])
|> validate_required([:name, :symbol])
end
end
__colors__
defmodule IPO.Options.Color do
...
embedded_schema do
field(:name, :string)
field(:value, :string)
end
@doc false
def changeset(%Color{} = color, attrs) do
color
|> cast(attrs, [:name, :value])
|> validate_required([:name, :value])
end
end
and here is the migration file for the main schema __options__, I put _default values_ for both embedded schemas. I tried it with both :map and :jsonb I though maybe there is difference. I also used default: %{name: "EGP", symbol: "E拢"} with :map type, but didn't affect anything.
def change do
create table(:options) do
add(:colors, {:array, :map}, default: [])
add(:currency, :map, default: "{\"name\":\"EGP\",\"symbol\":\"E拢\"}")
...
timestamps()
end
end
and here is how it looks like inside database, __options__ table
ipo_dev=# \d options
Table "public.options"
Column | Type | Collation | Nullable | Default
--------------+--------------------------------+-----------+----------+------------------------------------------
id | bigint | | not null | nextval('options_id_seq'::regclass)
colors | jsonb[] | | | ARRAY[]::jsonb[]
currency | jsonb | | | '{"name": "EGP", "symbol": "E拢"}'::jsonb
email_notify | boolean | | not null | false
email_verify | boolean | | not null | false
sms_notify | boolean | | not null | false
I should be able to insert a record for __options__ without providing any values for the two embedded schemas as both have default values, this works fine for the many __colors__, but it doesn't work at all for one __currency__!!
I tried even to put default: "{}" instead of default: "{\"name\":\"EGP\",\"symbol\":\"E拢\"}" but also doesn't work, although I see it in the database table under _Default_ column correct as I wrote it in the _ecto migration_ file, but I always git error __"can't be blank"__, notice down that __colors__ has no errors and is valid?: true but __currency__ valid?: false
{:error,
#Ecto.Changeset<
action: :insert,
changes: %{
colors: [
#Ecto.Changeset<
action: :insert,
changes: %{name: "primary", value: "#03afde"},
errors: [],
data: #IPO.Options.Color<>,
valid?: true
>,
#Ecto.Changeset<
action: :insert,
changes: %{name: "secondary", value: "#de440f"},
errors: [],
data: #IPO.Options.Color<>,
valid?: true
>
]
},
errors: [currency: {"can't be blank", [validation: :required]}],
data: #IPO.Platform.Website<>,
valid?: false
>}
Yes, the default in your database are not reflected in the schema. You have to set it automatically when building the association or remove the validation and use read_after_writes: true so Ecto reads the value back.
You have to set it automatically when building the association
I am sorry, I read documentation, and I didn't understand how to set it __automatically__, if I can't pass to embeds_one any other __opts__ except on_replace, and I can't add normal field for __currency__ because it will conflict with embeds_one :currency, IPO.Options.Currency, on_replace: :delete. So I see the only option I have is to remove validation for __:currency__?!!
You have to embed it after creating the Website entry. For example, in your changeset function, you can do this: Website |> cast(...) |> put_embed(..., :currency, %Currency{...}).
But I didn't understand, why in the case of embeds_many it worked fine and accepted the default value I passed in the migration file, but with embeds_one it didn't? This was my main reason behind opening this issue. Also, this solution of put_embed worked fine but it always put this value that passed for it in the database, even when I insert a new record with a different value it doesn't consider it and still take the one passed to put_embed!
Most helpful comment
You have to embed it after creating the Website entry. For example, in your changeset function, you can do this:
Website |> cast(...) |> put_embed(..., :currency, %Currency{...}).