Have changes in typed_struct structs?

So, I was planning on replacing my typed_struct usage with the one in Ash, but I got confused on how to approach in some cases.

For example, I have this module that uses typed_struct but it also “manipulates” the data before inserting into the struct:

defmodule Core.Services.OpenCorporates.Company do
  @moduledoc false

  alias Core.Services.OpenCorporates.Company.{Address, Agent}

  use TypedStruct

  typedstruct enforce: true do
    field :name, String.t()
    field :number, String.t()
    field :type, String.t()

    field :address, Address.t()

    field :jurisdiction_code, String.t()

    field :agent, Agent.t()
  end

  def new!(company) when is_list(company), do: company |> Map.new() |> new!()

  def new!(company) when is_map(company) do
    struct!(__MODULE__,
      name: maybe_downcase(company[:name]),
      number: maybe_downcase(company[:number]),
      type: maybe_downcase(company[:type]),
      address: Address.new!(company[:address]),
      jurisdiction_code: maybe_downcase(company[:jurisdiction_code]),
      agent: Agent.new!(company[:agent])
    )
  end

  defp maybe_downcase(nil), do: nil
  defp maybe_downcase(value), do: String.downcase(value)
end

Now, with Ash’s typed_struct, the new! functions would be already implemented for me, but they would not have the maybe_downcase call I’m running here.

I can rename these functions to be something like new_normalized! and call __MODULE__.new! inside of it, but I wonder if there is a cleaner way to do it.

That made me wonder if it would be possible to actually support changes inside these structs, that way I would just create a change that would normalize the struct and keep everything inside that scope.

def new(attrs) do
  super(
      name: maybe_downcase(company[:name]),
      number: maybe_downcase(company[:number]),
      type: maybe_downcase(company[:type]),
      address: Address.new!(company[:address]),
      jurisdiction_code: maybe_downcase(company[:jurisdiction_code]),
      agent: Agent.new!(company[:agent])
    )
  )
end

You should be able to do something like that.

Doesn’t seem to work:

iex(10)> recompile
Compiling 1 file (.ex)
    error: no super defined for new/1 in module Core.Services.OpenCorporates.Company. Overridable functions available are:
    │
 21 │     super(
    │     ^
    │
    └─ (core 1.218.0) lib/core/services/open_corporates/company.ex:21:5: Core.Services.OpenCorporates.Company.new/1


== Compilation error in file lib/core/services/open_corporates/company.ex ==
** (CompileError) lib/core/services/open_corporates/company.ex: cannot compile module Core.Services.OpenCorporates.Company (errors have been logged)
    lib/core/services/open_corporates/company.ex:20: (module)
    (elixir 1.18.4) lib/kernel/parallel_compiler.ex:423: anonymous fn/5 in Kernel.ParallelCompiler.spawn_workers/8

I was about to say that you need to have defoverridable new: 1in the Ash.TypedStruct but I looked in the code and it is there already :thinking:

:thinking: are you on the latest version?

Ah, I was not.. updating ash made it work!