Where and how do I write my business logic?

I know this is a silly question and I apologize as I'm still new to Ash, but where exactly do I write my business logic? I have a Book and Category resource. What I want to happen is to create a bunch of Category records every time a Book is created. Normally, I would write this business logic in a context/module somewhere and then call it inside the save handle_event in my liveview after a successful Book record creation. I honestly can't figure out if I should just do the same thing - write a module somewhere in the app and call it once AshPhoenix.Form.submit() succeeds. Or maybe there is a convention or an actual methodology when doing these things with Ash and AshPhoenix?
7 Replies
ZachDaniel
ZachDanielβ€’3y ago
Hello! So the best way to think about it is that, in Ash, your resources act as your context layer (i.e each resource is a context). The two main places that your business logic lives is in actions and calculations, with the various other structures around resources supporting it. You can do things like write manual actions to allow for doing "whatever you want" in an action, and you can use calculations to produce arbitrary information about a record. We're still working on these patterns, primarily by adding ways that you can elegantly express your domain using Ash primitives and derive behavior from it. An example of a manual action used for "adding friends" in the twitter example from a while back:
update :add_and_request_friend do
primary? true
accept []

argument :destination_user_id, :uuid do
allow_nil? false
end

manual fn changeset, _ ->
with {:ok, destination_user_id} <-
Ash.Changeset.fetch_argument(changeset, :destination_user_id),
{:ok, _} <-
Twitter.Accounts.FriendLink.create(changeset.data.id, destination_user_id, %{
status: :approved
}),
{:ok, _} <-
Twitter.Accounts.FriendLink.create(destination_user_id, changeset.data.id) do
{:ok, changeset.data}
end
end
end
update :add_and_request_friend do
primary? true
accept []

argument :destination_user_id, :uuid do
allow_nil? false
end

manual fn changeset, _ ->
with {:ok, destination_user_id} <-
Ash.Changeset.fetch_argument(changeset, :destination_user_id),
{:ok, _} <-
Twitter.Accounts.FriendLink.create(changeset.data.id, destination_user_id, %{
status: :approved
}),
{:ok, _} <-
Twitter.Accounts.FriendLink.create(destination_user_id, changeset.data.id) do
{:ok, changeset.data}
end
end
end
And an example calculation
calculate :visible_to,
:boolean,
expr(
author_id == ^arg(:user_id) or visibility == :public or
visible_as_friend(user_id: arg(:user_id))
) do
argument :user_id, :uuid do
allow_nil? false
end
end
calculate :visible_to,
:boolean,
expr(
author_id == ^arg(:user_id) or visibility == :public or
visible_as_friend(user_id: arg(:user_id))
) do
argument :user_id, :uuid do
allow_nil? false
end
end
We also have things like Ash.Flow for building complex sets of behaviors, and will be continuing to integrate the various tools to allow for more and more interesting and useful things to be done with these structures.
Terryble
TerrybleOPβ€’3y ago
Thank you so much for the very detailed answer. I definitely have a better idea of what I have to do now. I think the main source of my struggle so far has been the learning curve and that I don't even know how to form proper questions simply because of how much there is that I don't know. I really appreciate the support I've been getting from you so far. It's insane to me that the creator of a framework that I'm using has been replying to my questions almost immediately every time.
ZachDaniel
ZachDanielβ€’3y ago
I care a lot about the experience of my users πŸ˜„ I definitely know what you mean about the learning curve (or at least, I've heard about the struggle from lots of people getting started). At the end of the day, its why I've worked so hard to build up the community and will continue to do so, is because it doesn't really make sense to be able to read a getting started guide and then begin building effective applications (with any framework). And Ash has a lot of learning/synthesis of patterns built in, and grokking it all can be very difficult. So I'm here for questions πŸ˜„ Of course, I can only dedicate so much time to it, but its something that will always be a priority for me πŸ™‚
Terryble
TerrybleOPβ€’3y ago
Follow-up question: is this the only guide we have on Ash.Flow? https://ash-hq.org/docs/guides/ash/latest/topics/flows I think this is what I need, but the document doesn’t really tell me how to use Ash.Flow
Ash HQ
Guide: Flows
Read the "Flows" guide on Ash HQ
ZachDaniel
ZachDanielβ€’3y ago
Yeah, pretty much 😦 Its one of the newer parts of the framework, fewer people are using it. Hopefully some that do use it will contribute back to the guides πŸ˜†
Terryble
TerrybleOPβ€’3y ago
Alright. Thanks!
kernel
kernelβ€’3y ago
I should use Flow, just haven't got round to using it yet, but it probably will help me tidy up some nasty parts of my apps I've got a pretty convoluted ordering creation process which ends up creating lots of related resources etc etc and I'm doing that all with what are essentially callbacks and notifications now, bit of a mess the lack of Flow when I started using Ash was one of the few things I missed with regards to something like Trailblazer Operations (for Ruby)

Did you find this page helpful?