improvement: support select_by_default? flag on attributes

This commit is contained in:
Zach Daniel 2024-09-10 13:24:56 -04:00
parent 0f31d463d9
commit e33dc23a07
19 changed files with 117 additions and 63 deletions

View file

@ -187,6 +187,7 @@ spark_locals_without_parens = [
resource: 1, resource: 1,
resource: 2, resource: 2,
run: 1, run: 1,
select_by_default?: 1,
sensitive?: 1, sensitive?: 1,
short_name: 1, short_name: 1,
skip_global_validations?: 1, skip_global_validations?: 1,

View file

@ -89,6 +89,7 @@ end
| [`description`](#attributes-attribute-description){: #attributes-attribute-description } | `String.t` | | An optional description for the attribute. | | [`description`](#attributes-attribute-description){: #attributes-attribute-description } | `String.t` | | An optional description for the attribute. |
| [`sensitive?`](#attributes-attribute-sensitive?){: #attributes-attribute-sensitive? } | `boolean` | `false` | Whether or not the attribute value contains sensitive information, like PII(Personally Identifiable Information). See the [Sensitive Data guide](/documentation/topics/security/sensitive-data.md) for more. | | [`sensitive?`](#attributes-attribute-sensitive?){: #attributes-attribute-sensitive? } | `boolean` | `false` | Whether or not the attribute value contains sensitive information, like PII(Personally Identifiable Information). See the [Sensitive Data guide](/documentation/topics/security/sensitive-data.md) for more. |
| [`source`](#attributes-attribute-source){: #attributes-attribute-source } | `atom` | | If the field should be mapped to a different name in the data layer. Support varies by data layer. | | [`source`](#attributes-attribute-source){: #attributes-attribute-source } | `atom` | | If the field should be mapped to a different name in the data layer. Support varies by data layer. |
| [`select_by_default?`](#attributes-attribute-select_by_default?){: #attributes-attribute-select_by_default? } | `boolean` | `true` | Whether or not the attribute is selected by default. |
| [`always_select?`](#attributes-attribute-always_select?){: #attributes-attribute-always_select? } | `boolean` | `false` | Whether or not to ensure this attribute is always selected when reading from the database, regardless of applied select statements. | | [`always_select?`](#attributes-attribute-always_select?){: #attributes-attribute-always_select? } | `boolean` | `false` | Whether or not to ensure this attribute is always selected when reading from the database, regardless of applied select statements. |
| [`primary_key?`](#attributes-attribute-primary_key?){: #attributes-attribute-primary_key? } | `boolean` | `false` | Whether the attribute is the primary key. Composite primary key is also possible by using `primary_key? true` in more than one attribute. If primary_key? is true, allow_nil? must be false. | | [`primary_key?`](#attributes-attribute-primary_key?){: #attributes-attribute-primary_key? } | `boolean` | `false` | Whether the attribute is the primary key. Composite primary key is also possible by using `primary_key? true` in more than one attribute. If primary_key? is true, allow_nil? must be false. |
| [`allow_nil?`](#attributes-attribute-allow_nil?){: #attributes-attribute-allow_nil? } | `boolean` | `true` | Whether or not the attribute can be set to nil. If nil value is given error is raised. | | [`allow_nil?`](#attributes-attribute-allow_nil?){: #attributes-attribute-allow_nil? } | `boolean` | `true` | Whether or not the attribute can be set to nil. If nil value is given error is raised. |

View file

@ -782,11 +782,15 @@ defmodule Ash.Actions.Helpers do
resource resource
|> Ash.Resource.Info.attributes() |> Ash.Resource.Info.attributes()
|> Enum.flat_map(fn attribute -> |> Enum.flat_map(fn attribute ->
if is_nil(select) do
attribute.select_by_default?
else
if attribute.always_select? || attribute.primary_key? || attribute.name in select do if attribute.always_select? || attribute.primary_key? || attribute.name in select do
[] []
else else
[attribute.name] [attribute.name]
end end
end
end) end)
|> Enum.reduce(result, fn key, record -> |> Enum.reduce(result, fn key, record ->
record record
@ -804,22 +808,4 @@ defmodule Ash.Actions.Helpers do
results results
end end
end end
def attributes_to_select(%{select: nil, resource: resource}) do
resource
|> Ash.Resource.Info.attributes()
|> Enum.map(& &1.name)
end
def attributes_to_select(%{select: select, resource: resource}) do
resource
|> Ash.Resource.Info.attributes()
|> Enum.flat_map(fn attribute ->
if attribute.always_select? || attribute.primary_key? || attribute.name in select do
[attribute.name]
else
[]
end
end)
end
end end

View file

@ -898,12 +898,10 @@ defmodule Ash.Actions.Read do
if query.select do if query.select do
query query
else else
to_select = Ash.Query.select(
query.resource query,
|> Ash.Resource.Info.attributes() Ash.Resource.Info.selected_by_default_attribute_names(query.resource)
|> Enum.map(& &1.name) )
Ash.Query.select(query, to_select)
end end
end end

View file

@ -438,11 +438,23 @@ defmodule Ash.Changeset do
""" """
def select(changeset, fields, opts \\ []) do def select(changeset, fields, opts \\ []) do
if opts[:replace?] do if opts[:replace?] do
%{changeset | select: Enum.uniq(List.wrap(fields))} case fields do
%MapSet{} = fields -> %{changeset | select: Enum.to_list(fields)}
fields -> %{changeset | select: Enum.uniq(List.wrap(fields))}
end
else else
case fields do
%MapSet{} ->
%{
changeset
| select: MapSet.union(MapSet.new(changeset.select), fields) |> MapSet.to_list()
}
fields ->
%{changeset | select: Enum.uniq(List.wrap(fields) ++ (changeset.select || []))} %{changeset | select: Enum.uniq(List.wrap(fields) ++ (changeset.select || []))}
end end
end end
end
@doc """ @doc """
Calls the provided load statement on the result of the action at the very end of the action. Calls the provided load statement on the result of the action at the very end of the action.
@ -476,10 +488,7 @@ defmodule Ash.Changeset do
if changeset.select do if changeset.select do
Ash.Changeset.select(changeset, List.wrap(fields)) Ash.Changeset.select(changeset, List.wrap(fields))
else else
to_select = to_select = Ash.Resource.Info.selected_by_default_attribute_names(changeset.resource)
changeset.resource
|> Ash.Resource.Info.attributes()
|> Enum.map(& &1.name)
Ash.Changeset.select(changeset, to_select) Ash.Changeset.select(changeset, to_select)
end end

View file

@ -844,9 +844,14 @@ defmodule Ash.Query do
Use `ensure_selected/2` if you wish to make sure a field has been selected, without deselecting any other fields. Use `ensure_selected/2` if you wish to make sure a field has been selected, without deselecting any other fields.
""" """
def select(query, fields, opts \\ []) do def select(query, fields, opts \\ []) do
fields =
case fields do
%MapSet{} = fields -> fields
fields -> MapSet.new(List.wrap(fields))
end
query = new(query) query = new(query)
existing_fields = Ash.Resource.Info.attribute_names(query.resource) existing_fields = Ash.Resource.Info.attribute_names(query.resource)
fields = MapSet.new(List.wrap(fields))
valid_fields = MapSet.intersection(fields, existing_fields) valid_fields = MapSet.intersection(fields, existing_fields)
@ -863,16 +868,16 @@ defmodule Ash.Query do
query query
end end
always_select = select =
valid_fields valid_fields
|> MapSet.union(Ash.Resource.Info.always_selected_attribute_names(query.resource)) |> MapSet.union(Ash.Resource.Info.always_selected_attribute_names(query.resource))
|> MapSet.union(MapSet.new(Ash.Resource.Info.primary_key(query.resource))) |> MapSet.union(MapSet.new(Ash.Resource.Info.primary_key(query.resource)))
new_select = new_select =
if opts[:replace?] do if opts[:replace?] do
always_select select
else else
MapSet.union(MapSet.new(query.select || []), always_select) MapSet.union(MapSet.new(query.select || []), select)
end end
%{query | select: MapSet.to_list(new_select)} %{query | select: MapSet.to_list(new_select)}
@ -1021,10 +1026,7 @@ defmodule Ash.Query do
if query.select do if query.select do
Ash.Query.select(query, List.wrap(fields)) Ash.Query.select(query, List.wrap(fields))
else else
to_select = to_select = Ash.Resource.Info.selected_by_default_attribute_names(query.resource)
query.resource
|> Ash.Resource.Info.attributes()
|> Enum.map(& &1.name)
Ash.Query.select(query, to_select) Ash.Query.select(query, to_select)
end end
@ -1063,22 +1065,26 @@ defmodule Ash.Query do
select = select =
if query.select do if query.select do
query.select query.select -- List.wrap(fields)
else else
query.resource MapSet.difference(
|> Ash.Resource.Info.attributes() Ash.Resource.Info.selected_by_default_attribute_names(query.resource),
|> Enum.map(& &1.name) MapSet.new(List.wrap(fields))
)
end end
select = select -- List.wrap(fields)
select(query, select, replace?: true) select(query, select, replace?: true)
end end
def selecting?(query, field) do def selecting?(query, field) do
case query.select do case query.select do
nil -> nil ->
not is_nil(Ash.Resource.Info.attribute(query.resource, field)) query.resource
|> Ash.Resource.Info.attribute(field)
|> case do
%{select_by_default?: true} -> true
_ -> false
end
select -> select ->
if field in select do if field in select do

View file

@ -10,6 +10,7 @@ defmodule Ash.Resource.Attribute do
:public?, :public?,
:writable?, :writable?,
:always_select?, :always_select?,
:select_by_default?,
:default, :default,
:update_default, :update_default,
:description, :description,
@ -39,6 +40,7 @@ defmodule Ash.Resource.Attribute do
primary_key?: boolean(), primary_key?: boolean(),
public?: boolean(), public?: boolean(),
sortable?: boolean(), sortable?: boolean(),
select_by_default?: boolean(),
default: nil | term | (-> term), default: nil | term | (-> term),
update_default: nil | term | (-> term) | (Ash.Resource.record() -> term), update_default: nil | term | (-> term) | (Ash.Resource.record() -> term),
sensitive?: boolean(), sensitive?: boolean(),
@ -79,6 +81,13 @@ defmodule Ash.Resource.Attribute do
If the field should be mapped to a different name in the data layer. Support varies by data layer. If the field should be mapped to a different name in the data layer. Support varies by data layer.
""" """
], ],
select_by_default?: [
type: :boolean,
default: true,
doc: """
Whether or not the attribute is selected by default.
"""
],
always_select?: [ always_select?: [
type: :boolean, type: :boolean,
default: false, default: false,

View file

@ -1496,6 +1496,7 @@ defmodule Ash.Resource.Dsl do
Ash.Resource.Verifiers.ValidateRelationshipAttributesMatch, Ash.Resource.Verifiers.ValidateRelationshipAttributesMatch,
Ash.Resource.Verifiers.VerifyReservedCalculationArguments, Ash.Resource.Verifiers.VerifyReservedCalculationArguments,
Ash.Resource.Verifiers.VerifyIdentityFields, Ash.Resource.Verifiers.VerifyIdentityFields,
Ash.Resource.Verifiers.VerifySelectedByDefault,
Ash.Resource.Verifiers.EnsureAggregateFieldIsAttributeOrCalculation, Ash.Resource.Verifiers.EnsureAggregateFieldIsAttributeOrCalculation,
Ash.Resource.Verifiers.ValidateRelationshipAttributes, Ash.Resource.Verifiers.ValidateRelationshipAttributes,
Ash.Resource.Verifiers.NoReservedFieldNames, Ash.Resource.Verifiers.NoReservedFieldNames,

View file

@ -5,7 +5,7 @@ defmodule Ash.Resource.Igniter do
def list_resources(igniter) do def list_resources(igniter) do
Igniter.Code.Module.find_all_matching_modules(igniter, fn _mod, zipper -> Igniter.Code.Module.find_all_matching_modules(igniter, fn _mod, zipper ->
zipper zipper
|> Igniter.Code.Module.move_to_use(resource_mods()) |> Igniter.Code.Module.move_to_use(resource_mods(igniter))
|> case do |> case do
{:ok, _} -> {:ok, _} ->
true true
@ -24,7 +24,7 @@ defmodule Ash.Resource.Igniter do
{:ok, {igniter, _source, zipper}} -> {:ok, {igniter, _source, zipper}} ->
with {:ok, zipper} <- Igniter.Code.Common.move_to_do_block(zipper), with {:ok, zipper} <- Igniter.Code.Common.move_to_do_block(zipper),
{:ok, zipper} <- {:ok, zipper} <-
Igniter.Code.Module.move_to_use(zipper, resource_mods()), Igniter.Code.Module.move_to_use(zipper, resource_mods(igniter)),
{:ok, zipper} <- {:ok, zipper} <-
Igniter.Code.Function.move_to_nth_argument(zipper, 1), Igniter.Code.Function.move_to_nth_argument(zipper, 1),
{:ok, zipper} <- Igniter.Code.Keyword.get_key(zipper, :domain), {:ok, zipper} <- Igniter.Code.Keyword.get_key(zipper, :domain),
@ -41,8 +41,8 @@ defmodule Ash.Resource.Igniter do
end end
end end
def resource_mods do def resource_mods(igniter) do
app_name = Igniter.Project.Application.app_name() app_name = Igniter.Project.Application.app_name(igniter)
[Ash.Resource | List.wrap(Application.get_env(app_name, :base_resources))] [Ash.Resource | List.wrap(Application.get_env(app_name, :base_resources))]
end end

View file

@ -779,6 +779,11 @@ defmodule Ash.Resource.Info do
Extension.get_persisted(resource, :always_selected_attribute_names) Extension.get_persisted(resource, :always_selected_attribute_names)
end end
@spec selected_by_default_attribute_names(Spark.Dsl.t() | Ash.Resource.t()) :: MapSet.t()
def selected_by_default_attribute_names(resource) do
Extension.get_persisted(resource, :selected_by_default_attribute_names)
end
@doc "Returns all attributes, aggregates, calculations and relationships of a resource" @doc "Returns all attributes, aggregates, calculations and relationships of a resource"
@spec fields( @spec fields(
Spark.Dsl.t() | Ash.Resource.t(), Spark.Dsl.t() | Ash.Resource.t(),

View file

@ -66,12 +66,18 @@ defmodule Ash.Resource.Transformers.AttributesByName do
|> Enum.map(& &1.name) |> Enum.map(& &1.name)
|> MapSet.new() |> MapSet.new()
selected_by_default_attribute_names =
Enum.filter(attributes, & &1.select_by_default?)
|> Enum.map(& &1.name)
|> MapSet.new()
{:ok, {:ok,
persist( persist(
dsl_state, dsl_state,
%{ %{
attributes_by_name: attributes_by_name, attributes_by_name: attributes_by_name,
attribute_names: attribute_names, attribute_names: attribute_names,
selected_by_default_attribute_names: selected_by_default_attribute_names,
create_attributes_with_static_defaults: create_attributes_with_static_defaults, create_attributes_with_static_defaults: create_attributes_with_static_defaults,
create_attributes_with_non_matching_lazy_defaults: create_attributes_with_non_matching_lazy_defaults:
create_attributes_with_non_matching_lazy_defaults, create_attributes_with_non_matching_lazy_defaults,

View file

@ -0,0 +1,30 @@
defmodule Ash.Resource.Verifiers.VerifySelectedByDefault do
@moduledoc """
Raises an error when a required primary key is missing
"""
use Spark.Dsl.Verifier
alias Spark.Dsl.Verifier
def verify(dsl) do
resource = Verifier.get_persisted(dsl, :module)
data_layer = Ash.DataLayer.data_layer(resource)
if is_nil(data_layer) || data_layer.can?(resource, :select) do
:ok
else
Enum.each(Ash.Resource.Info.attributes(dsl), fn attribute ->
if !attribute.select_by_default? do
raise Spark.Error.DslError,
module: resource,
path: [:attributes, attribute.name],
message: """
Attribute #{inspect(resource)}.#{attribute.name} was marked with `select_by_default: false`,
but the data layer #{inspect(data_layer)} does not support selecting attributes.
This means that all attributes will always be selected.
"""
end
end)
end
end
end

View file

@ -18,7 +18,7 @@ defmodule Mix.Tasks.Ash.Gen.BaseResource do
glob = Path.join([base_resource_file, "..", "**", "*.ex"]) glob = Path.join([base_resource_file, "..", "**", "*.ex"])
app_name = Igniter.Project.Application.app_name() app_name = Igniter.Project.Application.app_name(igniter)
# need `Igniter.glob(igniter, path, filter)` to get all existing or new files that match a path & condition # need `Igniter.glob(igniter, path, filter)` to get all existing or new files that match a path & condition
# for each file that defines a resource that uses `Ash.Resource`, that is "further down" from this file, # for each file that defines a resource that uses `Ash.Resource`, that is "further down" from this file,

View file

@ -28,7 +28,7 @@ defmodule Mix.Tasks.Ash.Gen.Domain do
domain_file = Igniter.Code.Module.proper_location(domain) domain_file = Igniter.Code.Module.proper_location(domain)
app_name = Igniter.Project.Application.app_name() app_name = Igniter.Project.Application.app_name(igniter)
if "--ignore-if-exists" in argv && Igniter.exists?(igniter, domain_file) do if "--ignore-if-exists" in argv && Igniter.exists?(igniter, domain_file) do
igniter igniter

View file

@ -72,7 +72,7 @@ defmodule Mix.Tasks.Ash.Gen.Resource do
def igniter(igniter, argv) do def igniter(igniter, argv) do
{%{resource: resource}, argv} = positional_args!(argv) {%{resource: resource}, argv} = positional_args!(argv)
resource = Igniter.Code.Module.parse(resource) resource = Igniter.Code.Module.parse(resource)
app_name = Igniter.Project.Application.app_name() app_name = Igniter.Project.Application.app_name(igniter)
options = options!(argv) options = options!(argv)

View file

@ -81,10 +81,12 @@ defmodule Mix.Tasks.Ash.Install do
end end
defp generate_example(igniter, argv) do defp generate_example(igniter, argv) do
domain_module_name = Igniter.Code.Module.module_name("Support") domain_module_name = Igniter.Code.Module.module_name(igniter, "Support")
ticket_resource = Igniter.Code.Module.module_name("Support.Ticket") ticket_resource = Igniter.Code.Module.module_name(igniter, "Support.Ticket")
representative_resource = Igniter.Code.Module.module_name("Support.Representative") representative_resource = Igniter.Code.Module.module_name(igniter, "Support.Representative")
ticket_status_module_name = Igniter.Code.Module.module_name("Support.Ticket.Types.Status")
ticket_status_module_name =
Igniter.Code.Module.module_name(igniter, "Support.Ticket.Types.Status")
igniter igniter
|> Igniter.compose_task("ash.gen.domain", [inspect(domain_module_name)]) |> Igniter.compose_task("ash.gen.domain", [inspect(domain_module_name)])

View file

@ -360,7 +360,7 @@ defmodule Ash.MixProject do
{:simple_sat, "~> 0.1 and >= 0.1.1", optional: true}, {:simple_sat, "~> 0.1 and >= 0.1.1", optional: true},
# Code Generators # Code Generators
{:igniter, "~> 0.3 and >= 0.3.11"}, {:igniter, "~> 0.3 and >= 0.3.33"},
# IO Utilities # IO Utilities
{:owl, "~> 0.11"}, {:owl, "~> 0.11"},

View file

@ -18,7 +18,7 @@
"git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"},
"git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"}, "git_ops": {:hex, :git_ops, "2.6.1", "cc7799a68c26cf814d6d1a5121415b4f5bf813de200908f930b27a2f1fe9dad5", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "ce62d07e41fe993ec22c35d5edb11cf333a21ddaead6f5d9868fcb607d42039e"},
"glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"}, "glob_ex": {:hex, :glob_ex, "0.1.8", "f7ef872877ca2ae7a792ab1f9ff73d9c16bf46ecb028603a8a3c5283016adc07", [:mix], [], "hexpm", "9e39d01729419a60a937c9260a43981440c43aa4cadd1fa6672fecd58241c464"},
"igniter": {:hex, :igniter, "0.3.25", "cce36fd49b499d215d0605ee3bfeb8fabe2f86b70b2df24ef3a50797409bceee", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "ffccb3c03cfdc8694be27a4c1d5615799ac140c27f32c74d1817171c4d411a62"}, "igniter": {:hex, :igniter, "0.3.34", "fee93422583884b4a6985de45797097d36f36283d4e61c6154d0e8ec02e19e2b", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:rewrite, "~> 0.9", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "b8e0bd0cdc8354b44f292a3eab4eaac155e4a9c9784b066ec29a2587595bcae8"},
"iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"},
@ -39,7 +39,7 @@
"simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"}, "simple_sat": {:hex, :simple_sat, "0.1.3", "f650fc3c184a5fe741868b5ac56dc77fdbb428468f6dbf1978e14d0334497578", [:mix], [], "hexpm", "a54305066a356b7194dc81db2a89232bacdc0b3edaef68ed9aba28dcbc34887b"},
"sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"},
"sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"},
"spark": {:hex, :spark, "2.2.24", "0cbd0e224af530f8f12f0e83ac5743b21802fb821d85b58d32a4da7e2268522b", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "f05fd64ef74b3f3fe7817743962956dcc8a8e84bb9dc796ac7bf7fdcf4db5b6d"}, "spark": {:hex, :spark, "2.2.26", "1701f388a9cfb2e27cd037b6f4b72a999e49bdb2d2f946bdbde8a991ce42c499", [:mix], [{:igniter, ">= 0.2.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "7a57860e4d15ab2e395dffeac617f3ee64d371b47f7b3d718a8d535d75cc7556"},
"spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"},
"splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"}, "splode": {:hex, :splode, "0.2.4", "71046334c39605095ca4bed5d008372e56454060997da14f9868534c17b84b53", [:mix], [], "hexpm", "ca3b95f0d8d4b482b5357954fec857abd0fa3ea509d623334c1328e7382044c2"},
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},

View file

@ -613,7 +613,7 @@ defmodule Ash.Test.Actions.ReadTest do
|> strip_metadata() |> strip_metadata()
end end
test "a sort will sor rows accordingly when descending", %{ test "a sort will sort rows accordingly when descending", %{
post1: post1, post1: post1,
post2: post2 post2: post2
} do } do