Skip to content

Commit

Permalink
improvement: Allow resources to opt out of the primary key requiremen…
Browse files Browse the repository at this point in the history
…t. (#166)
  • Loading branch information
jimsynz authored Sep 6, 2023
1 parent 6b00276 commit 6cc88c8
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 10 deletions.
17 changes: 12 additions & 5 deletions lib/data_layer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -540,8 +540,13 @@ defmodule AshPostgres.DataLayer do
def can?(_, :create), do: true
def can?(_, :select), do: true
def can?(_, :read), do: true
def can?(_, :update), do: true
def can?(_, :destroy), do: true

def can?(resource, action) when action in ~w[update destroy]a do
resource
|> Ash.Resource.Info.primary_key()
|> Enum.any?()
end

def can?(_, :filter), do: true
def can?(_, :limit), do: true
def can?(_, :offset), do: true
Expand Down Expand Up @@ -1754,9 +1759,11 @@ defmodule AshPostgres.DataLayer do
value -> List.wrap(value)
end

names = [
{Ash.Resource.Info.primary_key(resource), table(resource, ash_changeset) <> "_pkey"} | names
]
names =
case Ash.Resource.Info.primary_key(resource) do
[] -> names
fields -> [{fields, table(resource, ash_changeset) <> "_pkey"} | names]
end

Enum.reduce(names, changeset, fn
{keys, name}, changeset ->
Expand Down
4 changes: 3 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ defmodule AshPostgres.MixProject do
"coveralls.github": :test,
"test.create": :test,
"test.migrate": :test,
"test.rollback": :test,
"test.migrate_tenants": :test,
"test.check_migrations": :test,
"test.drop": :test,
Expand Down Expand Up @@ -161,7 +162,7 @@ defmodule AshPostgres.MixProject do
{:ecto, "~> 3.9"},
{:jason, "~> 1.0"},
{:postgrex, ">= 0.0.0"},
{:ash, ash_version("~> 2.14 and >= 2.14.5")},
{:ash, ash_version("~> 2.14 and >= 2.14.12")},
{:git_ops, "~> 2.5", only: [:dev, :test]},
{:ex_doc, "~> 0.22", only: [:dev, :test], runtime: false},
{:ex_check, "~> 0.14", only: [:dev, :test]},
Expand Down Expand Up @@ -202,6 +203,7 @@ defmodule AshPostgres.MixProject do
"test.check_migrations": "ash_postgres.generate_migrations --check",
"test.migrate_tenants": "ash_postgres.migrate --tenants",
"test.migrate": "ash_postgres.migrate",
"test.rollback": "ash_postgres.rollback",
"test.create": "ash_postgres.create",
"test.reset": ["test.drop", "test.create", "test.migrate", "ash_postgres.migrate --tenants"],
"test.drop": "ash_postgres.drop"
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
%{
"ash": {:hex, :ash, "2.14.6", "d70fc1395cb61ade00e12267d69db45cd6f4266df11ef24cea209c388029ee10", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bce08f059b523e7251e06d85260cf7d727386d45993e5120da35630c1fa79f12"},
"ash": {:hex, :ash, "2.14.12", "bb2e3dbe82b49407f07854560ba3174a2c479508da8f33eb296764c6445477fb", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "799edb6326bd539ede0582b3e62f015a74062d3291e7b2c21ce4b0a2f9ebe88c"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
"comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"},
Expand Down
49 changes: 49 additions & 0 deletions priv/resource_snapshots/test_repo/post_views/20230905050351.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"attributes": [
{
"default": "fragment(\"now()\")",
"size": null,
"type": "utc_datetime_usec",
"source": "time",
"references": null,
"allow_nil?": false,
"primary_key?": false,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": "text",
"source": "browser",
"references": null,
"allow_nil?": true,
"primary_key?": false,
"generated?": false
},
{
"default": "nil",
"size": null,
"type": "uuid",
"source": "post_id",
"references": null,
"allow_nil?": false,
"primary_key?": false,
"generated?": false
}
],
"table": "post_views",
"hash": "45E84815BD6E5B9617B491C57A429F210D98AE37428AC7C210B2E9D6AEA27DB3",
"repo": "Elixir.AshPostgres.TestRepo",
"schema": null,
"check_constraints": [],
"identities": [],
"custom_indexes": [],
"multitenancy": {
"global": null,
"attribute": null,
"strategy": null
},
"base_filter": null,
"custom_statements": [],
"has_create_action": true
}
21 changes: 21 additions & 0 deletions priv/test_repo/migrations/20230905050351_add_post_views.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule AshPostgres.TestRepo.Migrations.AddPostViews do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""

use Ecto.Migration

def up do
create table(:post_views, primary_key: false) do
add :time, :utc_datetime_usec, null: false, default: fragment("now()")
add :browser, :text
add :post_id, :uuid, null: false
end
end

def down do
drop table(:post_views)
end
end
43 changes: 40 additions & 3 deletions test/primary_key_test.exs
Original file line number Diff line number Diff line change
@@ -1,14 +1,51 @@
defmodule AshPostgres.Test.PrimaryKeyTest do
@moduledoc false
use AshPostgres.RepoCase, async: false
alias AshPostgres.Test.{Api, IntegerPost, Post}
alias AshPostgres.Test.{Api, IntegerPost, Post, PostView}

require Ash.Query

test "creates resource with integer primary key" do
test "creates record with integer primary key" do
assert %IntegerPost{} = IntegerPost |> Ash.Changeset.new(%{title: "title"}) |> Api.create!()
end

test "creates resource with uuid primary key" do
test "creates record with uuid primary key" do
assert %Post{} = Post |> Ash.Changeset.new(%{title: "title"}) |> Api.create!()
end

describe "resources without a primary key" do
test "records can be created" do
post =
Post
|> Ash.Changeset.for_action(:create, %{title: "not very interesting"})
|> Api.create!()

assert {:ok, view} =
PostView
|> Ash.Changeset.for_action(:create, %{browser: :firefox, post_id: post.id})
|> Api.create()

assert view.browser == :firefox
assert view.post_id == post.id
assert DateTime.diff(DateTime.utc_now(), view.time, :microsecond) < 1_000_000
end

test "records can be queried" do
post =
Post
|> Ash.Changeset.for_action(:create, %{title: "not very interesting"})
|> Api.create!()

expected =
PostView
|> Ash.Changeset.for_action(:create, %{browser: :firefox, post_id: post.id})
|> Api.create!()

assert {:ok, [actual]} = Api.read(PostView)

assert actual.time == expected.time
assert actual.browser == expected.browser
assert actual.post_id == expected.post_id
end
end
end
1 change: 1 addition & 0 deletions test/support/registry.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule AshPostgres.Test.Registry do
entry(AshPostgres.Test.IntegerPost)
entry(AshPostgres.Test.Rating)
entry(AshPostgres.Test.PostLink)
entry(AshPostgres.Test.PostView)
entry(AshPostgres.Test.Author)
entry(AshPostgres.Test.Profile)
entry(AshPostgres.Test.User)
Expand Down
2 changes: 2 additions & 0 deletions test/support/resources/post.ex
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ defmodule AshPostgres.Test.Post do
source_attribute_on_join_resource: :source_post_id,
destination_attribute_on_join_resource: :destination_post_id
)

has_many(:views, AshPostgres.Test.PostView)
end

validations do
Expand Down
33 changes: 33 additions & 0 deletions test/support/resources/post_views.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule AshPostgres.Test.PostView do
@moduledoc false
use Ash.Resource, data_layer: AshPostgres.DataLayer

actions do
defaults([:create, :read])
end

attributes do
create_timestamp(:time)
attribute(:browser, :atom, constraints: [one_of: [:firefox, :chrome, :edge]])
end

relationships do
belongs_to :post, AshPostgres.Test.Post do
allow_nil?(false)
attribute_writable?(true)
end
end

resource do
require_primary_key?(false)
end

postgres do
table "post_views"
repo AshPostgres.TestRepo

references do
reference :post, ignore?: true
end
end
end

0 comments on commit 6cc88c8

Please sign in to comment.