It’s all about the domain.

When I first heard about Ash, I assumed it was “Rails for Elixir,” a set of conventions to reduce boilerplate and get an app moving quickly. Rails is great at that, but as projects grow beyond the conventions, it can be harder to adapt without extra workarounds.

After ElixirConf I decided I needed to know more, so I picked up the Ash Framework book.

Ash Framework

Learn how to use Ash Framework to build robust, maintainable Elixir applications. This book covers the principles and practices of domain-driven design with Ash's declarative approach.

From Persistence to Domain

Ash frames its approach around three principles:

  • Prefer data over code
  • Prefer deriving logic over hand-writing it
  • Prefer describing what should happen over how to do it

When we design a system, we usually begin with high-level concepts: the things we want to model and how they relate. Most frameworks then have us translate those ideas into persistence concerns such as tables, APIs, or controllers. Ash challenges that assumption by asking: what if we modeled the domain itself, and let everything else follow?

How This Plays Out in Practice

Ash uses code generation — a lot of code generation. Most of the book is about teaching us Ash’s code generation patterns.

One-time Scaffolds

mix ash.gen.resource creates a new resource module with placeholder DSL sections. This pattern appears repeatedly: Album in Chapter 2, Track in Chapter 8, ArtistFollower in Chapter 9, and Notification in Chapter 10. Ash includes other generators of the same kind such as mix ash.gen.domain, mix ash.gen.enum, and mix ash.gen.validation. These reduce initial typing, but once generated, the code is ours to maintain.

Repeatable Generators

mix ash_phoenix.gen.live, introduced in Chapter 8, is similar to Phoenix’s LiveView generator, but it derives its fields directly from an Ash resource. Because it draws from the resource definition, we can re-run it as the resource evolves. That makes it practical for prototyping, though each regeneration overwrites styling and layout changes, so it is best to stabilize the data layer first.

Ongoing Regeneration

mix ash.codegen regenerates supporting code based on the current state of our resources. In ash_postgres, for example, it doesn’t emit empty migration templates like Ecto. It compares our resource definitions to the existing schema and generates migration logic for the differences. Each run creates a new migration file, preserving history. Other extensions take a different approach. The TypeScript generator, for instance, overwrites artifacts like type definitions so they always match the current state.

Compile-time Macros

Ash also generates code through macros at compile time. Unlike CLI generators, which leave behind files we edit, macros inject functions directly into our modules whenever we recompile. The principle is the same: declare intent once, and the framework expands that intent into a family of imperative functions. The lifecycle differs, however. CLI tools create permanent scaffolds, while macro-based generation produces ephemeral code that is recreated on each compile.

Metaprogramming and Convention

Rails and Ash both use metaprogramming to turn declarative definitions into imperative methods. The difference is timing: Rails uses Ruby’s runtime metaprogramming, while Ash uses Elixir’s compile-time macros.

In Rails, an association like this:

has_many :posts

expands into helpers such as user.posts and user.posts.build. We don’t write those ourselves; they’re defined by convention.

In Ash, an action like this:

create :create

expands into functions like create_artist/2 and can_create_artist?/1. Again, we don’t write them; recompilation injects them into our domain module.

Model Your Domain

Rails, Django, and Spring all start from a data model. You declare a table, a model, or an entity, and the framework generates controllers, query helpers, and views outward from persistence.

Ash starts from the domain. The book describes it as a way to “describe and build the domain model of your applications—the ‘things’ that make up what your app is supposed to do, and the business logic of how they relate and interact with each other.” One of its core principles is “Derive > Hand-write.” Or as the book asks: “Why should you need to restate your application logic in five different ways?”

So yes, Ash definitely feels inspired by Rails. But what makes it different, and frankly more interesting, is that it starts from the domain.