Skip to content

Commit 2a54225

Browse files
committed
Use Ecto Multi over implicit Changeset logic
1 parent d888d01 commit 2a54225

12 files changed

+517
-234
lines changed

lib/upload.ex

+53-17
Original file line numberDiff line numberDiff line change
@@ -57,21 +57,28 @@ defmodule Upload do
5757
|> repo.exists?()
5858
end
5959

60-
@spec remove_by_key(String.t()) :: :ok | {:error, any()}
61-
def remove_by_key(key) do
60+
@spec delete(Blob.t()) :: :ok | {:error, any()}
61+
def delete(blob) do
62+
repo = Upload.Config.repo()
63+
64+
case Ecto.Multi.new()
65+
|> Upload.Multi.delete_blob(:remove_existing_blob, blob)
66+
|> repo.transaction() do
67+
{:ok, _} -> :ok
68+
error -> error
69+
end
70+
end
71+
72+
@spec delete_by_key(String.t()) :: :ok | {:error, any()}
73+
def delete_by_key(key) do
6274
repo = Upload.Config.repo()
6375

6476
case repo.get_by(Upload.Blob, key: key) do
6577
nil ->
6678
:ok
6779

6880
blob ->
69-
case Ecto.Multi.new()
70-
|> Upload.Multi.purge(:remove_existing_blob, blob)
71-
|> repo.transaction() do
72-
{:ok, _} -> :ok
73-
error -> error
74-
end
81+
delete(blob)
7582
end
7683
end
7784

@@ -106,17 +113,34 @@ defmodule Upload do
106113
iex> create_variant(original_blob, :small, &transform_fn/3)
107114
{:ok, %Blob{}}
108115
"""
109-
@spec create_variant(Blob.t(), String.t(), any()) :: {:ok, Blob.t()} | {:error, any()}
110-
def create_variant(original_blob, variant, transform_fn) when is_function(transform_fn, 3) do
116+
@spec create_variant(Blob.t(), String.t(), any(), keyword()) ::
117+
{:ok, Blob.t()} | {:error, any()}
118+
def create_variant(original_blob, variant, transform_fn, opts \\ [])
119+
when is_function(transform_fn, 3) do
111120
repo = Upload.Config.repo()
112121
variant = to_string(variant)
122+
formats = Keyword.get(opts, :formats, [:"image/jpeg"])
123+
124+
multi =
125+
Ecto.Multi.new()
126+
|> Upload.Multi.remove_existing_variant(original_blob, variant)
113127

114-
Ecto.Multi.new()
115-
|> Upload.Multi.remove_existing_variant(original_blob, variant)
116-
|> Upload.Multi.download_and_insert_variant(original_blob, variant, transform_fn)
128+
multi =
129+
formats
130+
|> Enum.reduce(multi, fn format, multi ->
131+
Upload.Multi.download_and_insert_variant(
132+
multi,
133+
original_blob,
134+
variant,
135+
transform_fn,
136+
format
137+
)
138+
end)
139+
140+
multi
117141
|> repo.transaction()
118142
|> case do
119-
{:ok, multi_result} -> {:ok, Map.fetch!(multi_result, "download_and_insert_#{variant}")}
143+
{:ok, multi_result} -> {:ok, Map.values(multi_result)}
120144
{:error, error} -> {:error, error}
121145
end
122146
end
@@ -127,22 +151,34 @@ defmodule Upload do
127151
128152
## Example
129153
130-
iex> create_multiple_variants(blob, [:small, :large], &transform_fn/3)
154+
iex> create_multiple_variants(blob, [:small, :large], &transform_fn/2)
131155
{:ok, [%Blob{...}, %Blob{...}]}
132156
"""
133157
@spec create_multiple_variants(Blob.t(), [variant_id()], any()) ::
134158
{:ok, [Blob.t()]} | {:error, String.t(), any()}
135-
def create_multiple_variants(original_blob, variants, transform_fn)
159+
def create_multiple_variants(original_blob, variants, transform_fn, opts \\ [])
136160
when is_function(transform_fn, 3) do
137161
variants = Enum.map(variants, &to_string/1)
162+
formats = Keyword.get(opts, :formats, [:"image/jpeg"])
163+
138164
repo = Upload.Config.repo()
139165
multi = Ecto.Multi.new()
140166

141167
variants
142168
|> Enum.reduce(multi, fn variant, multi ->
143169
multi
144170
|> Upload.Multi.remove_existing_variant(original_blob, variant)
145-
|> Upload.Multi.download_and_insert_variant(original_blob, variant, transform_fn)
171+
172+
formats
173+
|> Enum.reduce(multi, fn format, multi ->
174+
Upload.Multi.download_and_insert_variant(
175+
multi,
176+
original_blob,
177+
variant,
178+
transform_fn,
179+
format
180+
)
181+
end)
146182
end)
147183
|> repo.transaction()
148184
|> case do

lib/upload/blob.ex

+32-27
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,9 @@ defmodule Upload.Blob do
6464
|> foreign_key_constraint(:original_blob_id)
6565
|> validate_original_blob_id_is_not_variant()
6666
|> check_constraint(:variant, name: :variant_and_original_blob_id_are_only_nullable_together)
67-
|> maybe_upload()
68-
|> maybe_delete()
67+
68+
# |> maybe_upload()
69+
# |> maybe_delete()
6970
end
7071

7172
defp generate_id(changeset) do
@@ -84,7 +85,11 @@ defmodule Upload.Blob do
8485
key <> "." <> extension
8586
end)
8687
else
87-
add_error(changeset, :key, "Could not set extension from MIME type: '#{mime}'")
88+
add_error(
89+
changeset,
90+
:key,
91+
"Could not set the extension from the given MIME type: '#{mime}'"
92+
)
8893
end
8994
end
9095

@@ -98,30 +103,30 @@ defmodule Upload.Blob do
98103
)
99104
end
100105

101-
defp maybe_upload(changeset) do
102-
prepare_changes(changeset, fn changeset ->
103-
if changeset.action == :insert do
104-
%{path: path, key: key} = changeset.changes
105-
106-
:ok = Upload.Storage.upload(path, key)
107-
end
108-
109-
changeset
110-
end)
111-
end
112-
113-
defp maybe_delete(changeset) do
114-
prepare_changes(changeset, fn changeset ->
115-
if changeset.action == :delete do
116-
{:ok, _} =
117-
Ecto.Multi.new()
118-
|> Upload.Multi.purge(:blob, changeset.data)
119-
|> Upload.Config.repo().transaction()
120-
end
121-
122-
changeset
123-
end)
124-
end
106+
# defp maybe_upload(changeset) do
107+
# prepare_changes(changeset, fn changeset ->
108+
# if changeset.action == :insert do
109+
# %{path: path, key: key} = changeset.changes
110+
111+
# :ok = Upload.Storage.upload(path, key)
112+
# end
113+
114+
# changeset
115+
# end)
116+
# end
117+
118+
# defp maybe_delete(changeset) do
119+
# prepare_changes(changeset, fn changeset ->
120+
# if changeset.action == :delete do
121+
# {:ok, _} =
122+
# Ecto.Multi.new()
123+
# |> Upload.Multi.purge(:blob, changeset.data)
124+
# |> Upload.Config.repo().transaction()
125+
# end
126+
127+
# changeset
128+
# end)
129+
# end
125130

126131
defp validate_original_blob_id_is_not_variant(changeset) do
127132
repo = Upload.Config.repo()

lib/upload/changeset.ex

+59-28
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,30 @@ defmodule Upload.Changeset do
8282
case Map.fetch(changeset.params, to_string(field)) do
8383
{:ok, %Plug.Upload{} = upload} ->
8484
changeset
85-
|> remove_existing_field(field)
85+
# |> remove_existing_field(field)
8686
|> put_attachment(field, upload, key)
8787

88+
{:ok, path} when is_binary(path) ->
89+
case Upload.stat(path) do
90+
{:error, _error} ->
91+
message = Keyword.get(opts, :invalid_message, "is invalid")
92+
meta = [validation: :assoc, type: :map]
93+
add_error(changeset, field, message, meta)
94+
95+
{:ok, stat} ->
96+
changeset
97+
# |> remove_existing_field(field)
98+
|> put_attachment(field, stat, key)
99+
end
100+
88101
{:ok, nil} ->
89102
if Keyword.get(opts, :required, false) do
90103
message = Keyword.get(opts, :required_message, "can't be blank")
91104
meta = [validation: :required]
92105
add_error(changeset, field, message, meta)
93106
else
94107
changeset
95-
|> remove_existing_field(field)
108+
# |> remove_existing_field(field)
96109
|> put_assoc(field, nil)
97110
end
98111

@@ -106,39 +119,57 @@ defmodule Upload.Changeset do
106119
end
107120
end
108121

109-
defp remove_existing_field(changeset, field) do
110-
Ecto.Changeset.prepare_changes(changeset, fn changeset ->
111-
# record = repo.preload(changeset.data, field)
122+
# defp remove_existing_field(changeset, field) do
123+
# Ecto.Changeset.prepare_changes(changeset, fn changeset ->
124+
# # record = repo.preload(changeset.data, field)
112125

113-
case get_in(get_field(changeset, field), [Access.key(:key)]) do
114-
nil ->
115-
delete_existing_association_if_any(changeset, field)
126+
# dbg(changeset)
127+
# dbg(changeset.data)
116128

117-
key ->
118-
Upload.remove_by_key(key)
119-
end
129+
# # Upload.delete_by_key(changeset.changes.avatar.key)
120130

121-
changeset
122-
end)
123-
end
131+
# case get_in(get_field(changeset, field), [Access.key(:key)]) do
132+
# nil ->
133+
# nil
124134

125-
defp delete_existing_association_if_any(changeset, field) do
126-
repo = Upload.Config.repo()
135+
# # Logger.info("Deleting existing association")
136+
# # delete_existing_association_if_any(changeset, field)
127137

128-
case Map.get(changeset.data, field) do
129-
%Ecto.Association.NotLoaded{} ->
130-
raise "Calling cast_attachment requires the field to be preloaded."
138+
# key ->
139+
# Logger.info("Deleting existing entry by key")
140+
# Upload.delete_by_key(key)
141+
# end
131142

132-
nil ->
133-
:ok
143+
# # repo = Upload.Config.repo()
134144

135-
association ->
136-
{:ok, _} =
137-
Ecto.Multi.new()
138-
|> Upload.Multi.purge(:remove_existing_blob, association)
139-
|> repo.transaction()
140-
end
141-
end
145+
# # IO.inspect(changeset.data)
146+
# # IO.inspect(repo.reload(changeset.data))
147+
148+
# changeset
149+
# end)
150+
# end
151+
152+
# defp delete_existing_association_if_any(changeset, field) do
153+
# repo = Upload.Config.repo()
154+
155+
# dbg(changeset.data)
156+
157+
# case Map.get(changeset.data, field) do
158+
# %Ecto.Association.NotLoaded{} ->
159+
# raise "Calling cast_attachment requires the field to be preloaded."
160+
161+
# nil ->
162+
# :ok
163+
164+
# association ->
165+
# dbg(association)
166+
167+
# {:ok, _} =
168+
# Ecto.Multi.new()
169+
# |> Upload.Multi.purge(:remove_existing_blob, association)
170+
# |> repo.transaction()
171+
# end
172+
# end
142173

143174
@spec validate_attachment(changeset, field, field, validation) :: changeset
144175
def validate_attachment(changeset, field, blob_field, validation) do

lib/upload/logger.ex

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
defmodule Upload.Logger do
2+
require Logger
3+
4+
def info(message) do
5+
Logger.info(message)
6+
end
7+
end

lib/upload/migrations.ex

+4-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ defmodule Upload.Migrations do
2525
add(:checksum, :string, null: false)
2626

2727
add(:variant, :string)
28-
add(:original_blob_id, references(:blobs, type: :binary_id), type: :binary_id)
28+
29+
add(:original_blob_id, references(:blobs, type: :binary_id, on_delete: :delete_all),
30+
type: :binary_id
31+
)
2932

3033
timestamps(updated_at: false)
3134
end

0 commit comments

Comments
 (0)