How To Simplify Nested Data Structure with Ash Embedded Resources
Ash Framework for Phoenix Developers
How To Simplify Nested Data Structure with Ash Embedded Resources
Ash Framework for Phoenix Developers
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:
- A user selects different accounts(from chart of accounts) while filling the form loan.
- The user submits the form and wait for the supervisor to approve.
- 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