diff --git a/lib/data_layer.ex b/lib/data_layer.ex index eea56043..652ed117 100644 --- a/lib/data_layer.ex +++ b/lib/data_layer.ex @@ -2397,9 +2397,12 @@ defmodule AshPostgres.DataLayer do # Include fields with update_defaults (e.g. update_timestamp) # even if they aren't in the changeset attributes or upsert_fields. # These fields should always be refreshed when an upsert modifies fields. - # Can be disabled via context: %{data_layer: %{touch_update_defaults?: false}} + # Can be disabled via touch_update_defaults?: false in the changeset + # context (either in [:private] or [:data_layer]) or via options map touch_update_defaults? = - Enum.at(changesets, 0).context[:data_layer][:touch_update_defaults?] != false + Map.get(options, :touch_update_defaults?, true) && + Enum.at(changesets, 0).context[:private][:touch_update_defaults?] != false && + Enum.at(changesets, 0).context[:data_layer][:touch_update_defaults?] != false if touch_update_defaults? do update_default_fields = @@ -3228,12 +3231,21 @@ defmodule AshPostgres.DataLayer do else keys = keys || Ash.Resource.Info.primary_key(keys) + touch_update_defaults? = + changeset.context[:private][:touch_update_defaults?] != false + update_defaults = update_defaults(resource) explicitly_changing_attributes = changeset.attributes |> Map.keys() - |> Enum.concat(Keyword.keys(update_defaults)) + |> then(fn attrs -> + if touch_update_defaults? do + Enum.concat(attrs, Keyword.keys(update_defaults)) + else + attrs + end + end) |> Kernel.--(Map.get(changeset, :defaults, [])) |> Kernel.--(keys) @@ -3248,6 +3260,7 @@ defmodule AshPostgres.DataLayer do upsert_keys: keys, action_select: changeset.action_select, upsert_fields: upsert_fields, + touch_update_defaults?: touch_update_defaults?, return_records?: true }) do {:ok, []} -> diff --git a/mix.exs b/mix.exs index be86f42c..4bf15445 100644 --- a/mix.exs +++ b/mix.exs @@ -185,7 +185,7 @@ defmodule AshPostgres.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, ash_version("~> 3.15")}, + {:ash, ash_version("~> 3.19")}, {:spark, "~> 2.3 and >= 2.3.4"}, {:ash_sql, ash_sql_version("~> 0.4 and >= 0.4.3")}, {:igniter, "~> 0.6 and >= 0.6.29", optional: true}, diff --git a/mix.lock b/mix.lock index e294b23c..f83bd51c 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "ash": {:hex, :ash, "3.18.0", "9548e0db48e3a51132e0ebe8674fec6a7fa2752fc65bae3f546bca4a98d22ec9", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.14 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "22e7cde0856ae4f4024c2d74a5b9d66209fb3959ffe5a018d1c55e86bff90d36"}, + "ash": {:hex, :ash, "3.19.1", "b5e933547d948e44d27adaed5737195488292fc2066e7fe60dd3ac83a0c4e54f", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.3.14 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "697ac3e4fc6080cb03b1e4ee9088cb8a313a5299686ba1aa91efc86ec4028b6e"}, "ash_sql": {:hex, :ash_sql, "0.4.5", "30030675ce995570fcedccd3c0671d85beff03cc0c480e7da5002842dccf0277", [:mix], [{:ash, "~> 3.7", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, ">= 3.13.4 and < 4.0.0-0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "131e06e13ebcf06fc8d050267a5b29f6cc8ef6a781712e61a456f17726a64ea5"}, "benchee": {:hex, :benchee, "1.5.0", "4d812c31d54b0ec0167e91278e7de3f596324a78a096fd3d0bea68bb0c513b10", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.1", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "5b075393aea81b8ae74eadd1c28b1d87e8a63696c649d8293db7c4df3eb67535"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, diff --git a/test/bulk_create_test.exs b/test/bulk_create_test.exs index 1d19a48d..883b2af8 100644 --- a/test/bulk_create_test.exs +++ b/test/bulk_create_test.exs @@ -224,7 +224,7 @@ defmodule AshPostgres.BulkCreateTest do upsert?: true, upsert_identity: :uniq_one_and_two, upsert_fields: [:price], - context: %{data_layer: %{touch_update_defaults?: false}}, + touch_update_defaults?: false, return_stream?: true, return_errors?: true, return_records?: true diff --git a/test/upsert_test.exs b/test/upsert_test.exs index 4337e2d7..b639090f 100644 --- a/test/upsert_test.exs +++ b/test/upsert_test.exs @@ -93,4 +93,56 @@ defmodule AshPostgres.Test.UpsertTest do assert updated_post.id == id assert Decimal.equal?(updated_post.decimal, Decimal.new(5)) end + + test "upsert with touch_update_defaults? false does not update updated_at" do + id = Ash.UUID.generate() + past = DateTime.add(DateTime.utc_now(), -60, :second) + + Post + |> Ash.Changeset.for_create(:create, %{ + id: id, + title: "title", + updated_at: past + }) + |> Ash.create!() + + assert [%{updated_at: backdated}] = Ash.read!(Post) + assert DateTime.compare(backdated, past) == :eq + + upserted = + Post + |> Ash.Changeset.for_create(:create, %{ + id: id, + title: "title2" + }) + |> Ash.create!(upsert?: true, touch_update_defaults?: false) + + assert DateTime.compare(upserted.updated_at, past) == :eq + end + + test "upsert with empty upsert_fields does not update updated_at" do + id = Ash.UUID.generate() + past = DateTime.add(DateTime.utc_now(), -60, :second) + + Post + |> Ash.Changeset.for_create(:create, %{ + id: id, + title: "title", + updated_at: past + }) + |> Ash.create!() + + assert [%{updated_at: backdated}] = Ash.read!(Post) + assert DateTime.compare(backdated, past) == :eq + + upserted = + Post + |> Ash.Changeset.for_create(:create, %{ + id: id, + title: "title2" + }) + |> Ash.create!(upsert?: true, upsert_fields: []) + + assert DateTime.compare(upserted.updated_at, past) == :eq + end end