Skip to content

Commit 80331de

Browse files
committed
docs: add docs for pipelines
1 parent efe10ce commit 80331de

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed

documentation/topics/actions/actions.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,115 @@ Ticket
239239

240240
That is the best of both worlds! These same lessons transfer to all action types (changeset-based, query-based, and generic actions) as well.
241241

242+
## Pipelines
243+
244+
Pipelines let you define reusable groups of changes, validations, and preparations that can be shared across multiple actions. Instead of duplicating the same logic in every action, you define it once in a `pipeline` and reference it with `pipe_through`.
245+
246+
### Defining Pipelines
247+
248+
Pipelines are declared in a top-level `pipelines` block on the resource. Each pipeline can contain `change`, `validate`, and `prepare` declarations, just like an action.
249+
250+
```elixir
251+
pipelines do
252+
pipeline :audited do
253+
change set_attribute(:updated_by, actor(:id))
254+
validate present(:updated_by)
255+
end
256+
257+
pipeline :sorted_by_name do
258+
prepare build(sort: [:name])
259+
end
260+
end
261+
```
262+
263+
### Using Pipelines in Actions
264+
265+
Reference pipelines from any action using `pipe_through`:
266+
267+
```elixir
268+
actions do
269+
create :create do
270+
accept [:name, :email]
271+
pipe_through [:audited]
272+
end
273+
274+
update :update do
275+
accept [:name, :email]
276+
pipe_through [:audited]
277+
end
278+
279+
read :list do
280+
pipe_through [:sorted_by_name]
281+
end
282+
end
283+
```
284+
285+
The pipeline's entities are expanded inline at compile time — the action behaves exactly as if you had written the changes, validations, and preparations directly.
286+
287+
### How Entity Types Are Resolved
288+
289+
Pipelines can contain changes, validations, and preparations, but not all of these apply to every action type:
290+
291+
- **Create, Update, Destroy actions** receive the pipeline's `change` and `validate` entities. Preparations are ignored.
292+
- **Read and Generic actions** receive the pipeline's `prepare` and `validate` entities. Changes are ignored.
293+
294+
This means you can define a pipeline with all three entity types and safely reference it from any action type — only the applicable entities will be included.
295+
296+
### Ordering
297+
298+
Pipeline entities are inserted **at the position of the `pipe_through` declaration** within the action. This gives you full control over ordering:
299+
300+
```elixir
301+
create :create do
302+
accept [:name]
303+
change set_attribute(:state, :before) # runs first
304+
pipe_through [:my_pipeline] # pipeline entities run second
305+
change set_attribute(:name, "after") # runs third
306+
end
307+
```
308+
309+
When referencing multiple pipelines in a single `pipe_through`, their entities are appended in the order listed:
310+
311+
```elixir
312+
pipe_through [:pipeline_a, :pipeline_b]
313+
# pipeline_a entities first, then pipeline_b entities
314+
```
315+
316+
You can also use multiple `pipe_through` declarations in the same action:
317+
318+
```elixir
319+
create :create do
320+
pipe_through [:audit]
321+
pipe_through [:guard], where: present(:name)
322+
accept [:name]
323+
end
324+
```
325+
326+
### Conditional Pipelines with `where`
327+
328+
You can make a pipeline's entities conditional using the `where` option on `pipe_through`. The `where` conditions are prepended to each entity's existing `where` conditions:
329+
330+
```elixir
331+
create :create do
332+
accept [:name]
333+
pipe_through [:premium_features], where: attribute_equals(:role, :premium)
334+
end
335+
```
336+
337+
If a pipeline entity already has its own `where` condition, both conditions must pass for the entity to apply.
338+
339+
### Introspection
340+
341+
You can inspect pipelines at runtime using `Ash.Resource.Info`:
342+
343+
```elixir
344+
# List all pipelines on a resource
345+
Ash.Resource.Info.pipelines(MyResource)
346+
347+
# Get a specific pipeline by name
348+
Ash.Resource.Info.pipeline(MyResource, :audited)
349+
```
350+
242351
## Private Inputs
243352

244353
The concept of a "private input" can be somewhat paradoxical, but it can be used by actions that require something provided by the "system",

0 commit comments

Comments
 (0)