diff --git a/lib/ash/resource/dsl.ex b/lib/ash/resource/dsl.ex index 44485f27..1a402687 100644 --- a/lib/ash/resource/dsl.ex +++ b/lib/ash/resource/dsl.ex @@ -1314,6 +1314,8 @@ defmodule Ash.Resource.Dsl do ] @transformers [ + Ash.Resource.Transformers.AttributesByName, + Ash.Resource.Transformers.ValidationsAndChangesForType, Ash.Resource.Transformers.RequireUniqueActionNames, Ash.Resource.Transformers.SetRelationshipSource, Ash.Resource.Transformers.BelongsToAttribute, diff --git a/lib/ash/resource/info.ex b/lib/ash/resource/info.ex index a955229e..48f6b9d0 100644 --- a/lib/ash/resource/info.ex +++ b/lib/ash/resource/info.ex @@ -215,9 +215,15 @@ defmodule Ash.Resource.Info do Ash.Resource.Validation.t() ] def validations(resource, type) do - resource - |> validations() - |> Enum.filter(&(type in &1.on)) + case Extension.get_persisted(resource, :validations_by_on) do + nil -> + resource + |> validations() + |> Enum.filter(&(type in &1.on)) + + persisted -> + Map.get(persisted, type) || [] + end end @doc "A list of all validations for the resource" @@ -233,9 +239,15 @@ defmodule Ash.Resource.Info do | Ash.Resource.Change.t() ) def changes(resource, type) do - resource - |> changes() - |> Enum.filter(&(type in &1.on)) + case Extension.get_persisted(resource, :changes_by_on) do + nil -> + resource + |> changes() + |> Enum.filter(&(type in &1.on)) + + persisted -> + Map.get(persisted, type) || [] + end end @doc "A list of all changes for the resource" @@ -572,15 +584,17 @@ defmodule Ash.Resource.Info do @spec attribute(Spark.Dsl.t() | Ash.Resource.t(), String.t() | atom) :: Ash.Resource.Attribute.t() | nil def attribute(resource, name) when is_binary(name) do - resource - |> attributes() - |> Enum.find(&(to_string(&1.name) == name)) + Extension.get_persisted(resource, :attributes_by_name)[name] || + resource + |> attributes() + |> Enum.find(&(to_string(&1.name) == name)) end def attribute(resource, name) do - resource - |> attributes() - |> Enum.find(&(&1.name == name)) + Extension.get_persisted(resource, :attributes_by_name)[name] || + resource + |> attributes() + |> Enum.find(&(&1.name == name)) end @doc "Returns all public attributes of a resource" diff --git a/lib/ash/resource/transformers/attributes_by_name.ex b/lib/ash/resource/transformers/attributes_by_name.ex new file mode 100644 index 00000000..453205d9 --- /dev/null +++ b/lib/ash/resource/transformers/attributes_by_name.ex @@ -0,0 +1,28 @@ +defmodule Ash.Resource.Transformers.AttributesByName do + @moduledoc """ + Persists attribute_names and attributes_by_name. + """ + use Spark.Dsl.Transformer + + alias Spark.Dsl.Transformer + + def transform(dsl_state) do + attributes = + Ash.Resource.Info.attributes(dsl_state) + + attributes_by_name = + attributes + |> Enum.reduce(%{}, fn %{name: name} = attr, acc -> + acc + |> Map.put(name, attr) + |> Map.put(to_string(name), attr) + end) + + attribute_names = Enum.map(attributes, & &1.name) + + {:ok, + dsl_state + |> Transformer.persist(:attributes_by_name, attributes_by_name) + |> Transformer.persist(:attribute_names, attribute_names)} + end +end diff --git a/lib/ash/resource/transformers/validations_and_changes_for_type.ex b/lib/ash/resource/transformers/validations_and_changes_for_type.ex new file mode 100644 index 00000000..7252bed9 --- /dev/null +++ b/lib/ash/resource/transformers/validations_and_changes_for_type.ex @@ -0,0 +1,37 @@ +defmodule Ash.Resource.Transformers.ValidationsAndChangesForType do + @moduledoc """ + Persists global changes/validations and what type they go on. + """ + use Spark.Dsl.Transformer + + alias Spark.Dsl.Transformer + + def transform(dsl_state) do + validations_by_on = + dsl_state + |> Ash.Resource.Info.validations() + |> Enum.reduce(%{}, fn validation, acc -> + validation.on + |> List.wrap() + |> Enum.reduce(acc, fn on, acc -> + Map.update(acc, on, [validation], &[validation | &1]) + end) + end) + + changes_by_on = + dsl_state + |> Ash.Resource.Info.changes() + |> Enum.reduce(%{}, fn change, acc -> + change.on + |> List.wrap() + |> Enum.reduce(acc, fn on, acc -> + Map.update(acc, on, [change], &[change | &1]) + end) + end) + + {:ok, + dsl_state + |> Transformer.persist(:validations_by_on, validations_by_on) + |> Transformer.persist(:changes_by_on, changes_by_on)} + end +end diff --git a/lib/ash/type/new_type.ex b/lib/ash/type/new_type.ex index e237ecba..3d6c7e0e 100644 --- a/lib/ash/type/new_type.ex +++ b/lib/ash/type/new_type.ex @@ -110,16 +110,10 @@ defmodule Ash.Type.NewType do @impl Ash.Type def cast_input(value, constraints) do - case unquote(subtype_of).cast_input( - value, - constraints - ) do - {:ok, value} -> - apply_constraints(value, constraints) - - other -> - other - end + unquote(subtype_of).cast_input( + value, + constraints + ) end if function_exported?(subtype_of, :cast_input_array, 2) do diff --git a/lib/ash/type/type.ex b/lib/ash/type/type.ex index 7f59cbc2..3a1d5234 100644 --- a/lib/ash/type/type.ex +++ b/lib/ash/type/type.ex @@ -258,11 +258,7 @@ defmodule Ash.Type do end def array_constraints(type) do - if ash_type?(type) do - type.array_constraints() - else - [] - end + type.array_constraints() end @spec get_type(atom | module | {:array, atom | module}) :: @@ -271,11 +267,12 @@ defmodule Ash.Type do {:array, get_type(value)} end + for {short_name, value} <- @short_names do + def get_type(unquote(short_name)), do: unquote(value) + end + def get_type(value) when is_atom(value) do - case Keyword.fetch(@short_names, value) do - {:ok, mod} -> mod - :error -> value - end + value end def get_type(value) do @@ -663,13 +660,9 @@ defmodule Ash.Type do def apply_constraints(type, term, constraints) do type = get_type(type) - if ash_type?(type) do - case type.apply_constraints(term, constraints) do - :ok -> {:ok, term} - other -> other - end - else - {:ok, term} + case type.apply_constraints(term, constraints) do + :ok -> {:ok, term} + other -> other end end @@ -720,11 +713,8 @@ defmodule Ash.Type do end def constraints(type) do - if ash_type?(type) do - type.constraints() - else - [] - end + type = get_type(type) + type.constraints() end def cast_in_query?(type, constraints \\ []) @@ -734,14 +724,12 @@ defmodule Ash.Type do end def cast_in_query?(type, constraints) do - if ash_type?(type) do - if function_exported?(type, :cast_in_query?, 0) do - type.cast_in_query?() - else - type.cast_in_query?(constraints) - end + type = get_type(type) + + if function_exported?(type, :cast_in_query?, 0) do + type.cast_in_query?() else - false + type.cast_in_query?(constraints) end end diff --git a/mix.exs b/mix.exs index 480d78e4..03f1e493 100644 --- a/mix.exs +++ b/mix.exs @@ -302,7 +302,7 @@ defmodule Ash.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:spark, "~> 1.1 and >= 1.1.20"}, + {:spark, "~> 1.1 and >= 1.1.47"}, {:ecto, "~> 3.7"}, {:ets, "~> 0.8"}, {:decimal, "~> 2.0"}, diff --git a/mix.lock b/mix.lock index 17e2c95f..315d9d1b 100644 --- a/mix.lock +++ b/mix.lock @@ -31,7 +31,7 @@ "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "sobelow": {:hex, :sobelow, "0.13.0", "218afe9075904793f5c64b8837cc356e493d88fddde126a463839351870b8d1e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cd6e9026b85fc35d7529da14f95e85a078d9dd1907a9097b3ba6ac7ebbe34a0d"}, "sourceror": {:hex, :sourceror, "0.14.0", "b6b8552d0240400d66b6f107c1bab7ac1726e998efc797f178b7b517e928e314", [:mix], [], "hexpm", "809c71270ad48092d40bbe251a133e49ae229433ce103f762a2373b7a10a8d8b"}, - "spark": {:hex, :spark, "1.1.44", "be9f2669b03ae43447bda77045598a4500988538a7d0ba576b8e306332822147", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e49bf5ca770cb0bb9cac7ed8da5eb7871156b3236c8c535f3f4caa93377059a3"}, + "spark": {:hex, :spark, "1.1.47", "2bc334e542f519709e57853a425aa9304223c61e3ecf130b1cc014c6a4451f9a", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "c1ae778a3779b5d3e5b7c885cc9826577816dca10bbf4c21638198a1262f3df1"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},