mirror of
https://github.com/ash-project/ash.git
synced 2024-09-19 21:13:10 +12:00
feat(Ash.PlugHelpers): standardise conn interface for actors/tenants. (#432)
This commit is contained in:
parent
3c6758fa59
commit
2d6762ae61
4 changed files with 251 additions and 2 deletions
148
lib/ash/plug_helpers.ex
Normal file
148
lib/ash/plug_helpers.ex
Normal file
|
@ -0,0 +1,148 @@
|
|||
if Code.ensure_loaded?(Plug.Conn) do
|
||||
defmodule Ash.PlugHelpers do
|
||||
@moduledoc """
|
||||
Helpers for working with the Plug connection.
|
||||
"""
|
||||
|
||||
alias Plug.Conn
|
||||
require Logger
|
||||
|
||||
defmacrop emit_assign_warning(type) do
|
||||
quote do
|
||||
Logger.warning(fn ->
|
||||
{fun, arity} = __ENV__.function
|
||||
file = Path.relative_to_cwd(__ENV__.file)
|
||||
|
||||
"""
|
||||
Storing the #{unquote(type)} in conn assigns is deprecated.
|
||||
#{file}:#{__ENV__.line}: #{inspect(__ENV__.module)}.#{fun}/#{arity}
|
||||
"""
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the actor inside the Plug connection.
|
||||
|
||||
The actor is stored inside the [connection's private
|
||||
fields](https://hexdocs.pm/plug/Plug.Conn.html#module-private-fields).
|
||||
|
||||
## Example
|
||||
|
||||
iex> actor = build_actor(%{email: "marty@1985.retro"})
|
||||
...> conn = build_conn() |> set_actor(actor)
|
||||
%Plug.Conn{private: %{ash: %{actor: %{email: "marty@1985.retro"}}}} = conn
|
||||
|
||||
"""
|
||||
@spec set_actor(Conn.t(), Ash.Resource.record()) :: Conn.t()
|
||||
def set_actor(conn, actor) do
|
||||
ash_private =
|
||||
conn.private
|
||||
|> Map.get(:ash, %{})
|
||||
|> Map.put(:actor, actor)
|
||||
|
||||
conn
|
||||
|> Conn.put_private(:ash, ash_private)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Retrieves the actor from the Plug connection.
|
||||
|
||||
The actor is stored inside the [connection's private
|
||||
fields](https://hexdocs.pm/plug/Plug.Conn.html#module-private-fields).
|
||||
|
||||
## Deprecation warning
|
||||
|
||||
This function checks to see if the actor is already set in the `@actor`
|
||||
assign, and if so will emit a deprecation warning.
|
||||
|
||||
This is to allow apps using the previous method a chance to update.
|
||||
|
||||
Rather than setting the actor in the assigns, please use the `set_actor/2`
|
||||
method.
|
||||
|
||||
## Example
|
||||
|
||||
iex> actor = build_actor(%{email: "marty@1985.retro"})
|
||||
...> conn = build_conn() |> put_private(:ash, %{actor: actor})
|
||||
...> actor = get_actor(conn)
|
||||
%{email: "marty@1985.retro"} = actor
|
||||
|
||||
iex> actor = build_actor(%{email: "marty@1985.retro"})
|
||||
...> conn = build_conn() |> assign(:actor, actor)
|
||||
...> actor = get_actor(conn)
|
||||
%{email: "marty@1985.retro"} = actor
|
||||
"""
|
||||
@spec get_actor(Conn.t()) :: nil | Ash.Resource.record()
|
||||
def get_actor(%{assigns: %{actor: actor}}) when not is_nil(actor) do
|
||||
emit_assign_warning(:actor)
|
||||
|
||||
actor
|
||||
end
|
||||
|
||||
def get_actor(%{private: %{ash: %{actor: actor}}}), do: actor
|
||||
def get_actor(_), do: nil
|
||||
|
||||
@doc """
|
||||
Sets the tenant inside the Plug connection.
|
||||
|
||||
The tenant is stored inside the [connection's private
|
||||
fields](https://hexdocs.pm/plug/Plug.Conn.html#module-private-fields).
|
||||
|
||||
## Example
|
||||
|
||||
iex> tenant = build_tenant(%{name: "Deliver-yesterday"})
|
||||
...> conn = build_conn() |> set_tenant(tenant)
|
||||
%Plug.Conn{private: %{ash: %{tenant: %{name: "Deliver-yesterday"}}}} = conn
|
||||
"""
|
||||
@spec set_tenant(Conn.t(), Ash.Resource.record()) :: Conn.t()
|
||||
def set_tenant(conn, tenant) do
|
||||
ash_private =
|
||||
conn.private
|
||||
|> Map.get(:ash, %{})
|
||||
|> Map.put(:tenant, tenant)
|
||||
|
||||
conn
|
||||
|> Conn.put_private(:ash, ash_private)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Retrieves the tenant from the Plug connection.
|
||||
|
||||
The tenant is stored inside the [connection's private
|
||||
fields](https://hexdocs.pm/plug/Plug.Conn.html#module-private-fields).
|
||||
|
||||
## Deprecation warning
|
||||
|
||||
This function checks to see if the tenant is already set in the `@tenant`
|
||||
assign, and if so will emit a deprecation warning.
|
||||
|
||||
This is to allow apps using the previous method a chance to update.
|
||||
|
||||
Rather than setting the tenant in the assigns, please use the `set_tenant/2`
|
||||
method.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
iex> tenant = build_tenant(%{name: "Deliver-yesterday"})
|
||||
...> conn = build_conn() |> put_private(:ash, %{tenant: tenant})
|
||||
...> tenant = get_tenant(conn)
|
||||
%{name: "Deliver-yesterday"} = tenant
|
||||
|
||||
iex> tenant = build_tenant(%{name: "Deliver-yesterday"})
|
||||
...> conn = build_conn() |> assign(:tenant, tenant)
|
||||
...> tenant = get_tenant(conn)
|
||||
%{name: "Deliver-yesterday"} = tenant
|
||||
"""
|
||||
@spec get_tenant(Conn.t()) :: nil | Ash.Resource.record()
|
||||
def get_tenant(%{assigns: %{tenant: tenant}}) when not is_nil(tenant) do
|
||||
emit_assign_warning(:tenant)
|
||||
|
||||
tenant
|
||||
end
|
||||
|
||||
def get_tenant(%{private: %{ash: %{tenant: tenant}}}), do: tenant
|
||||
def get_tenant(_), do: nil
|
||||
end
|
||||
end
|
5
mix.exs
5
mix.exs
|
@ -19,7 +19,7 @@ defmodule Ash.MixProject do
|
|||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
package: package(),
|
||||
deps: deps(),
|
||||
dialyzer: [plt_add_apps: [:mix, :mnesia, :earmark]],
|
||||
dialyzer: [plt_add_apps: [:mix, :mnesia, :earmark, :plug]],
|
||||
xref: [exclude: [:mnesia]],
|
||||
docs: docs(),
|
||||
aliases: aliases(),
|
||||
|
@ -238,7 +238,8 @@ defmodule Ash.MixProject do
|
|||
{:git_ops, "~> 2.5", only: :dev},
|
||||
{:mix_test_watch, "~> 1.0", only: :dev, runtime: false},
|
||||
{:parse_trans, "3.3.0", only: [:dev, :test], override: true},
|
||||
{:elixir_sense, github: "elixir-lsp/elixir_sense", only: [:dev, :test, :docs]}
|
||||
{:elixir_sense, github: "elixir-lsp/elixir_sense", only: [:dev, :test, :docs]},
|
||||
{:plug, ">= 0.0.0", only: [:dev, :test], runtime: false}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
3
mix.lock
3
mix.lock
|
@ -27,12 +27,15 @@
|
|||
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
|
||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
|
||||
"mix_test_watch": {:hex, :mix_test_watch, "1.1.0", "330bb91c8ed271fe408c42d07e0773340a7938d8a0d281d57a14243eae9dc8c3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "52b6b1c476cbb70fd899ca5394506482f12e5f6b0d6acff9df95c7f1e0812ec3"},
|
||||
"nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
|
||||
"picosat_elixir": {:hex, :picosat_elixir, "0.2.0", "1c9d2d65b32039c9e3eb600bff903579e5916f559dbf0013b3c4ca617c93ac64", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "7900c6d58d65a9d8a2899f53019fb70e9b9678161bbb53f646e28e146aca138c"},
|
||||
"plug": {:hex, :plug, "1.13.6", "187beb6b67c6cec50503e940f0434ea4692b19384d47e5fdfd701e93cadb4cc2", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02b9c6b9955bce92c829f31d6284bf53c591ca63c4fb9ff81dfd0418667a34ff"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
|
||||
"providers": {:hex, :providers, "1.8.1", "70b4197869514344a8a60e2b2a4ef41ca03def43cfb1712ecf076a0f3c62f083", [:rebar3], [{:getopt, "1.0.1", [hex: :getopt, repo: "hexpm", optional: false]}], "hexpm", "e45745ade9c476a9a469ea0840e418ab19360dc44f01a233304e118a44486ba0"},
|
||||
"sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"},
|
||||
"sourceror": {:hex, :sourceror, "0.11.2", "549ce48be666421ac60cfb7f59c8752e0d393baa0b14d06271d3f6a8c1b027ab", [:mix], [], "hexpm", "9ab659118896a36be6eec68ff7b0674cba372fc8e210b1e9dc8cf2b55bb70dfb"},
|
||||
|
|
97
test/plug_helpers.ex
Normal file
97
test/plug_helpers.ex
Normal file
|
@ -0,0 +1,97 @@
|
|||
defmodule Ash.Test.PlugHelpersTest do
|
||||
@moduledoc false
|
||||
use ExUnit.Case, async: true
|
||||
alias Ash.Changeset
|
||||
import Ash.PlugHelpers
|
||||
import Plug.Conn
|
||||
|
||||
def build_conn, do: Plug.Test.conn(:get, "/")
|
||||
|
||||
defmodule User do
|
||||
@moduledoc false
|
||||
use Ash.Resource, data_layer: Ash.DataLayer.Ets
|
||||
|
||||
ets do
|
||||
private? true
|
||||
end
|
||||
|
||||
actions do
|
||||
read :read
|
||||
create :create
|
||||
end
|
||||
|
||||
attributes do
|
||||
uuid_primary_key :id
|
||||
attribute :email, :string
|
||||
end
|
||||
|
||||
multitenancy do
|
||||
strategy :attribute
|
||||
attribute :customer_id
|
||||
end
|
||||
|
||||
relationships do
|
||||
belongs_to :customer, Customer
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Customer do
|
||||
@moduledoc false
|
||||
use Ash.Resource, data_layer: Ash.DataLayer.Ets
|
||||
|
||||
ets do
|
||||
private? true
|
||||
end
|
||||
|
||||
actions do
|
||||
read :read
|
||||
create :create
|
||||
end
|
||||
|
||||
attributes do
|
||||
uuid_primary_key :id
|
||||
attribute :name, :string
|
||||
end
|
||||
|
||||
relationships do
|
||||
has_many :users, User
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Registry do
|
||||
@moduledoc false
|
||||
use Ash.Registry
|
||||
|
||||
entries do
|
||||
entry Customer
|
||||
entry User
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Api do
|
||||
@moduledoc false
|
||||
use Ash.Api
|
||||
|
||||
resources do
|
||||
registry Registry
|
||||
end
|
||||
end
|
||||
|
||||
def build_actor(attrs) do
|
||||
attrs =
|
||||
attrs
|
||||
|> Map.put_new_lazy(:customer_id, fn -> build_tenant(%{name: "Deliver-yesterday"}).id end)
|
||||
|
||||
User
|
||||
|> Changeset.for_create(:create, attrs, tenant: attrs.customer_id)
|
||||
|> Api.create!()
|
||||
end
|
||||
|
||||
def build_tenant(attrs) do
|
||||
Customer
|
||||
|> Changeset.for_create(:create, attrs)
|
||||
|> Api.create!()
|
||||
end
|
||||
|
||||
doctest Ash.PlugHelpers
|
||||
end
|
Loading…
Reference in a new issue