From bca82b2b13ccf3e09dbc9ae6b6e408e12238ef5e Mon Sep 17 00:00:00 2001 From: Roman Heinrich Date: Wed, 13 Dec 2023 22:12:07 +0100 Subject: [PATCH] Feat: add `upsert` macro --- CHANGELOG.md | 4 ++++ lib/dryhard.ex | 22 +++++++++++++++++++ .../20230320215000_dryhard_migrations.exs | 1 + test/dryhard_test.exs | 7 ++++++ 4 files changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b23741..e288a6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v0.1.1 (2023-12-13) + +- add new macro: `upsert`. It allows providing `:on_conflict` options, defaults to `[on_conflict: :nothing]` + ## v0.1.0 (2023-11-29) - mostly working, need a bit polish diff --git a/lib/dryhard.ex b/lib/dryhard.ex index f8e8d5f..d4e66bd 100644 --- a/lib/dryhard.ex +++ b/lib/dryhard.ex @@ -173,6 +173,28 @@ defmodule Dryhard do end end + defmacro upsert(config, changeset_fn, opts \\ [on_conflict: :nothing]) do + quote bind_quoted: [config: config, changeset_fn: changeset_fn, opts: opts] do + @doc """ + Creates a single #{config.plural_name} record (ignores conflicts, like ID / uniq indexes) + Provide upsert options as documented here: + - https://hexdocs.pm/ecto/Ecto.Repo.html#c:insert/2-upserts + + Defaults to: + `[on_conflict: :nothing]` + + Params: + - attrs: map to pass into the changeset function + """ + def unquote(:"upsert_#{config.singular_name}")(attrs) do + unquote(config.schema) + |> struct() + |> unquote(changeset_fn).(attrs) + |> unquote(config.repo).insert(unquote(opts)) + end + end + end + defmacro create(config, changeset_fn) do quote bind_quoted: [config: config, changeset_fn: changeset_fn] do @doc """ diff --git a/priv/repo/migrations/20230320215000_dryhard_migrations.exs b/priv/repo/migrations/20230320215000_dryhard_migrations.exs index 4c93102..a24e62b 100644 --- a/priv/repo/migrations/20230320215000_dryhard_migrations.exs +++ b/priv/repo/migrations/20230320215000_dryhard_migrations.exs @@ -17,6 +17,7 @@ defmodule Dryhard.Repo.Migrations.CreateTables do timestamps() end + create unique_index(:users, [:username]) create table(:posts) do add :user_id, references(:users) diff --git a/test/dryhard_test.exs b/test/dryhard_test.exs index f87f8d2..b6bbc17 100644 --- a/test/dryhard_test.exs +++ b/test/dryhard_test.exs @@ -22,6 +22,7 @@ defmodule DryhardTest do Dryhard.get(@resource) Dryhard.new(@resource) Dryhard.create(@resource, &User.changeset/2) + Dryhard.upsert(@resource, &User.changeset/2) Dryhard.change(@resource, &User.changeset/2) Dryhard.update(@resource, &User.changeset/2) Dryhard.delete(@resource) @@ -74,6 +75,12 @@ defmodule DryhardTest do assert user1 == UserContext.get_user_by_username("user1") end + test "upsert_user works (ignore conflicts on inserts by default)" do + {:ok, user1} = UserContext.upsert_user(%{username: "user1"}) + {:ok, _user1_duplicate} = UserContext.upsert_user(%{username: "user1"}) + assert user1 == UserContext.get_user_by_username("user1") + end + test "update_user works" do {:ok, user1} = UserContext.create_user(%{username: "user1"}) assert user1 == UserContext.get_user_by_username("user1")