improvement: add more resource updating logic

This commit is contained in:
Zach Daniel 2024-09-15 16:50:55 -04:00
parent bf741990a2
commit f6222a3153
4 changed files with 271 additions and 64 deletions

View file

@ -159,12 +159,29 @@ defmodule Ash.Resource.Igniter do
@doc "Returns true if the given resource defines an attribute with the provided name" @doc "Returns true if the given resource defines an attribute with the provided name"
@spec defines_attribute(Igniter.t(), Ash.Resource.t(), atom()) :: {Igniter.t(), true | false} @spec defines_attribute(Igniter.t(), Ash.Resource.t(), atom()) :: {Igniter.t(), true | false}
def defines_attribute(igniter, resource, name) do def defines_attribute(igniter, resource, name) do
{igniter, defines?} =
if name in [:inserted_at, :updated_at] do
has_timestamps_call(igniter, resource)
else
{igniter, false}
end
if defines? do
{igniter, true}
else
Spark.Igniter.find(igniter, resource, fn _, zipper -> Spark.Igniter.find(igniter, resource, fn _, zipper ->
with {:ok, zipper} <- enter_section(zipper, :attributes), with {:ok, zipper} <- enter_section(zipper, :attributes),
{:ok, _zipper} <- {:ok, _zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope( move_to_one_of_function_call_in_current_scope(
zipper, zipper,
[
:attribute, :attribute,
:uuid_primary_key,
:integer_primary_key,
:uuid_v7_primary_key,
:create_timestamp,
:update_timestamp
],
[2, 3], [2, 3],
&Igniter.Code.Function.argument_equals?(&1, 0, name) &Igniter.Code.Function.argument_equals?(&1, 0, name)
) do ) do
@ -182,10 +199,21 @@ defmodule Ash.Resource.Igniter do
{igniter, false} {igniter, false}
end end
end end
end
@doc "Returns true if the given resource defines an action with the provided name" @doc "Returns true if the given resource defines an action with the provided name"
@spec defines_action(Igniter.t(), Ash.Resource.t(), atom()) :: {Igniter.t(), true | false} @spec defines_action(Igniter.t(), Ash.Resource.t(), atom()) :: {Igniter.t(), true | false}
def defines_action(igniter, resource, name) do def defines_action(igniter, resource, name) do
{igniter, defines?} =
if name in [:create, :read, :update, :destroy] do
has_default_action(igniter, resource, name)
else
{igniter, false}
end
if defines? do
{igniter, true}
else
Spark.Igniter.find(igniter, resource, fn _, zipper -> Spark.Igniter.find(igniter, resource, fn _, zipper ->
with {:ok, zipper} <- enter_section(zipper, :actions), with {:ok, zipper} <- enter_section(zipper, :actions),
{:ok, _zipper} <- {:ok, _zipper} <-
@ -215,6 +243,22 @@ defmodule Ash.Resource.Igniter do
{igniter, false} {igniter, false}
end end
end end
end
@spec ensure_primary_action(Igniter.t(), Ash.Resource.t(), :create | :read | :update | :destroy) ::
Igniter.t()
def ensure_primary_action(igniter, resource, type) do
case has_default_action(igniter, resource, type) do
{igniter, true} ->
igniter
{igniter, false} ->
case has_action_with_primary(igniter, resource, type) do
{igniter, true} -> igniter
{igniter, false} -> add_default_action(igniter, resource, type)
end
end
end
@doc "Returns true if the given resource defines a relationship with the provided name" @doc "Returns true if the given resource defines a relationship with the provided name"
@spec defines_relationship(Igniter.t(), Ash.Resource.t(), atom()) :: {Igniter.t(), true | false} @spec defines_relationship(Igniter.t(), Ash.Resource.t(), atom()) :: {Igniter.t(), true | false}
@ -301,6 +345,30 @@ defmodule Ash.Resource.Igniter do
add_block(igniter, resource, :resource, resource_configuration) add_block(igniter, resource, :resource, resource_configuration)
end end
@doc "Ensures that created_at and updated_at timestamps exist on the resource"
def ensure_timestamps(igniter, resource) do
{igniter, defines_inserted_at?} = defines_attribute(igniter, resource, :inserted_at)
{igniter, defines_created_at?} = defines_attribute(igniter, resource, :created_at)
defines_create_timestamp? = defines_inserted_at? || defines_created_at?
{igniter, defines_updated_at?} = defines_attribute(igniter, resource, :updated_at)
igniter
|> then(fn igniter ->
if defines_create_timestamp? do
igniter
else
add_create_timestamp_call(igniter, resource)
end
end)
|> then(fn igniter ->
if defines_updated_at? do
igniter
else
add_update_timestamp_call(igniter, resource)
end
end)
end
defp enter_section(zipper, name) do defp enter_section(zipper, name) do
with {:ok, zipper} <- with {:ok, zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope( Igniter.Code.Function.move_to_function_call_in_current_scope(
@ -332,4 +400,149 @@ defmodule Ash.Resource.Igniter do
:error -> move_to_one_of_function_call_in_current_scope(zipper, rest, arities, pred) :error -> move_to_one_of_function_call_in_current_scope(zipper, rest, arities, pred)
end end
end end
defp has_default_action(igniter, resource, type) do
Spark.Igniter.find(igniter, resource, fn _, zipper ->
with {:ok, zipper} <- enter_section(zipper, :actions),
{:ok, _zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:defaults,
1,
fn zipper ->
with {:ok, zipper} <- Igniter.Code.Function.move_to_nth_argument(zipper, 0),
{:ok, _zipper} <-
Igniter.Code.List.move_to_list_item(zipper, fn zipper ->
if Igniter.Code.Tuple.tuple?(zipper) do
with {:ok, zipper} <- Igniter.Code.Tuple.tuple_elem(zipper, 0) do
Igniter.Code.Common.nodes_equal?(zipper, type)
else
_ -> false
end
else
Igniter.Code.Common.nodes_equal?(zipper, type)
end
end) do
true
else
_ -> false
end
end
) do
{:ok, true}
else
_ -> :error
end
end)
|> case do
{:ok, igniter, _module, _value} ->
{igniter, true}
{:error, igniter} ->
{igniter, false}
end
end
@spec has_action_with_primary(Igniter.t(), Ash.Resource.t(), atom()) ::
{Igniter.t(), true | false}
def has_action_with_primary(igniter, resource, type) do
Spark.Igniter.find(igniter, resource, fn _, zipper ->
with {:ok, zipper} <- enter_section(zipper, :actions),
{:ok, _zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope(zipper, type, [1, 2]) do
case Igniter.Code.Common.move_to_do_block(zipper) do
{:ok, zipper} ->
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:primary?,
1,
&Igniter.Code.Function.argument_equals?(&1, 0, true)
)
:error ->
with {:ok, zipper} <- Igniter.Code.Function.move_to_nth_argument(zipper, 1),
{:ok, zipper} <- Igniter.Code.Keyword.get_key(zipper, :primary?),
true <- Igniter.Code.Common.nodes_equal?(zipper, true) do
{:ok, true}
end
end
end
end)
|> case do
{:ok, igniter, _module, _value} ->
{igniter, true}
{:error, igniter} ->
{igniter, false}
end
end
defp add_default_action(igniter, resource, type) do
Spark.Igniter.find(igniter, resource, fn _, zipper ->
with {:ok, zipper} <- enter_section(zipper, :actions),
{:ok, _zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:defaults,
1
) do
{:ok, true}
else
_ -> :error
end
end)
|> case do
{:ok, igniter, module, _value} ->
Igniter.Project.Module.find_and_update_module!(igniter, module, fn zipper ->
with {:ok, zipper} <- enter_section(zipper, :actions),
{:ok, _zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:defaults,
1
),
{:ok, zipper} <- Igniter.Code.List.prepend_new_to_list(zipper, type) do
{:ok, zipper}
else
_ ->
{:error,
"Failed to add a default action of type #{inspect(type)} in #{inspect(module)}"}
end
end)
{:error, igniter} ->
igniter
end
end
defp add_create_timestamp_call(igniter, resource) do
add_block(igniter, resource, :attributes, "create_timestamp :created_at")
end
defp add_update_timestamp_call(igniter, resource) do
add_block(igniter, resource, :attributes, "update_timestamp :updated_at")
end
defp has_timestamps_call(igniter, resource) do
Spark.Igniter.find(igniter, resource, fn _, zipper ->
with {:ok, zipper} <- enter_section(zipper, :attributes),
{:ok, _zipper} <-
Igniter.Code.Function.move_to_function_call_in_current_scope(
zipper,
:timestamps,
1
) do
{:ok, true}
else
_ -> :error
end
end)
|> case do
{:ok, igniter, _module, _value} ->
{igniter, true}
{:error, igniter} ->
{igniter, false}
end
end
end end

View file

@ -26,21 +26,17 @@ defmodule Mix.Tasks.Ash.Gen.Domain do
{%{domain: domain}, argv} = positional_args!(argv) {%{domain: domain}, argv} = positional_args!(argv)
domain = Igniter.Code.Module.parse(domain) domain = Igniter.Code.Module.parse(domain)
domain_file = Igniter.Project.Module.proper_location(igniter, domain)
app_name = Igniter.Project.Application.app_name(igniter) 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.Project.Module.module_exists?(igniter, domain) do
igniter igniter
else else
igniter igniter
|> Igniter.create_new_file(domain_file, """ |> Igniter.Project.Module.create_module(domain, """
defmodule #{inspect(domain)} do
use Ash.Domain use Ash.Domain
resources do resources do
end end
end
""") """)
|> Igniter.Project.Config.configure( |> Igniter.Project.Config.configure(
"config.exs", "config.exs",

View file

@ -210,10 +210,9 @@ defmodule Mix.Tasks.Ash.Gen.Resource do
domain, domain,
resource resource
) )
|> Igniter.create_new_file( |> Igniter.Project.Module.create_module(
Igniter.Project.Module.proper_location(igniter, resource), resource,
""" """
defmodule #{inspect(resource)} do
use #{base}, use #{base},
otp_app: #{inspect(app_name)}, otp_app: #{inspect(app_name)},
domain: #{inspect(domain)} domain: #{inspect(domain)}
@ -223,7 +222,6 @@ defmodule Mix.Tasks.Ash.Gen.Resource do
#{attributes} #{attributes}
#{relationships} #{relationships}
end
""" """
) )
|> extend(resource, options[:extend], argv) |> extend(resource, options[:extend], argv)

View file

@ -19,7 +19,7 @@
"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"},
"ham": {:hex, :ham, "0.3.0", "7cd031b4a55fba219c11553e7b13ba73bd86eab4034518445eff1e038cb9a44d", [:mix], [], "hexpm", "7d6c6b73d7a6a83233876cc1b06a4d9b5de05562b228effda4532f9a49852bf6"}, "ham": {:hex, :ham, "0.3.0", "7cd031b4a55fba219c11553e7b13ba73bd86eab4034518445eff1e038cb9a44d", [:mix], [], "hexpm", "7d6c6b73d7a6a83233876cc1b06a4d9b5de05562b228effda4532f9a49852bf6"},
"igniter": {:hex, :igniter, "0.3.36", "7dffb41e8c25dac3de8a0947c4973dc3db3cd25f394fd87ac334fe18a725f291", [: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", "5a493222cbf4e3cf0106cd090c93a1f61fa4df958b7d00e03a836e8a67a4bab2"}, "igniter": {:hex, :igniter, "0.3.37", "ad4ec1c0d73dedf5514ac52c5e93d5daa64bf4037a17088a9a7f4d44133a5846", [: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", "727b74a67df63cbe4c21a99707e02c50f4b7740c93cd3431fa9184a863eb064c"},
"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"},
@ -40,7 +40,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.27", "213447d22c5ccdfa9d77bc4c58ba2f5ace99dadd9b974e52a374fa58320ab9e4", [: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", "59ca9129fe707dcfec768591d1d2582c6cc8062fb6329df4837cba8a88392b0b"}, "spark": {:hex, :spark, "2.2.28", "fa97b2ec210fe612bdea2fb01713852af3d0cb2e2dbd959dcfcf2bf97a119073", [:mix], [{:igniter, ">= 0.3.36 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", "bfd19844e0c663aac3619d866d2b1c3422637be8ced500428d5cc5e36f6f9792"},
"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"},