Ash embeds throwing "is not a valid type"

Hey all I’m running into this when trying to use embeds with Ash, even though I’m following the docs from ash 3.0.0 found here: Embedded Resources — ash v3.0.0-rc.21

** (RuntimeError) {:array, WfdBase.Recipes.Ingredient} is not a valid type.

Valid types include any custom types, or the following short codes (alongside the types they map to):

  :map -> Ash.Type.Map
  :keyword -> Ash.Type.Keyword
  :term -> Ash.Type.Term
  :atom -> Ash.Type.Atom
  :string -> Ash.Type.String
  :integer -> Ash.Type.Integer
  :float -> Ash.Type.Float
  :duration_name -> Ash.Type.DurationName
  :function -> Ash.Type.Function
  :boolean -> Ash.Type.Boolean
  :struct -> Ash.Type.Struct
  :uuid -> Ash.Type.UUID
  :binary -> Ash.Type.Binary
  :date -> Ash.Type.Date
  :time -> Ash.Type.Time
  :decimal -> Ash.Type.Decimal
  :ci_string -> Ash.Type.CiString
  :naive_datetime -> Ash.Type.NaiveDatetime
  :utc_datetime -> Ash.Type.UtcDatetime
  :utc_datetime_usec -> Ash.Type.UtcDatetimeUsec
  :datetime -> Ash.Type.DateTime
  :url_encoded_binary -> Ash.Type.UrlEncodedBinary
  :union -> Ash.Type.Union
  :module -> Ash.Type.Module
  :vector -> Ash.Type.Vector

My main resource is this:

defmodule WfdBase.Recipes.Recipe do
  use Ash.Resource,
    data_layer: AshPostgres.DataLayer,
    domain: WfdBase.Recipes

  postgres do
    table "recipes"
    repo WfdBase.Repo
  end

  attributes do
    uuid_primary_key :id
    attribute :title, :string, default: nil
    attribute :preparation_step, :string, default: nil
    attribute :preparation_time, :integer

    attribute :kitchenware, {:array, :string}, default: []
    attribute :utensils, {:array, :string}, default: []

    attribute :diet_labels, {:array, :string}
    attribute :dish_type, {:array, :string}
    attribute :cuisine_type, {:array, :string}
    attribute :cautions, {:array, :string}, default: []
    attribute :yield, :integer

    attribute :ingredients, {:array, WfdBase.Recipes.Ingredient}
    attribute :instructions, {:array, WfdBase.Recipes.Instruction}
    attribute :nutritional_content, {:array, WfdBase.Recipes.Nutrient}
  end

  actions do
    defaults [:read]

    create :create do
      accept [
        :title,
        :preparation_step,
        :preparation_time,
        :kitchenware,
        :utensils,
        :diet_labels,
        :dish_type,
        :cuisine_type,
        :cautions,
        :yield,
        :ingredients,
        :instructions,
        :nutritional_content
      ]
    end
  end
end

And the embeded schemas I’m using are the following:

defmodule WfdBase.Recipes.Ingredient do
  use Ash.Resource,
    data_layer: :embedded,
    embed_nil_values?: false

  attributes do
    attribute :name, :string, public?: true
    attribute :unit, :string, public?: true
    attribute :amount, :string, public?: true
  end
end

defmodule WfdBase.Recipes.Instruction do
  use Ash.Resource,
    data_layer: :embedded,
    embed_nil_values?: false

  attributes do
    attribute :position, :integer, public?: true
    attribute :description, :string, public?: true
  end
end

defmodule WfdBase.Recipes.Nutrient do
  use Ash.Resource,
    data_layer: :embedded,
    embed_nil_values?: false

  attributes do
    attribute :name, :string, public?: true
    attribute :unit, :string, public?: true
    attribute :amount, :integer, public?: true
  end
end

Maybe I’m missing something very evident, but would really appreciate some input

Thanks in advance!
Camille

1 Like

:thinking: are you defining the types in the same file as the resource? If so, can you try splitting them out into a file per type?

that fixed it, but now I get this changeset error:

Failed to Create Record: [
  %Ash.Error.Unknown.UnknownError{
    error: "** (Ecto.ChangeError) value `[#WfdBase.Recipes.Nutrient<__meta__: #Ecto.Schema.Metadata<:built, \"\">, name: :calories, unit: :kcal, amount: 300, aggregates: %{}, calculations: %{}, ...>, #WfdBase.Recipes.Nutrient<__meta__: #Ecto.Schema.Metadata<:built, \"\">, name: :fat, unit: :g, amount: 4, aggregates: %{}, calculations: %{}, ...>, #WfdBase.Recipes.Nutrient<__meta__: #Ecto.Schema.Metadata<:built, \"\">, name: :carbohydrates, unit: :g, amount: 35, aggregates: %{}, calculations: %{}, ...>, #WfdBase.Recipes.Nutrient<__meta__: #Ecto.Schema.Metadata<:built, \"\">, name: :protein, unit: :g, amount: 30, aggregates: %{}, calculations: %{}, ...>]` for `WfdBase.Recipes.Recipe.nutritional_content` in `insert_all` does not match type {:array, #WfdBase.Recipes.Nutrient.EctoType<[on_update: :update_on_match]>}",
    field: nil,
    splode: Ash.Error,
    bread_crumbs: [],
    vars: [],
    path: [],
    stacktrace: #Splode.Stacktrace<>,
    class: :unknown
  }
]

This is the code that’s doing the create:

      case result do
        {:ok, recipe} ->
          changeset =
            Recipe
            |> Ash.Changeset.for_create(:create, RecipeMapper.map_schema_to_attributes(recipe))

          case Ash.create(changeset, upsert?: true) do
            {:ok, record} ->
              IO.inspect(record, label: "Record Created")

            {:error, changeset} ->
              IO.inspect(changeset.errors, label: "Failed to Create Record")
          end

        {:error, reason} ->
          IO.inspect({:error, recipe_name, reason})
      end
...

  def map_schema_to_attributes(%GeneratedRecipe{} = recipe) do
    %{
      title: recipe.title,
      preparation_step: recipe.preparation_step,
      preparation_time: recipe.preparation_time,
      kitchenware: recipe.kitchenware,
      utensils: recipe.utensils,
      diet_labels: recipe.diet_labels,
      dish_type: recipe.dish_type,
      cuisine_type: recipe.cuisine_type,
      cautions: recipe.cautions,
      yield: recipe.yield,
      ingredients: map_ingredients(recipe.ingredients),
      instructions: map_instructions(recipe.instructions),
      nutritional_content: map_nutritional_content(recipe.nutritional_content)
    }
  end

nvm, I’m sure it’s a casting issue from atom → string

Instead of

attribute :ingredients, {:array, WfdBase.Recipes.Ingredient}

You should do

attribute :ingredients, {:array, :struct} ,constraints: [items: [instance_of: WfdBase.Recipes.Ingredient]]

You only need to do that for arguments that you want to accept non-embedded resources in, not for this case.

I have been doing that almost everywhere. :joy::joy: