Ecto: I get `can't be blank` for an embedded schema although it has default value for it.

Created on 27 Sep 2020  路  4Comments  路  Source: elixir-ecto/ecto

Environment

  • Elixir version (elixir -v): __1.10.4__
  • Database and version (PostgreSQL 9.4, MongoDB 3.2, etc.): __PostgreSQL 12.4__
  • Ecto version (mix deps): __3.4.6__
  • Database adapter and version (mix deps): __0.15.6__
  • Operating system: __macOS 10.15.7__

Current behavior

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

Expected behavior

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
 >}

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{...}).

All 4 comments

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!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mcginleyr1 picture mcginleyr1  路  5Comments

madshargreave picture madshargreave  路  3Comments

fuelen picture fuelen  路  3Comments

a12e picture a12e  路  4Comments

brandonparsons picture brandonparsons  路  3Comments