Ash [warning] one or more before_transaction hooks

I have a File resource with a create action that has a change that uploads the file to storage inside Ash.Changeset.before_transaction/2. This works fine, no errors or warnings.

I then have a Project resource which has a many_to_many relation to File through ProjectFile. In the Project create action I’m using manage_relationship to create files which uses the File creation action that uploads the file. This is when it triggers this warning:

One or more before_transaction hooks on MyApp.Files.File.create are being executed, but there is an ongoing transaction already happening.

The warning does suggest a few options like silencing the warning or creating another action that is safe to use in a surrounding transaction, and use that instead of this one.

Does Ash use nested transactions when inserting relationships? Or is it just using a nested transaction because I’ve added some hooks?

I’m guessing in my situation it’s best not to silence this warning because we don’t want to be hitting external apis from within transactions? My alternative approach is to upload the file in a before_transaction from the Project like so:

defmodule MyApp.Projects.Project do
  use Ash.Resource,
    domain: MyApp.Project,
    authorizers: [Ash.Policy.Authorizer],
    data_layer: AshPostgres.DataLayer

  postgres do
    table "projects"
    repo MyApp.Repo
  end

  actions do
    defaults [:read, :update]

    create :create_with_files do
      accept [:title]

      argument :files, {:array, :map} do
        allow_nil? false
      end

      change relate_actor(:user)

      change fn changeset, _ ->
        Ash.Changeset.before_transaction(changeset, fn changeset ->
          # upload files but don't create the File record as we're not in a transaction
          # put the uploaded files in changeset context to access later
          nil
        end)
      end

      change fn changeset, result ->
        Ash.Changeset.after_transaction(changeset, fn changeset, result ->
          # if result has an error delete the file from storage
          nil
        end)
      end

      change fn changeset, _ ->
        # during the main transaction get the files from the changeset context
        # manage the realtionship manually using Ash.Changeset.manage_relationship
        nil
      end
    end
  end

  relationships do
    belongs_to :user, MyApp.Accounts.User

    many_to_many :files, MyApp.Files.File do
      through MyApp.Projects.ProjectFile
      source_attribute_on_join_resource :project_id
      destination_attribute_on_join_resource :file_id
      public? true
    end
  end

  policies do
    policy action([:create_with_files]) do
      authorize_if always()
    end
  end
end

As an Ash newbie does this seem like the correct way to approach this with Ash?

Side note anyone want to make an ash_file_storage extension? :grinning:

This seems reasonable. And someone is working on ash_storage although it’s only in the conceptual stages :smile:

1 Like