Using Ash Embedded Resources as DTOs in Phoenix Controllers

Is there a recommended way of transforming params from a Phoenix controller function into an embedded Ash Resource (cast + validate)? The idea for this is using the embedded resources as DTOs and keeping intentional separation between the public interface (ie API) and the data layer.

Ideally for validation, I’d prefer to use the properties and constraints already attached to the attribute instead of defining a validations block.

I’ve tried several ways but each seem to be a little verbose. Any advise is greatly appreciated.

1 Like

You can use embedded resources that way, yes. I can’t think of anything that would necessarily make it less verbose, at least not without seeing your initial stab at an implementation.

However, actions have tools for creating a separate interface from the data that they map to.

create :create do
  accept []

  argument :foo, :string
  argument :bar, :string

  change set_attribute(:internal_foo, arg(:foo))
  change set_attribute(:internal_bar, arg(:bar))
end


....
code_interface do
  ...
  define :create, args: [:foo, :bar]
end

In that instance, you’ve got YourResource.create(foo, bar) that acts as your public in-code interface to the resource. How much is the separate DTO really doing for you past that point?

I have a few scenarios where the parameters aren’t used for directly loading a resource. One case is an OTP code verification where I want to validate the code sent in against a pattern. The code is eventually used for comparison but it isn’t directly used for loading the “challenge” resource.

Another case is where I need to load multiple resources off a single endpoint operation to perform a calculation and return an embedded resource of the result (such as finding all schedule openings for a specific time range).

You might be interested in generic actions for those cases. Generic actions return any instance of an Ash type (or an error).

action :say_hello, :string do
  argument :name, :string, allow_nil?: false

  run fn input, _ -> 
    {:ok, "Hello #{input.arguments.name}"}
  end
end

With generic actions, is there a way to have them execute from the owning resource when using code_interface? To borrow the example with :say_hello, I’d like to increment the count of invocations by triggering a separate update action within the resource after executing some other arbitrary function inside run.

Yes, you can use define to define a function that calls a generic action just like the other action types, if I’m understanding your question. You can also use transaction? true inside the generic action if you want to transactionally compose multiple operations.

I was trying to see if, when using a generic action with a code interface, that it could generate a 3 arity function instead of 2 arity. Or does it essentially just take a map that has keys for the resource and arguments of the action.

What would an example of transaction? true look like? Are you saying that the other actions should be called from outside the action? Is there a cleaner way of calling another action inside of run/2?

You can generate any arity you want.

action :say_hello, :string do
  transaction? true # unnecessary for this example
  argument :name, :string, allow_nil?: false

  run fn input, _ ->
    {:ok, “Hello #{input.arguments.name}”}
  end
end

define :say_hello, args: [:name]