ash-framework-idioms
Write idiomatic Ash framework code using intentful actions, code interfaces, relationship management, DSL-first modeling, and framework-native changes/preparations instead of ad hoc helper layers. Use when creating or reviewing Ash resources, domains, actions, queries, or Phoenix integrations.
- License 许可
- MIT
- 最近更新
- 2026-11-11
- Stars
- ★ 0
- Forks
- 0
01 前言 Front Matter
- name
- ash-framework-idioms
- description
- Write idiomatic Ash framework code using intentful actions, code interfaces, relationship management, DSL-first modeling, and framework-native changes/preparations instead of ad hoc helper layers. Use when creating or reviewing Ash resources, domains, actions, queries, or Phoenix integrations.
- compatibility
- opencode
02 资源文件 1
- 许可证LICENSE1.0 KB
03 SKILL.md 正文
Use this skill to keep Ash code aligned with the framework's intended style instead of recreating Ecto-context patterns or building custom wrapper layers around features Ash already provides.
Core mindset
Ash applications should express domain behavior in:
- resources
- actions
- relationships
- calculations and aggregates
- changes, validations, and preparations
- code interfaces
Prefer Ash-native DSL and action modeling over hand-written helper modules that assemble queries and changesets everywhere.
Primary rules
- Put business behavior in well-named actions.
- Use code interfaces for function-style access from the rest of the app.
- Keep controllers, LiveViews, and other web modules thin.
- Prefer resource capabilities over bespoke helper functions.
- Reach for
Ash.Query/Ash.Changesetdirectly only when customizing invocation of an already meaningful action, or when building framework-level tooling.
Actions over helpers
Ash is not meant to stop at generic CRUD.
Prefer:
- many small, intentful actions
:open,:archive,:top,:assign_reviewer,:publish,:approve
Over:
- one generic
:read,:update,:destroyplus a pile of helper functions in contexts or LiveViews
Bad shape:
- web layer builds filters, sorting, limits, and domain rules manually every time
Better shape:
- define a named action that captures the behavior
- expose it via a code interface
- optionally customize the query when needed at the call site
If you see a helper like top_tickets(user_id) that mostly builds an Ash query, strongly consider making it a read action plus a code interface instead.
Code interfaces are the default app-facing API
When Ash resources/actions are called from application code, prefer code interfaces on the domain or resource.
Use them for:
- create/update/destroy flows
get_byaccessors- calculation exposure
- authorization checks
Prefer:
MyApp.Support.open_ticket(subject)MyApp.Dashboards.get_dashboard_group_by_id!(id, load: [...])
Over:
- calling
Ash.get!,Ash.read!,Ash.create!, etc. directly from controllers, LiveViews, or unrelated modules unless the module is intentionally composing/customizing the action call
Important guidance:
- use
get_bycode interfaces instead of sprinklingAsh.get!in web modules - use generated
can_*?functions for UI authorization checks when relevant - use
default_optionssparingly for stable interface defaults
DSL-first modeling
Ask before writing helper code: "Does Ash already have a way to model this?"
Usually prefer Ash features like:
- action arguments
accept/default_accept- validations
- changes
- preparations
- calculations
- aggregates
- identities
- relationships and
manage_relationship - policies
Avoid writing boilerplate conversion or orchestration code if the DSL can describe it directly.
Use the right extension point
- Changes customize create/update/destroy behavior.
- Preparations customize read and generic action behavior.
- Validations enforce business rules.
- Calculations expose derived values.
- Aggregates expose counts/sums/etc.
- Relationships model traversal and related-data management.
Do not put read-query logic into custom helper modules when a preparation on a read action is the right home. Do not put write-side orchestration into controllers when a change on the action is the right home.
Relationship management
Prefer manage_relationship when the intent is standard relationship CRUD from action input.
Use change manage_relationship(...) when:
- input comes from action arguments
- you want AshPhoenix/AshGraphQL/AshJsonApi to understand the behavior naturally
Use Ash.Changeset.manage_relationship/4 directly when:
- building related values programmatically in a custom change
- the flow is conditional or transformed before relationship management
Avoid manually updating foreign keys and join records unless there is a genuinely special case.
Keep web layers thin
In Phoenix or LiveView code:
- call domain/resource code interfaces
- pass action args and options
- keep filtering, loading, and domain semantics in the Ash layer
Do not treat Ash like a glorified Repo wrapper.
Bad pattern:
- controller or LiveView manually builds query/filter/sort/load chains that represent domain behavior
Good pattern:
- named resource action + code interface, with optional query customization only when truly needed
Defaults are for bootstrapping, not the end state
defaults [...] is a good starting point, but mature Ash resources often need more expressive action sets.
Do not stop at generic CRUD if the resource has meaningful domain operations.
Custom changes and preparations
- Anonymous functions are fine for prototyping.
- Prefer modules for lasting behavior.
- Module-based changes/preparations are more reusable, more organized, and support advanced features like atomic or batch behavior.
If behavior matters enough to keep, give it a proper module.
Nested actions and context
When invoking nested actions from changes or preparations:
- preserve scope/context properly
- prefer
scope: contextor the relevant Ash helpers for threading shared context
Do not manually drop important actor/tenant/shared information.
Query customization guidance
It is okay to customize invocation outside the action when necessary, for example:
- adding an extra filter to a named read action
- adding dynamic loads
- providing tenant/actor/context options
But the base meaning should still live in the action itself.
Think:
- actions define the capability
- code interfaces expose the capability ergonomically
- ad hoc query building refines the capability for a specific call site
Review checklist
When writing or reviewing Ash code, check:
- Should this helper actually be an action?
- Should this direct
Ash.*call be a code interface call instead? - Is query logic living in a preparation or named read action where it belongs?
- Is write logic living in changes/validations/manage_relationship where it belongs?
- Is the web layer too aware of Ash internals?
- Is this recreating framework behavior that Ash already provides?
Good outcomes
Idiomatic Ash code tends to have:
- thin web modules
- rich, named actions
- clear code interfaces
- less boilerplate helper code
- fewer manual query pipelines scattered across the app
- behavior described declaratively in the resource/domain DSL
04 同仓库更多 Skills
skill-creator
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
modern-astro-v6
Build idiomatic Astro v6 applications with server-first rendering, islands, content collections, actions, and current v6-safe APIs. Use when creating, reviewing, or migrating Astro projects, pages, components, content layers, or integrations.
frontend-design
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
elixir-idioms
Write idiomatic Elixir and Phoenix code with pattern matching, small composable functions, OTP-friendly boundaries, and BEAM-aware error handling. Use when creating or reviewing Elixir code, APIs, GenServers, Phoenix apps, Ecto code, or tests.
effect-ts-idioms
Write idiomatic modern Effect TypeScript using Effect.gen or clear pipelines, typed error channels, Context and Layer services, and resource-safe runtime boundaries. Use when building or reviewing code with Effect, @effect/platform, Schema, Layers, or Effect-based applications.