diff --git a/documentation/dsls/DSL:-Ash.Resource.md b/documentation/dsls/DSL:-Ash.Resource.md index 7bf76adb..24b75125 100644 --- a/documentation/dsls/DSL:-Ash.Resource.md +++ b/documentation/dsls/DSL:-Ash.Resource.md @@ -2402,7 +2402,7 @@ Declare named aggregates on the resource. These are aggregates that can be loaded only by name using `Ash.Query.load/2`. They are also available as top level fields on the resource. -See the [aggregates guide](/documentation/topics/aggregates.md) for more. +See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. ### Nested DSLs @@ -2449,7 +2449,7 @@ Declares a named count aggregate on the resource Supports `filter`, but not `sort` (because that wouldn't affect the count) -See the [aggregates guide](/documentation/topics/aggregates.md) for more. +See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. ### Nested DSLs @@ -2542,7 +2542,7 @@ Declares a named `exists` aggregate on the resource Supports `filter`, but not `sort` (because that wouldn't affect if something exists) -See the [aggregates guide](/documentation/topics/aggregates.md) for more. +See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. ### Nested DSLs @@ -2632,7 +2632,7 @@ Declares a named `first` aggregate on the resource First aggregates return the first value of the related record that matches. Supports both `filter` and `sort`. -See the [aggregates guide](/documentation/topics/aggregates.md) for more. +See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. ### Nested DSLs @@ -2727,7 +2727,7 @@ Declares a named `sum` aggregate on the resource Supports `filter`, but not `sort` (because that wouldn't affect the sum) -See the [aggregates guide](/documentation/topics/aggregates.md) for more. +See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. ### Nested DSLs @@ -2820,7 +2820,7 @@ Declares a named `list` aggregate on the resource. A list aggregate selects the list of all values for the given field and relationship combination. -See the [aggregates guide](/documentation/topics/aggregates.md) for more. +See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. ### Nested DSLs @@ -2914,7 +2914,7 @@ Declares a named `max` aggregate on the resource Supports `filter`, but not `sort` (because that wouldn't affect the max) -See the [aggregates guide](/documentation/topics/aggregates.md) for more. +See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. ### Nested DSLs @@ -3006,7 +3006,7 @@ Declares a named `min` aggregate on the resource Supports `filter`, but not `sort` (because that wouldn't affect the min) -See the [aggregates guide](/documentation/topics/aggregates.md) for more. +See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. ### Nested DSLs @@ -3098,7 +3098,7 @@ Declares a named `avg` aggregate on the resource Supports `filter`, but not `sort` (because that wouldn't affect the avg) -See the [aggregates guide](/documentation/topics/aggregates.md) for more. +See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. ### Nested DSLs @@ -3192,7 +3192,7 @@ Supports `filter` and `sort`. Custom aggregates provide an `implementation` which must implement data layer specific callbacks. -See the relevant data layer documentation and the [aggregates guide](/documentation/topics/aggregates.md) for more. +See the relevant data layer documentation and the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. ### Nested DSLs @@ -3286,7 +3286,7 @@ Declare named calculations on the resource. These are calculations that can be loaded only by name using `Ash.Query.load/2`. They are also available as top level fields on the resource. -See the [calculations guide](/documentation/topics/calculations.md) for more. +See the [calculations guide](/documentation/topics/resources/calculations.md) for more. ### Nested DSLs @@ -3322,7 +3322,7 @@ To ensure that the necessary fields are selected: 2.) Define a `select/2` callback in the calculation module 3.) Set `always_select?` on the attribute in question -See the [calculations guide](/documentation/topics/calculations.md) for more. +See the [calculations guide](/documentation/topics/resources/calculations.md) for more. ### Nested DSLs @@ -3371,7 +3371,7 @@ argument name, type An argument to be passed into the calculation's arguments map -See the [calculations guide](/documentation/topics/calculations.md) for more. +See the [calculations guide](/documentation/topics/resources/calculations.md) for more. diff --git a/documentation/home.md b/documentation/home.md index d4611c70..86c9aa24 100644 --- a/documentation/home.md +++ b/documentation/home.md @@ -33,6 +33,12 @@ Welcome to the Ash Framework documentation! Here you will find everything you ne - [Glossary](documentation/topics/glossary.md) +### Resources + +- [Attributes](documentation/topics/resources/attributes.md) +- [Calculations](documentation/topics/resources/calculations.md) +- [Aggregates](documentation/topics/resources/aggregates.md) + ### Actions - [Actions](documentation/topics/actions/actions.md) diff --git a/documentation/how_to/auto-format-code.md b/documentation/how_to/auto-format-code.md deleted file mode 100644 index 8c08dc19..00000000 --- a/documentation/how_to/auto-format-code.md +++ /dev/null @@ -1,51 +0,0 @@ -# Auto-Format Ash code - -Ash comes with several utilities that can help keep your modules consistently formatted and organized. - -## Basic setup - -Add `:ash` (and any other Ash libraries you are using) to your `.formatter.exs` file: - -```elixir -[ - # ... - import_deps: [..., :ash], - # ... -] -``` - -This means that when you auto-format your code, either via `mix format` or via an integration in your code editor, the exported data from Ash's `.formatter.exs` will be included and followed. - -It includes definitions for `locals_without_parens`, meaning that your DSL builder code such as `attribute :name, :string` won't have parentheses added (to make it `attribute(:name, :string)`) when formatting the file. The parentheses won't be removed if they currently exist, but they won't be added if missing when formatting. - -## Spark.Formatter - -For more granular formatting, you can use `Spark.Formatter`, from the `spark` library. - -> #### What is `spark`? {: .info} -> -> [`spark`](https://hexdocs.pm/spark) is a small library for building domain-specific languages (DSLs), and is what Ash itself uses internally. It provides the secret sauce to allow you to write your resources declaratively, and the editor integration so you get full autocomplete and documentation for free. - -Add `Spark.Formatter` as a plugin in your `.formatter.exs` file: - -```elixir -[ - # ... - plugins: [..., Spark.Formatter] - # ... -] -``` - -By itself, `Spark.Formatter` doesn't do much - but it is configurable. - -The most common configuration is to remove _all_ extra parentheses from your DSL builder code - this is how the examples within Ash documentation is formatted. - -To enable this, add the following line to your application config in `config.exs`: - -```elixir -config :spark, :formatter, remove_parens?: true, "Ash.Resource": [] -``` - -This tells Spark that it should remove parenthesis from all modules that use `Ash.Resource`, following the `locals_without_parens` rules exported from all your dependencies (and any you may have added yourself). - -`Spark.Formatter` has more configuration available - check the documentation for more details! diff --git a/documentation/topics/development/development-utilities.md b/documentation/topics/development/development-utilities.md index a4f25c2f..f80e6e49 100644 --- a/documentation/topics/development/development-utilities.md +++ b/documentation/topics/development/development-utilities.md @@ -1,12 +1,24 @@ # Development Utilities +## Formatting DSLs + +All Ash packages that ship with extensions provide exports in their `.formatter.exs`. This prevents the formatter from turning, for example, `attribute :name, :string` into `attribute(:name, :string)`. To enable this, add `:ash` (and any other Ash libraries you are using) to your `.formatter.exs` file: + +```elixir +[ + # ... + import_deps: [..., :ash], + # ... +] +``` + ## ElixirSense Plugin Ash uses [Spark](https://hexdocs.pm/spark) to build all of our DSLs (like `Ash.Resource` and `Ash.Domain`) and to validate options lists to functions. `Spark` ships with an extension that is automatically picked up by ElixirLS to provide autocomplete for all of our DSLs, and options list. You don't need to do anything to enable this, but it only works with ElixirLS (not other language server tools). ## Formatter plugin -`Spark` also ships with a formatter plugin that can help you keep your resources formatted consistently. This plugin can sort the sections of your DSL to make your resources more consistent, and it can ensure that the DSL builders don't have parenthesis around them. (i.e `attribute :foo, :bar` vs `attribute(:foo, :bar)`). +`Spark` also ships with a formatter plugin that can help you keep your resources formatted consistently. This plugin can sort the sections of your DSL to make your resources more consistent, and it can remove any accidentally added parentheses around DSL code. ### Adding the plugin diff --git a/documentation/topics/glossary.md b/documentation/topics/glossary.md index 297c8445..0a7aa1a8 100644 --- a/documentation/topics/glossary.md +++ b/documentation/topics/glossary.md @@ -27,7 +27,7 @@ An aggregate is a special type of field for a resource, one that summarizes rela If a Project resource has_many Ticket resources, an example of an aggregate on the Project might be to count the tickets associated to each project. -See the [Aggregates guide](/documentation/topics/aggregates.md) for more. +See the [Aggregates guide](/documentation/topics/resources/aggregates.md) for more. ## Domain @@ -39,7 +39,7 @@ See the [Domains guide](/documentation/topics/domains.md) for more. A piece of data belonging to a resource. The most basic building block; an attribute has a type and a value. For resources backed by a data layer, they typically represent a column in a database table, or a key in an object store, for example. -See the [Attributes guide](/documentation/topics/attributes.md) for more. +See the [Attributes guide](/documentation/topics/resources/attributes.md) for more. ## Authorizer @@ -51,7 +51,7 @@ See the [Actors & Authorization](documentation/topics/security/actors-and-author A calculation is a special type of field for a resource, one that is not directly stored in the data layer but generated on-demand. Typically it will derive from other information on the record, but it may come from some other data source entirely. -See the [Calculations guide](/documentation/topics/calculations.md) for more. +See the [Calculations guide](/documentation/topics/resources/calculations.md) for more. ## Changeset diff --git a/documentation/topics/aggregates.md b/documentation/topics/resources/aggregates.md similarity index 54% rename from documentation/topics/aggregates.md rename to documentation/topics/resources/aggregates.md index cf463c7f..017c90cc 100644 --- a/documentation/topics/aggregates.md +++ b/documentation/topics/resources/aggregates.md @@ -1,6 +1,6 @@ # Aggregates -Aggregates in Ash allow for retrieving summary information over groups of related data. A simple example might be to show the "count of published posts for a user". Aggregates allow us quick and performant access to this data, in a way that supports being filtered/sorted on automatically. More aggregate types can be added, but you will be restricted to only the supported types. In cases where aggregates don't suffice, use [Calculations](/documentation/topics/calculations.md), which are intended to be much more flexible. +Aggregates in Ash allow for retrieving summary information over groups of related data. A simple example might be to show the "count of published posts for a user". Aggregates allow us quick and performant access to this data, in a way that supports being filtered/sorted on automatically. More aggregate types can be added, but you will be restricted to only the supported types. In cases where aggregates don't suffice, use [Calculations](/documentation/topics/resources/calculations.md), which are intended to be much more flexible. ## Declaring aggregates on a resource @@ -16,17 +16,20 @@ end The available aggregate types are: -- `count` - counts related items meeting the criteria -- `first` - gets the first related value matching the criteria. Must specify the `field` to get. -- `sum` - sums the related items meeting the criteria. Must specify the `field` to sum. -- `list` - lists the related values. Must specify the `field` to list. +- `count` - counts related items meeting the criteria. +- `exists` - checks if any related items meet the criteria. +- `first` - gets the first related value matching the criteria. Must specify the `field`. +- `sum` - sums the related items meeting the criteria. Must specify the `field`. +- `list` - lists the related values. Must specify the `field`. +- `max` - gets the maximum related value. Must specify the `field`. +- `min` - gets the minimum related value. Must specify the `field`. +- `avg` - gets the average related value. Must specify the `field`. +- `custom` - allows for a custom aggregate. Implementation depends on the data layer. Must provide an `implementation`. + +The declared set of named aggregates can be used by extensions and referred to throughout your application As an escape hatch, they can also be loaded in the query using `Ash.Query.load/2`, or after the fact using `Ash.load/3`. Aggregates declared on the resource will be keys in the resource's struct. See the docs on `d:Ash.Resource.Dsl.aggregates` for more information. -The aggregates declared on a resource allow for declaring a set of named aggregates that can be used by extensions. - -As an escape hatch, they can also be loaded in the query using `Ash.Query.load/2`, or after the fact using `Ash.load/3`. Aggregates declared on the resource will be keys in the resource's struct. - ## Custom aggregates in the query Custom aggregates can be added to the query and will be placed in the `aggregates` key of the results. This is an escape hatch, and is not the primary way that you should be using aggregates. It does, however, allow for dynamism, i.e if you are accepting user input that determines what the filter and/or field should be, that kind of thing. @@ -36,9 +39,9 @@ Example: ```elixir User |> Ash.Query.aggregate( - :count_of_posts, - :count, - :posts, + :count_of_posts, + :count, + :posts, query: [ filter: [published: published?] ] @@ -52,15 +55,31 @@ See the documentation for `Ash.Query.aggregate/4` for more information. Join filters allows for more complex aggregate queries, including joining with predicates based on multiple related values. ### Example - + ```elixir aggregates do sum :saved_money, [:redeems, :deal], :amount do # where any redeem of the deal is redeemed filter expr(redeems.redeemed == true) - + + # where the `redeems` are `redeemed` join_filter :redeems, expr(redeemed == true) + + # where the `redeems.deal.active` == `redeems.require_active` join_filter [:redeems, :deal], expr(active == parent(require_active)) end - end -``` \ No newline at end of file + end +``` + +## Inline Aggregates + +Aggregates can be created in-line in expressions, with their relationship path specified and any options provided that match the options given to `Ash.Query.Aggregate.new/4`. For example: + +```elixir +calculate :grade, :decimal, expr( + count(answers, query: [filter: expr(correct == true)]) / + count(answers, query: [filter: expr(correct == false)]) +) +``` + +See the [Expressions guide](/documentation/topics/expressions.md#inline-aggregates) for more. diff --git a/documentation/topics/attributes.md b/documentation/topics/resources/attributes.md similarity index 73% rename from documentation/topics/attributes.md rename to documentation/topics/resources/attributes.md index 2b60fbc0..96b95296 100644 --- a/documentation/topics/attributes.md +++ b/documentation/topics/resources/attributes.md @@ -1,25 +1,10 @@ # Attributes -Attributes specify the name, type and properties of a piece of information in a resource. +Attributes specify the `name`, `type` and additional configuration of a simple property of a record. When using SQL data layers, for example, an attribute would correspond to a column in a database table. -## Ways of writing attributes +To see the options available when building attributes, see `d:Ash.Resource.Dsl.attributes.attribute` -There are two ways to write an attribute: - -```elixir -attribute :name, :string, allow_nil?: false - -# or ... -attribute :name, :string do - allow_nil? false -end -``` - -Both ways will work. Though when you're using many options the latter is preferred. This is also true of any other keyword in the Ash DSL, so you can build a flexible yet concise domain model. - -For more information on attribute types including composite types and defining your own custom type see `Ash.Type` - -You can find a comprehensive of attribute options with detailed descriptions on the `d:Ash.Resource.Dsl.attributes` page. +If you are looking to compute values on demand, see the [Calculations guide](/documentation/topics/resources/calculations.md) and the [aggregates guide](/documentation/topics/resources/aggregates.md). ## Special attributes diff --git a/documentation/topics/calculations.md b/documentation/topics/resources/calculations.md similarity index 94% rename from documentation/topics/calculations.md rename to documentation/topics/resources/calculations.md index 6b16b0e7..02efae97 100644 --- a/documentation/topics/calculations.md +++ b/documentation/topics/resources/calculations.md @@ -42,6 +42,8 @@ defmodule Concat do @impl true # A callback to tell Ash what keys must be loaded/selected when running this calculation + # you can include related data here, but be sure to include the attributes you need from said related data + # i.e `posts: [:title, :body]`. def load(_query, opts, _context) do opts[:keys] end @@ -106,5 +108,5 @@ If the calculation uses an expression, you can also filter and sort on it like s ```elixir query |> Ash.Query.filter(full_name(separator: ",")) -|> Ash.Query.sort(full_name: {:asc, %{separator: ","}}) +|> Ash.Query.sort(full_name: {%{separator: ","}, :asc}) ``` diff --git a/documentation/topics/security/sensitive-data.md b/documentation/topics/security/sensitive-data.md index 15decd46..7eba033d 100644 --- a/documentation/topics/security/sensitive-data.md +++ b/documentation/topics/security/sensitive-data.md @@ -6,7 +6,7 @@ By default, attributes, calculations, aggregates and relationships are *private* ## Public & Private Arguments -Public/private arguments work the same way as public/private fields, except that they default to `public?: true`. This is because arguments to an action being usedin a public interface would naturally be expected to be `public`. If an argument is marked as `public?: false`, it can only be set with `Ash.Query.set_argument/3` or `Ash.Changeset.set_argument/3` +Public/private arguments work the same way as public/private fields, except that they default to `public?: true`. This is because arguments to an action being used in a public interface would naturally be expected to be `public`. If an argument is marked as `public?: false`, it can only be set with `Ash.Query.set_argument/3` or `Ash.Changeset.set_argument/3` ## Sensitive Attributes diff --git a/documentation/tutorials/get-started.md b/documentation/tutorials/get-started.md index 839c7c3a..a5f7989c 100644 --- a/documentation/tutorials/get-started.md +++ b/documentation/tutorials/get-started.md @@ -91,7 +91,7 @@ To ensure that your code stays formatted like the examples here, you can add `:a > #### Note {: .neutral} > -> For more auto-formatting options, see the [Auto-Format Code guide](/documentation/how_to/auto-format-code.md). +> For more auto-formatting options, see the [Development Utilities guide](/documentation/topics/development/development-utilities.md). And run `mix deps.get`, to install the dependency. diff --git a/lib/ash/resource/dsl.ex b/lib/ash/resource/dsl.ex index d8f05a4a..aa8eb940 100644 --- a/lib/ash/resource/dsl.ex +++ b/lib/ash/resource/dsl.ex @@ -991,7 +991,7 @@ defmodule Ash.Resource.Dsl do Supports `filter`, but not `sort` (because that wouldn't affect the count) - See the [aggregates guide](/documentation/topics/aggregates.md) for more. + See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. """, examples: [ """ @@ -1022,7 +1022,7 @@ defmodule Ash.Resource.Dsl do First aggregates return the first value of the related record that matches. Supports both `filter` and `sort`. - See the [aggregates guide](/documentation/topics/aggregates.md) for more. + See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. """, examples: [ """ @@ -1053,7 +1053,7 @@ defmodule Ash.Resource.Dsl do Supports `filter`, but not `sort` (because that wouldn't affect the max) - See the [aggregates guide](/documentation/topics/aggregates.md) for more. + See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. """, examples: [ """ @@ -1078,7 +1078,7 @@ defmodule Ash.Resource.Dsl do Supports `filter`, but not `sort` (because that wouldn't affect the min) - See the [aggregates guide](/documentation/topics/aggregates.md) for more. + See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. """, examples: [ """ @@ -1103,7 +1103,7 @@ defmodule Ash.Resource.Dsl do Supports `filter`, but not `sort` (because that wouldn't affect the sum) - See the [aggregates guide](/documentation/topics/aggregates.md) for more. + See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. """, examples: [ """ @@ -1128,7 +1128,7 @@ defmodule Ash.Resource.Dsl do Supports `filter`, but not `sort` (because that wouldn't affect the avg) - See the [aggregates guide](/documentation/topics/aggregates.md) for more. + See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. """, examples: [ """ @@ -1153,7 +1153,7 @@ defmodule Ash.Resource.Dsl do Supports `filter`, but not `sort` (because that wouldn't affect if something exists) - See the [aggregates guide](/documentation/topics/aggregates.md) for more. + See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. """, examples: [ """ @@ -1178,7 +1178,7 @@ defmodule Ash.Resource.Dsl do Custom aggregates provide an `implementation` which must implement data layer specific callbacks. - See the relevant data layer documentation and the [aggregates guide](/documentation/topics/aggregates.md) for more. + See the relevant data layer documentation and the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. """, examples: [ """ @@ -1215,7 +1215,7 @@ defmodule Ash.Resource.Dsl do A list aggregate selects the list of all values for the given field and relationship combination. - See the [aggregates guide](/documentation/topics/aggregates.md) for more. + See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. """, examples: [ """ @@ -1246,7 +1246,7 @@ defmodule Ash.Resource.Dsl do These are aggregates that can be loaded only by name using `Ash.Query.load/2`. They are also available as top level fields on the resource. - See the [aggregates guide](/documentation/topics/aggregates.md) for more. + See the [aggregates guide](/documentation/topics/resources/aggregates.md) for more. """, examples: [ """ @@ -1276,7 +1276,7 @@ defmodule Ash.Resource.Dsl do describe: """ An argument to be passed into the calculation's arguments map - See the [calculations guide](/documentation/topics/calculations.md) for more. + See the [calculations guide](/documentation/topics/resources/calculations.md) for more. """, examples: [ """ @@ -1310,7 +1310,7 @@ defmodule Ash.Resource.Dsl do 2.) Define a `select/2` callback in the calculation module 3.) Set `always_select?` on the attribute in question - See the [calculations guide](/documentation/topics/calculations.md) for more. + See the [calculations guide](/documentation/topics/resources/calculations.md) for more. """, examples: [ { @@ -1340,7 +1340,7 @@ defmodule Ash.Resource.Dsl do These are calculations that can be loaded only by name using `Ash.Query.load/2`. They are also available as top level fields on the resource. - See the [calculations guide](/documentation/topics/calculations.md) for more. + See the [calculations guide](/documentation/topics/resources/calculations.md) for more. """, examples: [ """ diff --git a/mix.exs b/mix.exs index 14f9448e..94fccbe1 100644 --- a/mix.exs +++ b/mix.exs @@ -46,6 +46,7 @@ defmodule Ash.MixProject do "documentation/topics/about_ash/what-is-ash.md", "documentation/topics/about_ash/design-principles.md", "documentation/topics/about_ash/contributing-to-ash.md", + "documentation/topics/resources/attributes.md", "documentation/topics/actions/actions.md", "documentation/topics/actions/read-actions.md", "documentation/topics/actions/create-actions.md", @@ -63,10 +64,8 @@ defmodule Ash.MixProject do "documentation/how_to/structure-your-project.md", "documentation/how_to/use-without-data-layers.md", "documentation/how_to/validate-changes.md", - "documentation/how_to/auto-format-code.md", - "documentation/topics/aggregates.md", - "documentation/topics/attributes.md", - "documentation/topics/calculations.md", + "documentation/topics/resources/aggregates.md", + "documentation/topics/resources/calculations.md", "documentation/topics/code-interface.md", "documentation/topics/constraints.md", "documentation/topics/embedded-resources.md", @@ -101,6 +100,11 @@ defmodule Ash.MixProject do "documentation/tutorials/get-started.md" ], Tutorials: [], + Resources: [ + "documentation/topics/resources/attributes.md", + "documentation/topics/resources/calculations.md", + "documentation/topics/resources/aggregates.md" + ], Actions: [ "documentation/topics/actions/actions.md", "documentation/topics/actions/read-actions.md", @@ -144,10 +148,6 @@ defmodule Ash.MixProject do "documentation/how_to/structure-your-project.md", "documentation/how_to/use-without-data-layers.md", "documentation/how_to/validate-changes.md", - "documentation/how_to/auto-format-code.md", - "documentation/topics/aggregates.md", - "documentation/topics/attributes.md", - "documentation/topics/calculations.md", "documentation/topics/code-interface.md", "documentation/topics/constraints.md", "documentation/topics/embedded-resources.md",