This commit is contained in:
Zach Daniel 2023-09-25 10:22:54 -04:00
parent 71079cf664
commit 34e7a206ab
13 changed files with 23 additions and 200 deletions

View file

@ -1,7 +1,5 @@
import Config
config :ash, :use_all_identities_in_manage_relationship?, false
if Mix.env() == :dev do
config :git_ops,
mix_project: AshSqlite.MixProject,

View file

@ -14,7 +14,7 @@ defmodule MyApp.Reaction do
attributes do
attribute(:resource_id, :uuid)
end
...
end
```
@ -78,5 +78,5 @@ end
When a migration is marked as `polymorphic? true`, the migration generator will look at
all resources that are related to it, that set the `%{data_layer: %{table: "table"}}` context.
For each of those, a migration is generated/managed automatically. This means that adding reactions
to a new resource is as easy as adding the relationship and table context, and then running
to a new resource is as easy as adding the relationship and table context, and then running
`mix ash_sqlite.generate_migrations`.

View file

@ -329,7 +329,7 @@ defmodule AshSqlite.DataLayer do
def can?(_, :bulk_create), do: true
def can?(_, {:lock, _}), do: false
def can?(_, :transact), do: true
def can?(_, :transact), do: false
def can?(_, :composite_primary_key), do: true
def can?(_, {:atomic, :update}), do: true
def can?(_, :upsert), do: true
@ -388,11 +388,6 @@ defmodule AshSqlite.DataLayer do
def can?(_, {:sort, _}), do: true
def can?(_, _), do: false
@impl true
def in_transaction?(resource) do
AshSqlite.DataLayer.Info.repo(resource).in_transaction?()
end
@impl true
def limit(query, nil, _), do: {:ok, query}
@ -1574,29 +1569,6 @@ defmodule AshSqlite.DataLayer do
%{query | __ash_bindings__: new_ash_bindings}
end
@impl true
def transaction(resource, func, timeout \\ nil, reason \\ %{type: :custom, metadata: %{}}) do
repo =
case reason[:data_layer_context] do
%{repo: repo} when not is_nil(repo) ->
repo
_ ->
AshSqlite.DataLayer.Info.repo(resource)
end
func = fn ->
repo.on_transaction_begin(reason)
func.()
end
if timeout do
repo.transaction(func, timeout: timeout)
else
repo.transaction(func)
end
end
@impl true
def rollback(resource, term) do
AshSqlite.DataLayer.Info.repo(resource).rollback(term)

View file

@ -310,7 +310,7 @@ defmodule AshSqlite.MigrationGenerator do
operations
|> organize_operations
|> build_up_and_down()
|> write_migration!(repo, opts, false)
|> write_migration!(repo, opts)
create_new_snapshot(snapshots, repo_name(repo), opts)
end
@ -676,7 +676,7 @@ defmodule AshSqlite.MigrationGenerator do
repo |> Module.split() |> List.last() |> Macro.underscore()
end
defp write_migration!({up, down}, repo, opts, run_without_transaction?) do
defp write_migration!({up, down}, repo, opts) do
migration_path = migration_path(opts, repo)
{migration_name, last_part} =
@ -714,14 +714,6 @@ defmodule AshSqlite.MigrationGenerator do
module_name =
Module.concat([repo, Migrations, Macro.camelize(last_part)])
module_attributes =
if run_without_transaction? do
"""
@disable_ddl_transaction true
@disable_migration_lock true
"""
end
contents = """
defmodule #{inspect(module_name)} do
@moduledoc \"\"\"
@ -732,8 +724,6 @@ defmodule AshSqlite.MigrationGenerator do
use Ecto.Migration
#{module_attributes}
def up do
#{up}
end
@ -1509,7 +1499,6 @@ defmodule AshSqlite.MigrationGenerator do
}
end)
[
pkey_operations,
unique_indexes_to_remove,
@ -2288,7 +2277,6 @@ defmodule AshSqlite.MigrationGenerator do
|> Enum.map(&Map.put(&1, :base_filter, AshSqlite.DataLayer.Info.base_filter_sql(resource)))
end
defp default(%{name: name, default: default}, resource, _repo) when is_function(default) do
configured_default(resource, name) || "nil"
end

View file

@ -7,16 +7,6 @@ defmodule AshSqlite.Repo do
You can use `Ecto.Repo`'s `init/2` to configure your repo like normal, but
instead of returning `{:ok, config}`, use `super(config)` to pass the
configuration to the `AshSqlite.Repo` implementation.
## Transaction Hooks
You can define `on_transaction_begin/1`, which will be invoked whenever a transaction is started for Ash.
This will be invoked with a map containing a `type` key and metadata.
```elixir
%{type: :create, %{resource: YourApp.YourResource, action: :action}}
```
"""
@doc "Use this to inform the data layer about what extensions are installed"
@ -29,8 +19,6 @@ defmodule AshSqlite.Repo do
"""
@callback min_pg_version() :: integer()
@callback on_transaction_begin(reason :: Ash.DataLayer.transaction_reason()) :: term
@doc "The path where your migrations are stored"
@callback migrations_path() :: String.t() | nil
@doc "Allows overriding a given migration type for *all* fields, for example if you wanted to always use :timestamptz for :utc_datetime fields"
@ -62,8 +50,6 @@ defmodule AshSqlite.Repo do
{:ok, new_config}
end
def on_transaction_begin(_reason), do: :ok
def insert(struct_or_changeset, opts \\ []) do
struct_or_changeset
|> to_ecto()
@ -160,7 +146,6 @@ defmodule AshSqlite.Repo do
def to_ecto(other), do: other
defoverridable init: 2,
on_transaction_begin: 1,
installed_extensions: 0,
override_migration_type: 1,
min_pg_version: 0

View file

@ -228,4 +228,4 @@ defmodule AshSqlite.TestRepo.Migrations.MigrateResources1 do
drop table(:users)
end
end
end

View file

@ -1,14 +0,0 @@
defmodule AshSqliteTest do
use AshSqlite.RepoCase, async: false
test "transaction metadata is given to on_transaction_begin" do
AshSqlite.Test.Post
|> Ash.Changeset.new(%{title: "title"})
|> AshSqlite.Test.Api.create!()
assert_receive %{
type: :create,
metadata: %{action: :create, actor: nil, resource: AshSqlite.Test.Post}
}
end
end

View file

@ -1,15 +0,0 @@
defmodule AshSqlite.ConstraintTest do
@moduledoc false
use AshSqlite.RepoCase, async: false
alias AshSqlite.Test.{Api, Post}
require Ash.Query
test "constraint messages are properly raised" do
assert_raise Ash.Error.Invalid, ~r/yo, bad price/, fn ->
Post
|> Ash.Changeset.new(%{title: "title", price: -1})
|> Api.create!()
end
end
end

View file

@ -2,9 +2,19 @@ defmodule AshSqlite.EctoCompatibilityTest do
use AshSqlite.RepoCase, async: false
require Ash.Query
defmodule Schema do
use Ecto.Schema
schema "orgs" do
field(:name, :string)
end
end
test "call Ecto.Repo.insert! via Ash Repo" do
org =
%AshSqlite.Test.Organization{name: "The Org"}
%Schema{id: Ash.UUID.generate(), name: "The Org"}
# |> Ecto.Changeset.cast(%{name: "The Org"}, [:name])
# |> Ecto.Changeset.validate_required([:name])
|> AshSqlite.TestRepo.insert!()
assert org.name == "The Org"

View file

@ -123,7 +123,7 @@ defmodule AshSqlite.MigrationGeneratorTest do
# the migration adds the id, with its default
assert file_contents =~
~S[add :id, :uuid, null: false, default: fragment("uuid_generate_v4()"), primary_key: true]
~S[add :id, :uuid, null: false, primary_key: true]
# the migration adds the id, with its default
assert file_contents =~

View file

@ -17,9 +17,9 @@ defmodule AshSqlite.Test.Organization do
attribute(:name, :string)
end
relationships do
has_many(:users, AshSqlite.Test.User)
has_many(:posts, AshSqlite.Test.Post)
has_many(:managers, AshSqlite.Test.Manager)
end
# relationships do
# has_many(:users, AshSqlite.Test.User)
# has_many(:posts, AshSqlite.Test.Post)
# has_many(:managers, AshSqlite.Test.Manager)
# end
end

View file

@ -2,8 +2,4 @@ defmodule AshSqlite.TestRepo do
@moduledoc false
use AshSqlite.Repo,
otp_app: :ash_sqlite
def on_transaction_begin(data) do
send(self(), data)
end
end

View file

@ -1,97 +0,0 @@
defmodule AshSqlite.Test.TransactionTest do
use AshSqlite.RepoCase, async: false
alias AshSqlite.Test.{Api, Post}
require Ash.Query
test "after_transaction hooks are invoked on failure" do
assert_raise Ash.Error.Unknown, ~r/something bad happened/, fn ->
Post
|> Ash.Changeset.for_create(:create)
|> Ash.Changeset.after_action(fn _changeset, _result ->
raise "something bad happened"
end)
|> send_after_transaction_result()
|> Api.create()
end
assert_receive {:error,
%RuntimeError{
message: "something bad happened"
}}
end
test "after_transaction hooks are invoked on failure, even in a nested context" do
assert_raise Ash.Error.Unknown, ~r/something bad happened inside/, fn ->
Post
|> Ash.Changeset.for_create(:create)
|> Ash.Changeset.after_action(fn _changeset, result ->
Post
|> Ash.Changeset.for_create(:create)
|> Ash.Changeset.after_action(fn _changeset, _result ->
raise "something bad happened inside"
end)
|> send_after_transaction_result()
|> Api.create!()
{:ok, result}
end)
|> send_after_transaction_result()
|> Api.create()
end
assert_receive {:error,
%RuntimeError{
message: "something bad happened inside"
}}
assert_receive {:error, %Ash.Error.Unknown{}}
end
test "after_transaction hooks are invoked on success" do
Post
|> Ash.Changeset.for_create(:create)
|> send_after_transaction_result()
|> Api.create()
assert_receive {:ok, %Post{}}
end
test "after_transaction hooks are invoked on success and can reverse a failure" do
assert {:ok, %Post{}} =
Post
|> Ash.Changeset.for_create(:create)
|> Ash.Changeset.after_action(fn _changeset, result ->
Post
|> Ash.Changeset.for_create(:create)
|> Ash.Changeset.after_action(fn _changeset, _result ->
raise "something bad happened inside"
end)
|> send_after_transaction_result()
|> Api.create!()
{:ok, result}
end)
|> Ash.Changeset.after_transaction(fn _changeset, {:error, _} ->
Post
|> Ash.Changeset.for_create(:create)
|> Api.create()
end)
|> send_after_transaction_result()
|> Api.create()
assert_receive {:error,
%RuntimeError{
message: "something bad happened inside"
}}
assert_receive {:ok, %Post{}}
end
defp send_after_transaction_result(changeset) do
Ash.Changeset.after_transaction(changeset, fn _changeset, result ->
send(self(), result)
result
end)
end
end