feat: add pipelines DSL for reusable action logic composition#2652
feat: add pipelines DSL for reusable action logic composition#2652zachdaniel merged 12 commits intoash-project:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new pipelines DSL to Ash resources, letting actions reuse shared groups of changes/validations/preparations/arguments/accept via pipe_through, resolved at compile-time by a new transformer.
Changes:
- Introduces
pipelines+pipelineDSL sections andpipe_throughaction declarations (with optionalwhere:gating). - Adds
Ash.Resource.Transformers.ResolvePipelinesto inject/merge pipeline entities, arguments, and accept into actions. - Adds introspection (
Ash.Resource.Info.pipelines/1,pipeline/2) plus tests and DSL documentation updates.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test/resource/actions/pipelines_test.exs | Exercises pipeline injection/ordering/where behavior and merge edge cases. |
| lib/ash/resource/validation.ex | Sets where default to [] for safer list operations during pipeline resolution. |
| lib/ash/resource/transformers/resolve_pipelines.ex | New transformer implementing pipeline resolution and merging into actions. |
| lib/ash/resource/pipeline.ex | New pipeline entity struct + schema. |
| lib/ash/resource/info.ex | Adds pipeline introspection helpers. |
| lib/ash/resource/dsl.ex | Adds pipelines section + pipe_through entity and wires transformer ordering. |
| lib/ash/resource/actions/create.ex | Adds pipe_through field/type to create actions. |
| lib/ash/resource/actions/update.ex | Adds pipe_through field/type to update actions. |
| lib/ash/resource/actions/destroy.ex | Adds pipe_through field/type to destroy actions. |
| lib/ash/resource/actions/read.ex | Adds pipe_through field/type to read actions. |
| lib/ash/resource/actions/action/action.ex | Adds pipe_through field/type to generic actions. |
| lib/ash/resource/actions/pipe_through.ex | New struct + schema for pipe_through declarations (names + where). |
| documentation/dsls/DSL-Ash.Resource.md | Documents new DSL sections/entities. |
| .formatter.exs | Adds pipeline/pipe_through to locals-without-parens. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
27f42a7 to
904cf9b
Compare
|
This is very cool! A few thoughts: I think it would be best not to involve For this:
A few things here:
update :close do
pipe_through [:change_state], where: present(:admin_role) # <- the pipeline happens first
change set_attribute(:state, :closed) # <- this happens second
end
# vs
update :close do
change set_attribute(:state, :closed) # <- this happens first
pipe_through [:change_state], where: present(:admin_role) # <- the pipeline happens second
end |
|
Thanks for the feedback! On
|
Yep! I agree with that approach.
This part is very complex. I do agree that its useful, but also consider the confusion around things like this: create :create do
pipe_through [:pipe]
change set_attribute(:foo, arg(:foo))
end
pipelines do
pipeline :pipe do
argument :foo, :string
...
end
endBy disconnecting the accept/arguments from their action we create a situation where its very easy to accidentally break action interfaces by changing pipelines. I see what you are saying on the value here and I'm very willing to continue the conversation, but I think that we should consider that more carefully and separate it into a separate PR. For example, we might want something like this: # explicitly inherit these items
pipe_through [:pipe], accept: [:foo, :bar], arguments: [:foo, :bar]So the point there is that if a pipe there doesn't include that accept/argument etc. you'd find out at compile time. But I think the above conversation needs more design considerations. |
| ``` | ||
|
|
||
| ``` | ||
| pipe_through [:change_state], where: expr(^actor(:role) == :super_user) |
There was a problem hiding this comment.
This example isn't quite right, you can't currently use an expr this way, only validations. (something I'd like to support in the future).
|
One last comment that I noticed and then we're good to go 😄 |
|
🚀 Thank you for your contribution! 🚀 |
Contributor checklist
Leave anything that you believe does not apply unchecked.
Summary
pipelinesDSL section for declaring reusable groups of changes, validations, preparations, arguments, and accepted attributes that can be composed into multiple actions viapipe_throughUsage
How it works
A compile-time transformer (
ResolvePipelines) runs before other action transformers and:changes+validationsinto CUD actions,preparations+validationsinto read/generic actions. Pipeline entities are prepended before the action's own.:*is preserved,nilaccept gets the pipeline's value.pipe_through [:name], where: present(:field)applies the condition to all entities from that pipeline. Multiplepipe_throughdeclarations are supported with independentwhereclauses.