-
-
Notifications
You must be signed in to change notification settings - Fork 11
AshEvents wrapping prevents update_default from working on update actions #59
Description
Code of Conduct
- I agree to follow this project's Code of Conduct
AI Policy
- I agree to follow this project's AI Policy, or I agree that AI was not used while creating this issue.
Versions
Environment
- Elixir version: 1.18.4
- Ash version: 3.5.34
- AshEvents version: 0.4.3
- AshPostgres version: 2.4.19
Operating system
MacOS
Current Behavior
Description
When AshEvents wraps update actions (via only_actions or by not being in ignore_actions), the update_default attribute option stops working. This means fields like updated_at with update_default: &DateTime.utc_now/0 are not automatically updated when records are modified.
Root Cause
The issue occurs because AshEvents uses UpdateActionWrapper which:
- Implements
Ash.Resource.ManualUpdate - Directly calls
data_layer.update()bypassing Ash's normal update process - This skips the application of
update_defaultvalues
Reference: /deps/ash_events/lib/events/update_action_wrapper.ex
defmodule AshEvents.UpdateActionWrapper do
use Ash.Resource.ManualUpdate
def update(changeset, module_opts, ctx) do
# ... event creation logic ...
data_layer = Ash.Resource.Info.data_layer(changeset.resource)
data_layer.update(changeset.resource, changeset) # <- Bypasses update_default
end
endSteps to Reproduce
- Create a resource with AshEvents extension and timestamps:
defmodule MyResource do
use Ash.Resource,
extensions: [AshEvents.Events]
events do
event_log MyApp.EventLog
only_actions [:update] # or don't use ignore_actions
end
actions do
defaults [:create, :read]
update :update do
accept [:name]
require_atomic? false
end
end
attributes do
uuid_primary_key :id
attribute :name, :string
timestamps() # Creates updated_at with update_default: &DateTime.utc_now/0
end
end- Create and update a record:
{:ok, record} = MyResource |> Ash.Changeset.for_create(:create, %{name: "Initial"}) |> Ash.create()
Process.sleep(100) # Ensure time difference
{:ok, updated} = record |> Ash.Changeset.for_update(:update, %{name: "Updated"}) |> Ash.update()
# Expected: updated.updated_at != record.updated_at
# Actual: updated.updated_at == record.updated_at (NOT UPDATED!)Impact
This affects:
- All resources using AshEvents with update actions in
only_actions - Timestamp tracking (
updated_atfields) - Any custom fields using
update_default - State machine transitions (when using AshStateMachine with AshEvents)
Workaround
Currently, the only workaround is to exclude affected actions from event logging:
events do
event_log MyApp.EventLog
ignore_actions [:update, :state_transition_action, ...] # Add actions that need update_default
endOr use a manual change to update the timestamp:
update :update do
change set_attribute(:updated_at, &DateTime.utc_now/0)
endExpected Behavior
AshEvents should preserve the normal Ash update behavior, including the application of update_default values, while still logging events.
Environment
- Elixir version: 1.18.4
- Ash version: 3.5.34
- AshEvents version: 0.4.3
- AshPostgres version: 2.4.19
Possible Solutions
-
Apply update_defaults before calling data_layer.update(): The wrapper could manually apply update_default values to the changeset before passing it to the data layer.
-
Use Ash's internal update process: Instead of directly calling
data_layer.update(), find a way to use Ash's internal update mechanism that applies defaults. -
Document the limitation: If this is intentional behavior, it should be clearly documented that update_default doesn't work with event-logged actions.
Additional Context
This issue was discovered while implementing a notification system where state transitions needed to be tracked with accurate timestamps. The updated_at field was not being updated for any state machine transitions or update actions that were included in AshEvents logging.
The issue affects both simple updates (changing a single field) and complex operations (state transitions, generic actions that internally call updates), regardless of implementation complexity.
Reproduction
No response
Expected Behavior
No response