← Back to list

How To Simplify Nested Data Structure with Ash Embedded Resources

Ash Framework for Phoenix Developers

Kamaro Lambert · 2026-02-17 07:32 · 4 claps · 3.2 min read
#ash-framework #elixir #phoenix #software-development
Open on Medium ↗
Wiki topics: 💻 · Programming

How To Simplify Nested Data Structure with Ash Embedded Resources

Ash Framework for Phoenix Developers

Photo by Ferenc Almasi on Unsplash

Photo by Ferenc Almasi on Unsplash

Two weeks ago I was working on a feature that affects the general ledger(GL) in a social fund system. The business rule is that the user does a transaction that will involve moving funds in between different accounts, but that can only be posted if the supervisor has approved the transaction.

Let’s call this transaction a loan.

Here is what the flow looks like:

  1. A user selects different accounts(from chart of accounts) while filling the form loan.
  2. The user submits the form and wait for the supervisor to approve.
  3. When the supervisor approves, the posting to the GL happens.

It becomes complex in step 1-2. One transaction(loan) may involve any accounts. I don’t want to create another table to just host postings in transit. I want to find an simpler way to attach postings in transit to the loan, and process them when the loan has been approved.

This is where Ash Embedded resources come in.

If you’re looking to dive deeper into the Ash Framework, grab my book **Ash Framework for Phoenix Developers**.

You can solve this in different ways, but Ash Embedded resource has proven me to be much easier to work with for these kinds of problems.

1. What are Embedded Resources In Ash?

At the fundamental level, Ash embedded resources are normal resources helps you store structured data(maps) in your attribute. At the database level, they are Json field. They help you store structured data in a resource attribute without having to create a has_many relationships to another resource.

They make it easier to work with Json data structure in a database table. You can read more about embedded resources here: https://hexdocs.pm/ash/embedded-resources.html

2. How are Embedded Resource Defined

Embedded resources are defined like any other resource in Ash, except that the data layer is defined as :embedded. They have actions, attributes, relationships, and validations. Attributes are Json fields in the database table.

See below example:

defmodule MyApp.Domain.Resource.Attributes.GlEntry do
  use Ash.Resource, data_layer: :embedded

  actions do

  end

  attributes do

  end

  relationships do

  end

  validations do

  end  

  # more...
end

A defined embedded resource will look like the follow:

defmodule MyApp.Loans.Loan.Attributes.GLEntry do
  use Ash.Resource,
    data_layer: :embedded,
    embed_nil_values?: false

  actions do
    default_accept [:amount, :from_account_id, :to_account_id, :description, :id]
    defaults [:create, :read]
  end

  attributes do
    attribute :id, :integer do
      description "Identifier of this double entry "
      allow_nil? false
      default 1
    end

    attribute :amount, :decimal do
      description "Amount to transfer"
      allow_nil? false
      public? true
    end

    attribute :description, :string do
      description "Remarks for this journal entry"
      default ""
    end
  end

  relationships do
    belongs_to :from_account, MyApp.Ledger.Account do
      description "The account to transfer amount from during loan transaction"
      source_attribute :from_account_id
      allow_nil? false
    end

    belongs_to :to_account, MyApp.Ledger.Account do
      description "The account to transfer amount to during loan transaction"
      source_attribute :to_account_id
      allow_nil? false
    end
  end
end

And in the resource that uses it, they are defined as other attributes, but the database type is the name of the embedded resource.

defmodule MyApp.Loans.Loan do
  use Ash.Resource, data_layer: AshPostgres.DataLayer

  actions do
    create :create_loan do
      accept [
        # Other attribute this action accepts
        :accounting_entries
      ]

      validate MyApp.Loans.Loan.Validations.RequireGuarantorsIfPrincipleExceedsLoanableAmount
      validate MyApp.Loans.Loan.Validations.LoanTypeMustAllowSelectedInstallments
      validate MyApp.Loans.Loan.Validations.MustNotExceedMaximumAllowedAmount

      change MyApp.Loans.Loan.Changes.ChargeProcessingFeeIfApplicable
      change MyApp.Loans.Loan.Changes.AddUniqueTransactionID
      change MyApp.Loans.Loan.Changes.AddAmountReceived
      change MyApp.Loans.Loan.Changes.AddInterestAmount
      change MyApp.Loans.Loan.Changes.PostGuarantors
    end

    update :approve_and_post do
      description "Approve an existing loan and post to the general ledger"

      filter expr(status == :pending)
      change set_attribute(:status, :approved)
      change MyApp.Loans.Loan.Changes.PostToLedger
    end
  end

  attributes do
    # Other attributes
    # ==========================EMBEDDED RESOURCE IN ATTRIBUTE=============
    attributes :accounting_entries, {:array, MyApp.Loans.Loan.Attributes.GLEntry} do
      description "The general ledger entries list related to this records"
      allow_nil? false 
    end
    # =====================================================================

  end

end

Then, you will create records for the resource with embedded resource attribute the same way you’ve been doing it.

See below example.

# You can save as many accounting entries as you want
params =  %{
  "accounting_entries" => %{
    "0" => %{
      "amount" => "1000",
      "description" => "Principle",
      "from_account_id" => "019c1fc1-fd37-723a-9540-5afc21bc7516",
      "id" => 1,
      "to_account_id" => "019c1fc1-fd71-7923-8a68-7b077635242e"
    },
    "1" => %{
      "amount" => "2000",
      "description" => "Interests",
      "from_account_id" => "019c1fc1-fd50-7543-b7a3-d2197db2b0cc",
      "id" => 2,
      "to_account_id" => "019c1fc1-fd80-7e68-bd5d-f417544b325f"
    }
  },
  "amount_received" => Decimal.new("75403.00"),
  "desired_amount" => Decimal.new("875403.00"),
  "installments" => "8",
  "interest" => Decimal.new("100.00"),
  "loan_type_id" => "019c1fc1-c14d-72b8-bdd0-62155e5205df",
  "monthly_fees" => Decimal.new("875403.00"),
  "principle" => Decimal.new("875403.00"),
  "remarks" => "Issuing loans to Person X WYZ"
}

# Then you can insert data in your database like you would do
Ash.create(MyApp.Loans.Loan, params)

That’s how you use embedded resources to simplify complex data strutcure inin Ash framework resouce.

If you’re looking to dive deeper into the Ash Framework, grab my book **Ash Framework for Phoenix Developers**.

Let me know your thoughts in the comment below. What would you like me to write about next?


메타데이터
post_id
afcdfd21ed86
slug
how-i-simplify-complex-data-with-ash-embedded-resources-afcdfd21ed86
url
https://medium.com/@lambert.kamaro/how-i-simplify-complex-data-with-ash-embedded-resources-afcdfd21ed86
canonical_url
https://medium.com/@lambert.kamaro/how-i-simplify-complex-data-with-ash-embedded-resources-afcdfd21ed86
author_url
https://medium.com/@lambert.kamaro
status
ok
fetched_at
2026-07-21 10:43:05