diff --git a/documentation/topics/pagination.md b/documentation/topics/pagination.md index cdd2c4d5..456f0f24 100644 --- a/documentation/topics/pagination.md +++ b/documentation/topics/pagination.md @@ -4,7 +4,7 @@ Pagination is configured at the action level. There are two kinds of pagination pros and cons to each. An action can support both at the same time, or only one (or none). A full count of records can be requested by passing `page: [count: true]`, but it should be kept in mind that doing this requires running the same query twice, one of which is a count of all records. Ash does these in parallel, but it can still be quite expensive on large -datasets. For more information on the options for configuring actions to support pagination, see the [pagination section](Ash.Resource.Dsl.html#module-pagination) in `Ash.Resource.Dsl`. +datasets. For more information on the options for configuring actions to support pagination, see the {{link:ash:dsl:resource/actions/read/prepare}} ## Offset Pagination @@ -28,8 +28,8 @@ Api.read(Resource, page: [limit: 10, offset: 10]) ### Offset Cons -- Does not perform well on large datasets -- When moving between pages, if data was created or deleted, records may appear on multiple pages or be skipped +- Does not perform well on large datasets (if you have to ask if your dataset is "large", it probably isn't) +- When moving between pages, if data was created or deleted, records may appear on multiple pages ## Keyset Pagination @@ -49,11 +49,6 @@ last_record = List.last(page.results) next_page = Api.read(Resource, page: [limit: 10, after: last_record.__metadata__.keyset]) ``` -### Important Limitation - -Keyset pagination cannot currently be used in conjunction with aggregate and calculation sorting. -Combining them will result in an error on the query. - ### Keyset Pros - Performs very well on large datasets (assuming indices exist on the columns being sorted on) @@ -61,10 +56,5 @@ Combining them will result in an error on the query. ### Keyset Cons -- A bit more complex +- A bit more complex to use - Can't go to a specific page number -- Can't use aggregate and calculation sorting (at the moment, this will change soon) - -For more information on keyset vs offset based pagination, see: - -- [Offset vs Seek Pagination](https://taylorbrazelton.com/posts/2019/03/offset-vs-seek-pagination/) diff --git a/documentation/topics/policies.md b/documentation/topics/policies.md index 496e952d..66e2ddc8 100644 --- a/documentation/topics/policies.md +++ b/documentation/topics/policies.md @@ -117,7 +117,45 @@ Keep in mind that, for create actions, many `expr/1` checks won't make sense, an #### Using exists -In these and in other filter checks, it is advised to use `exists/2` when referring to relationships, because of the way that the policy authorizer may mix & match your policies when building filters. There is a semantic difference in filters between `friends.first_name == "ted" and friends.last_name == "dansen"`. This means that you have a *single* friend with the first_name "bob" and the last name "fred". If you use `exists`, then your policies can be used in filters without excluding unnecessary data, i.e `exists(friends, first_name == "ted") and exists(friends, last_name == "dansen")` means "you have one friend with the first_name "ted" and one friend with the last_name "dansen". +Lets compare the following expressions: + +Filtering on related data by directly referencing the relationship +```elixir +friends.first_name == "ted" and friends.last_name == "dansen" +``` + +Filtering on related data using `exists/2` + +```elixir +exists(friends, first_name == "ted") and exists(friends, last_name == "dansen") +``` + +In policies (and often any time you mean "a related thing exists where some condition is true") you should generally prefer filter checks, it is advised to use `exists/2` when referring to relationships, because of the way that the policy authorizer may mix & match your policies when building filters. This is also true when adding filters to actions. If you use `exists`, then your policies can be used in filters without excluding unnecessary data, i.e `exists(friends, first_name == "ted") and exists(friends, last_name == "dansen")` means "you have one friend with the first_name "ted" and one friend with the last_name "dansen". For instance, imagine a scenario where you have an action like this: + +```elixir +read :friends_of_ted do + filter expr(friends.first_name == "ted") +end +``` + +And someone calls it like so: +```elixir +Resource +|> Ash.Query.for_read(:friends_of_ted) +|> Ash.Query.filter(friends.last_name == "dansen") +``` + +The resulting filter is `friends.first_name == "ted" and friends.last_name == "dansen"`. This means that there must be one friend with the name "ted dansen". Sometimes that *is* what you mean to do, but generally speaking I would expect the above code to say "friends of ted that also have a friend with the last name `"dansen"`". To accomplish that, we can rework the example like so: +```elixir +read :friends_of_ted do + filter expr(exists(friends, first_name == "ted")) +end + +# Calling it +Resource +|> Ash.Query.for_read(:friends_of_ted) +|> Ash.Query.filter(exists(friends, last_name == "dansen")) +``` #### How expressions are used diff --git a/documentation/topics/pub_sub.md b/documentation/topics/pub_sub.md index 4e1d9c8b..df35138c 100644 --- a/documentation/topics/pub_sub.md +++ b/documentation/topics/pub_sub.md @@ -16,7 +16,7 @@ prefix "user" publish :create, ["created", :user_id] ``` -This might publish a message to \"user:created:1\"" for example. +This might publish a message to "user:created:1" for example. For updates, if the field in the template is being changed, a message is sent to *both* values. So if you change `user 1` to `user 2`, the same message would