mirror of
https://github.com/ash-project/ash_sqlite.git
synced 2024-09-19 21:03:01 +12:00
WIP
This commit is contained in:
parent
71079cf664
commit
34e7a206ab
13 changed files with 23 additions and 200 deletions
|
@ -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,
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
15
lib/repo.ex
15
lib/repo.ex
|
@ -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
|
||||
|
|
|
@ -228,4 +228,4 @@ defmodule AshSqlite.TestRepo.Migrations.MigrateResources1 do
|
|||
|
||||
drop table(:users)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
||||
|
|
|
@ -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 =~
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
Loading…
Reference in a new issue