Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add restore functionality #143

Open
wants to merge 62 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 61 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
dc3e751
add restore functionality
mithereal Mar 30, 2023
65ab0e0
Merge branch 'revelrylabs:master' into master
mithereal Nov 18, 2023
163efdf
fix travis test
mithereal Nov 18, 2023
ae2d7b6
fix travis test
mithereal Nov 18, 2023
d43cd77
fix travis test
mithereal Nov 18, 2023
4f9d963
fix travis test
mithereal Nov 18, 2023
93aade4
fix travis test
mithereal Nov 18, 2023
5c584ed
fix travis test
mithereal Nov 19, 2023
99888dc
fix travis test
mithereal Nov 19, 2023
50a6d17
fix travis test
mithereal Nov 19, 2023
b274dbf
fix travis test
mithereal Nov 19, 2023
767da99
fix travis test
mithereal Nov 19, 2023
74765ca
fix travis test
mithereal Nov 19, 2023
e5b295e
fix travis test
mithereal Nov 19, 2023
b6ed4be
fix travis test
mithereal Nov 19, 2023
26b39fd
fix travis test
mithereal Nov 19, 2023
e4875cc
fix travis test
mithereal Nov 19, 2023
268264f
fix travis test
mithereal Nov 19, 2023
f0ff87c
fix travis test
mithereal Nov 19, 2023
b8a1fa3
fix travis test
mithereal Nov 19, 2023
8338dda
fix travis test
mithereal Nov 19, 2023
67793ba
fix travis test
mithereal Nov 19, 2023
851d416
fix travis test
mithereal Nov 19, 2023
de3b521
fix travis test
mithereal Nov 19, 2023
598598e
fix travis test
mithereal Nov 19, 2023
991e30e
fix travis test
mithereal Nov 19, 2023
8bf9591
fix travis test
mithereal Nov 19, 2023
e5518b0
fix travis test
mithereal Nov 19, 2023
629160c
fix travis test
mithereal Nov 19, 2023
06b3284
fix travis test
mithereal Nov 19, 2023
6b5328f
fix travis test
mithereal Nov 19, 2023
ea40e69
fix travis test
mithereal Nov 19, 2023
1db064b
fix travis test
mithereal Nov 19, 2023
e833b92
fix travis test
mithereal Nov 19, 2023
db6e502
fix travis test
mithereal Nov 19, 2023
b1e1c22
fix travis test
mithereal Nov 19, 2023
18a1670
fix travis test
mithereal Nov 19, 2023
32910ca
fix travis test
mithereal Nov 19, 2023
8e5a229
fix travis test
mithereal Nov 19, 2023
e031344
fix travis test
mithereal Nov 19, 2023
72c932c
fix travis test
mithereal Nov 19, 2023
5a17175
fix travis test
mithereal Nov 19, 2023
37071e4
fix travis test
mithereal Nov 19, 2023
fca9455
fix travis test
mithereal Nov 19, 2023
40952c5
fix travis test
mithereal Nov 19, 2023
6fc8520
fix travis test
mithereal Nov 19, 2023
3d22526
fix travis test
mithereal Nov 19, 2023
c87543a
fix travis test
mithereal Nov 19, 2023
391583d
fix travis test
mithereal Nov 19, 2023
d7f2f62
fix travis test
mithereal Nov 19, 2023
dfdb307
fix travis test
mithereal Nov 19, 2023
8e446f5
fix travis test
mithereal Nov 19, 2023
0fdfc36
fix travis test
mithereal Nov 19, 2023
f329109
add deleted_at index
mithereal Nov 19, 2023
ec92a16
fix #159
mithereal Nov 19, 2023
f13c1ed
Revert "fix #159"
mithereal Nov 19, 2023
85a123c
rename soft_restore to restore
mithereal Nov 21, 2023
13ac95d
Revert "rename soft_restore to restore"
mithereal Nov 21, 2023
360f427
fix soft_restore functions
mithereal Feb 11, 2024
7dff0d4
fix tests
mithereal Feb 11, 2024
512081c
Apply suggestions from code review
mithereal Feb 12, 2024
4821a61
remove index and fix test
mithereal Feb 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
import_deps: [:ecto],
]
4 changes: 2 additions & 2 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
import Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
Expand All @@ -27,4 +27,4 @@ use Mix.Config
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
import_config "#{Mix.env}.exs"
import_config "#{Mix.env()}.exs"
2 changes: 1 addition & 1 deletion config/dev.exs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
use Mix.Config
import Config
2 changes: 1 addition & 1 deletion config/prod.exs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
use Mix.Config
import Config
2 changes: 1 addition & 1 deletion config/test.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use Mix.Config
import Config

config :ecto_soft_delete, ecto_repos: [Ecto.SoftDelete.Test.Repo]

Expand Down
2 changes: 1 addition & 1 deletion config/test.exs.travis
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use Mix.Config
import Config

config :ecto_soft_delete, ecto_repos: [Ecto.SoftDelete.Test.Repo]

Expand Down
5 changes: 5 additions & 0 deletions lib/ecto/soft_delete_migration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ defmodule Ecto.SoftDelete.Migration do
add :password, :string
timestamps()
soft_delete_columns()
soft_delete_index(:users)
mithereal marked this conversation as resolved.
Show resolved Hide resolved
end
end
end
Expand All @@ -26,4 +27,8 @@ defmodule Ecto.SoftDelete.Migration do
def soft_delete_columns do
add(:deleted_at, :utc_datetime_usec, [])
end

def soft_delete_index(table) do
create(index(table, [:deleted_at]))
end
end
2 changes: 1 addition & 1 deletion lib/ecto/soft_delete_query.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule Ecto.SoftDelete.Query do
results = Repo.all(query)

"""
@spec with_undeleted(Ecto.Queryable.t) :: Ecto.Queryable.t
@spec with_undeleted(Ecto.Queryable.t()) :: Ecto.Queryable.t()
def with_undeleted(query) do
query
|> where([t], is_nil(t.deleted_at))
Expand Down
60 changes: 58 additions & 2 deletions lib/ecto/soft_delete_repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ defmodule Ecto.SoftDelete.Repo do
"""
@callback soft_delete_all(queryable :: Ecto.Queryable.t()) :: {integer, nil | [term]}

@doc """
Soft restores all entries matching the given query.

It returns a tuple containing the number of entries and any returned
result as second element. The second element is `nil` by default
unless a `select` is supplied in the update query.

## Examples

MyRepo.soft_restore_all(%Post{}, MyRepo)


"""
@callback soft_restore_all(struct :: Ecto.Queryable.t()) :: {integer, nil | [term]}

@doc """
Soft deletes a struct.
Updates the `deleted_at` field with the current datetime in UTC.
Expand All @@ -37,8 +52,8 @@ defmodule Ecto.SoftDelete.Repo do

post = MyRepo.get!(Post, 42)
case MyRepo.soft_delete post do
{:ok, struct} -> # Soft deleted with success
{:error, changeset} -> # Something went wrong
{:ok, struct} -> "Soft deleted with success"
{:error, changeset} -> "Something went wrong"
end

"""
Expand All @@ -51,6 +66,31 @@ defmodule Ecto.SoftDelete.Repo do
@callback soft_delete!(struct_or_changeset :: Ecto.Schema.t() | Ecto.Changeset.t()) ::
Ecto.Schema.t()

@doc """
Soft restores a struct.
Updates the `deleted_at` to null.
It returns `{:ok, struct}` if the struct has been successfully
soft deleted or `{:error, changeset}` if there was a validation
or a known constraint error.

## Examples

post = MyRepo.get!(Post, 42)
case MyRepo.soft_restore post do
{:ok, struct} -> "Soft restore with success"
{:error, changeset} -> "Something went wrong"
end

"""
@callback soft_restore(struct :: Ecto.Schema.t()) ::
{:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}

@doc """
Same as `c:soft_restore/1` but returns the struct or raises if the changeset is invalid.
"""
@callback soft_restore!(struct :: Ecto.Schema.t()) ::
Ecto.Schema.t()

defmacro __using__(_opts) do
quote do
import Ecto.Query
Expand All @@ -71,6 +111,22 @@ defmodule Ecto.SoftDelete.Repo do
|> update!()
end

def soft_restore_all(queryable) do
update_all(queryable, set: [deleted_at: nil])
end

def soft_restore(struct_or_changeset) do
struct_or_changeset
|> Ecto.Changeset.change(deleted_at: nil)
|> update()
end

def soft_restore!(struct_or_changeset) do
struct_or_changeset
|> Ecto.Changeset.change(deleted_at: nil)
|> update!()
end

@doc """
Overrides all query operations to exclude soft deleted records
if the schema in the from clause has a deleted_at column
Expand Down
2 changes: 1 addition & 1 deletion lib/ecto/soft_delete_schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ defmodule Ecto.SoftDelete.Schema do
"""
defmacro soft_delete_schema do
quote do
field :deleted_at, :utc_datetime_usec
field(:deleted_at, :utc_datetime_usec)
end
end
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule EctoSoftDelete.Mixfile do
def project do
[
app: :ecto_soft_delete,
version: "2.0.2",
version: "2.0.3",
elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()),
build_embedded: Mix.env() == :prod,
Expand Down
10 changes: 7 additions & 3 deletions test/soft_delete_migration_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ defmodule Ecto.SoftDelete.Migration.Test do

setup meta do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(Repo)
{:ok, runner} = Runner.start_link({self(), Repo, __MODULE__, meta[:direction] || :forward, :up, %{level: false, sql: false}})

{:ok, runner} =
Runner.start_link(
{self(), Repo, __MODULE__, meta[:direction] || :forward, :up, %{level: false, sql: false}}
)

Runner.metadata(runner, meta)
{:ok, runner: runner}
end
Expand All @@ -21,7 +26,6 @@ defmodule Ecto.SoftDelete.Migration.Test do

flush()

assert {:create, _,
[{:add, :deleted_at, :utc_datetime_usec, []}]} = create_command
assert {:create, _, [{:add, :deleted_at, :utc_datetime_usec, []}]} = create_command
end
end
49 changes: 49 additions & 0 deletions test/soft_delete_repo_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,55 @@ defmodule Ecto.SoftDelete.Repo.Test do
end
end

describe "soft_restore/1" do
test "should soft restore the queryable" do
user = Repo.insert!(%User{email: "[email protected]"})
user = Repo.soft_delete!(user)
Repo.soft_restore(user, Repo)

assert Repo.get_by!(User, [email: "[email protected]"], with_deleted: true).deleted_at ==
nil
end
end

describe "soft_restore_all/1" do
test "soft deleted the query" do
Repo.insert!(%User{email: "[email protected]"})
Repo.insert!(%User{email: "[email protected]"})
Repo.insert!(%User{email: "[email protected]"})

assert Repo.soft_delete_all(User) == {3, nil}

assert User |> Repo.all(with_deleted: true) |> length() == 3

assert %DateTime{} =
Repo.get_by!(User, [email: "[email protected]"], with_deleted: true).deleted_at

assert %DateTime{} =
Repo.get_by!(User, [email: "[email protected]"], with_deleted: true).deleted_at

assert %DateTime{} =
Repo.get_by!(User, [email: "[email protected]"], with_deleted: true).deleted_at

assert Repo.soft_restore_all(%User{}, Repo) == {3, nil}

assert User |> Repo.all(with_deleted: true) |> length() == 3

assert Repo.get_by!(User, [email: "[email protected]"], with_deleted: true).deleted_at ==
nil

assert Repo.get_by!(User, [email: "[email protected]"], with_deleted: true).deleted_at ==
nil

assert Repo.get_by!(User, [email: "[email protected]"], with_deleted: true).deleted_at ==
nil
end

test "when no results are found" do
assert Repo.soft_delete_all(User) == {0, nil}
end
end
mithereal marked this conversation as resolved.
Show resolved Hide resolved

describe "prepare_query/3" do
test "excludes soft deleted records by default" do
user = Repo.insert!(%User{email: "[email protected]"})
Expand Down
9 changes: 5 additions & 4 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
{:ok, _} = Application.ensure_all_started(:postgrex)
{:ok, _pid} = Ecto.SoftDelete.Test.Repo.start_link
{:ok, _pid} = Ecto.SoftDelete.Test.Repo.start_link()

defmodule Ecto.SoftDelete.Test.Migrations do
use Ecto.Migration
import Ecto.SoftDelete.Migration

def change do
drop_if_exists table(:users)
drop_if_exists(table(:users))

create table(:users) do
add :email, :string
add(:email, :string)
soft_delete_columns()
end

create table(:nondeletable) do
add :value, :string
add(:value, :string)
end
end
end
Expand Down