From 311410a3eff6fd7d6709fbb50b5453a22e9506f2 Mon Sep 17 00:00:00 2001 From: tulinmola Date: Mon, 26 Jun 2017 15:29:06 +0200 Subject: [PATCH] Add delete_attachments to schema DummyDefinition being in the same file than schema test caused mock library to fail in some cases (as it documentation says, because of the way mock overrides the module, it must be defined in a separate file from the test file). That's why definition module was declared in setup for each test, causing some warnings that are gone now. --- README.md | 26 ++++++++++++++++++++++++++ lib/arc_ecto/definition.ex | 3 ++- lib/arc_ecto/schema.ex | 20 ++++++++++++++++++++ mix.exs | 5 +++++ test/schema_test.exs | 26 ++++++++++++++------------ test/support/dummy_definition.ex | 4 ++++ 6 files changed, 71 insertions(+), 13 deletions(-) create mode 100644 test/support/dummy_definition.ex diff --git a/README.md b/README.md index 57598af..5c574cc 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,32 @@ Both public and signed urls will include the timestamp for cache busting, and ar MyApp.Avatar.url({user.avatar, user}, :thumb, signed: true) ``` +### Delete attached file from storage + +Attachments can be deleted using `delete_attachments/2` before calling `Repo.delete/1`. + +```elixir +defmodule MyApp.User do + # ... + + def delete_changeset(data) do + data + |> changeset(%{}) + |> delete_attachments(@attachments) + end +end +``` + +Then, to delete, instead of calling `Repo.delete` from element, do it from deletion +changeset (like when using `Ecto.Changeset.prepare_changes/2`): + +```elixir + user + |> MyApp.User.delete_changeset() + |> MyApp.Repo.delete() + +``` + ## License Copyright 2015 Sean Stavropoulos diff --git a/lib/arc_ecto/definition.ex b/lib/arc_ecto/definition.ex index ee1c149..08b724a 100644 --- a/lib/arc_ecto/definition.ex +++ b/lib/arc_ecto/definition.ex @@ -6,6 +6,7 @@ defmodule Arc.Ecto.Definition do defmodule Module.concat(unquote(definition), "Type") do @behaviour Ecto.Type def type, do: Arc.Ecto.Type.type + def definition, do: unquote(definition) def cast(value), do: Arc.Ecto.Type.cast(unquote(definition), value) def load(value), do: Arc.Ecto.Type.load(unquote(definition), value) def dump(value), do: Arc.Ecto.Type.dump(unquote(definition), value) @@ -30,7 +31,7 @@ defmodule Arc.Ecto.Definition do end def url(f, v, options), do: super(f, v, options) - + def delete({%{file_name: file_name, updated_at: _updated_at}, scope}), do: super({file_name, scope}) def delete(args), do: super(args) diff --git a/lib/arc_ecto/schema.ex b/lib/arc_ecto/schema.ex index 3b9a56c..e924cd6 100644 --- a/lib/arc_ecto/schema.ex +++ b/lib/arc_ecto/schema.ex @@ -2,6 +2,26 @@ defmodule Arc.Ecto.Schema do defmacro __using__(_) do quote do import Arc.Ecto.Schema + + def delete_attachments(changeset_or_data, fields) do + scope = case changeset_or_data do + %Ecto.Changeset{} -> Ecto.Changeset.apply_changes(changeset_or_data) + %{__meta__: _} -> changeset_or_data + end + Enum.each(fields, &(delete_attachment(scope, &1))) + changeset_or_data + end + + defp delete_attachment(scope, field) do + type = __MODULE__.__schema__(:type, field) + definition = type.definition() + value = Map.get(scope, field) + if value do + {value, scope} + |> definition.urls() + |> Enum.each(fn {_size, path} -> definition.delete({path, scope}) end) + end + end end end diff --git a/mix.exs b/mix.exs index 45484d7..91a07e7 100644 --- a/mix.exs +++ b/mix.exs @@ -7,6 +7,7 @@ defmodule Arc.Ecto.Mixfile do [app: :arc_ecto, version: @version, elixir: "~> 1.0", + elixirc_paths: elixirc_paths(Mix.env), deps: deps(), # Hex @@ -21,6 +22,10 @@ defmodule Arc.Ecto.Mixfile do [applications: [:logger, :arc]] end + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + defp description do """ An integration with Arc and Ecto. diff --git a/test/schema_test.exs b/test/schema_test.exs index 86f71a1..d7e25c2 100644 --- a/test/schema_test.exs +++ b/test/schema_test.exs @@ -2,10 +2,7 @@ defmodule ArcTest.Ecto.Schema do use ExUnit.Case, async: false import Mock - defmodule DummyDefinition do - use Arc.Definition - use Arc.Ecto.Definition - end + alias ArcTest.Ecto.DummyDefinition defmodule TestUser do use Ecto.Schema @@ -36,15 +33,12 @@ defmodule ArcTest.Ecto.Schema do |> cast(params, ~w(first_name)a) |> cast_attachments(params, ~w(avatar)a) end - end - setup do - defmodule DummyDefinition do - use Arc.Definition - use Arc.Ecto.Definition + def delete_changeset(user, params) do + user + |> changeset(params) + |> delete_attachments(~w(avatar)a) end - - :ok end def build_upload(path) do @@ -70,7 +64,7 @@ defmodule ArcTest.Ecto.Schema do cs = TestUser.changeset(%TestUser{}, %{"avatar" => upload}) assert called DummyDefinition.store({upload, %TestUser{}}) assert cs.valid? == false - assert cs.errors == [avatar: {"is invalid", [type: ArcTest.Ecto.Schema.DummyDefinition.Type]}] + assert cs.errors == [avatar: {"is invalid", [type: DummyDefinition.Type]}] end test_with_mock "converts changeset into schema", DummyDefinition, [store: fn({%{__struct__: Plug.Upload, path: "/path/to/my/file.png", file_name: "file.png"}, %TestUser{}}) -> {:error, :invalid_file} end] do @@ -101,4 +95,12 @@ defmodule ArcTest.Ecto.Schema do changeset = TestUser.path_changeset(%TestUser{}, %{"avatar" => "/path/to/my/file.png"}) assert called DummyDefinition.store({"/path/to/my/file.png", %TestUser{}}) end + + test_with_mock "deletes attachments", DummyDefinition, [store: fn({%{__struct__: Plug.Upload, path: "/path/to/my/file.png", file_name: "file.png"}, %TestUser{}}) -> {:ok, "file.png"} end, + urls: fn({%{file_name: "file.png"}, %TestUser{}}) -> %{original: "file.png"} end, + delete: fn({"file.png", %TestUser{}}) -> :ok end] do + upload = build_upload("/path/to/my/file.png") + TestUser.delete_changeset(%TestUser{}, %{"avatar" => upload}) + assert called DummyDefinition.delete({"file.png", :_}) + end end diff --git a/test/support/dummy_definition.ex b/test/support/dummy_definition.ex new file mode 100644 index 0000000..089230e --- /dev/null +++ b/test/support/dummy_definition.ex @@ -0,0 +1,4 @@ +defmodule ArcTest.Ecto.DummyDefinition do + use Arc.Definition + use Arc.Ecto.Definition +end