Skip to content

Commit

Permalink
Cast Plug.Upload by default only
Browse files Browse the repository at this point in the history
  • Loading branch information
stavro committed Dec 19, 2016
1 parent 7c17544 commit 9719139
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 22 deletions.
22 changes: 17 additions & 5 deletions lib/arc_ecto/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ defmodule Arc.Ecto.Schema do
end
end

defmacro cast_attachments(changeset_or_data, params, allowed) do
defmacro cast_attachments(changeset_or_data, params, allowed, options \\ []) do
quote bind_quoted: [changeset_or_data: changeset_or_data,
params: params,
allowed: allowed] do
allowed: allowed,
options: options] do

# If given a changeset, apply the changes to obtain the underlying data
scope = case changeset_or_data do
Expand All @@ -31,9 +32,20 @@ defmodule Arc.Ecto.Schema do
params
|> Arc.Ecto.Schema.convert_params_to_binary
|> Dict.take(allowed)
|> Enum.map(fn
{field, nil} -> {field, nil}
{field, file} -> {field, {file, scope}}
|> Enum.reduce([], fn
# Don't wrap nil casts in the scope object
{field, nil}, fields -> [{field, nil} | fields]

# Allow casting Plug.Uploads
{field, upload = %{__struct__: Plug.Upload}}, fields -> [{field, {upload, scope}} | fields]

# If casting a binary (path), ensure we've explicitly allowed paths
{field, path}, fields when is_binary(path) ->
if Keyword.get(options, :allow_paths, false) do
[{field, {path, scope}} | fields]
else
fields
end
end)
|> Enum.into(%{})
end
Expand Down
54 changes: 37 additions & 17 deletions test/schema_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ defmodule ArcTest.Ecto.Schema do
|> validate_required(:avatar)
end

def path_changeset(user, params \\ :invalid) do
user
|> cast(params, ~w(first_name)a)
|> cast_attachments(params, ~w(avatar)a, allow_paths: true)
|> validate_required(:avatar)
end

def changeset2(user, params \\ :invalid) do
user
|> cast(params, ~w(first_name)a)
Expand All @@ -40,45 +47,58 @@ defmodule ArcTest.Ecto.Schema do
:ok
end

def build_upload(path) do
%{__struct__: Plug.Upload, path: path, file_name: Path.basename(path)}
end

test "supports :invalid changeset" do
cs = TestUser.changeset(%TestUser{})
assert cs.valid? == false
assert cs.changes == %{}
assert cs.errors == [avatar: {"can't be blank", []}]
end

test_with_mock "cascades storage success into a valid change", DummyDefinition, [store: fn({"/path/to/my/file.png", %TestUser{}}) -> {:ok, "file.png"} end] do
cs = TestUser.changeset(%TestUser{}, %{"avatar" => "/path/to/my/file.png"})
assert called DummyDefinition.store({"/path/to/my/file.png", %TestUser{}})
test_with_mock "cascades storage success into a valid change", DummyDefinition, [store: fn({%{__struct__: Plug.Upload, path: "/path/to/my/file.png", file_name: "file.png"}, %TestUser{}}) -> {:ok, "file.png"} end] do
upload = build_upload("/path/to/my/file.png")
cs = TestUser.changeset(%TestUser{}, %{"avatar" => upload})
assert cs.valid?
%{file_name: "file.png", updated_at: _} = cs.changes.avatar
end

test_with_mock "cascades storage error into an error", DummyDefinition, [store: fn({"/path/to/my/file.png", %TestUser{}}) -> {:error, :invalid_file} end] do
cs = TestUser.changeset(%TestUser{}, %{"avatar" => "/path/to/my/file.png"})
assert called DummyDefinition.store({"/path/to/my/file.png", %TestUser{}})
test_with_mock "cascades storage error into an error", DummyDefinition, [store: fn({%{__struct__: Plug.Upload, path: "/path/to/my/file.png", file_name: "file.png"}, %TestUser{}}) -> {:error, :invalid_file} end] do
upload = build_upload("/path/to/my/file.png")
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]}]
end

test_with_mock "converts changeset into schema", DummyDefinition, [store: fn({"/path/to/my/file.png", %TestUser{}}) -> {:error, :invalid_file} end] do
TestUser.changeset(%TestUser{}, %{"avatar" => "/path/to/my/file.png"})
assert called DummyDefinition.store({"/path/to/my/file.png", %TestUser{}})
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
upload = build_upload("/path/to/my/file.png")
TestUser.changeset(%TestUser{}, %{"avatar" => upload})
assert called DummyDefinition.store({upload, %TestUser{}})
end

test_with_mock "applies changes to schema", DummyDefinition, [store: fn({"/path/to/my/file.png", %TestUser{}}) -> {:error, :invalid_file} end] do
TestUser.changeset(%TestUser{}, %{"avatar" => "/path/to/my/file.png", "first_name" => "test"})
assert called DummyDefinition.store({"/path/to/my/file.png", %TestUser{first_name: "test"}})
test_with_mock "applies changes to schema", DummyDefinition, [store: fn({%{__struct__: Plug.Upload, path: "/path/to/my/file.png", file_name: "file.png"}, %TestUser{}}) -> {:error, :invalid_file} end] do
upload = build_upload("/path/to/my/file.png")
TestUser.changeset(%TestUser{}, %{"avatar" => upload, "first_name" => "test"})
assert called DummyDefinition.store({upload, %TestUser{first_name: "test"}})
end

test_with_mock "converts atom keys", DummyDefinition, [store: fn({"/path/to/my/file.png", %TestUser{}}) -> {:error, :invalid_file} end] do
TestUser.changeset(%TestUser{}, %{avatar: "/path/to/my/file.png"})
assert called DummyDefinition.store({"/path/to/my/file.png", %TestUser{}})
test_with_mock "converts atom keys", DummyDefinition, [store: fn({%{__struct__: Plug.Upload, path: "/path/to/my/file.png", file_name: "file.png"}, %TestUser{}}) -> {:error, :invalid_file} end] do
upload = build_upload("/path/to/my/file.png")
TestUser.changeset(%TestUser{}, %{avatar: upload})
assert called DummyDefinition.store({upload, %TestUser{}})
end

test_with_mock "casting nil attachments", DummyDefinition, [store: fn({"/path/to/my/file.png", %TestUser{}}) -> {:ok, "file.png"} end] do
changeset = TestUser.changeset(%TestUser{}, %{"avatar" => "/path/to/my/file.png"})
test_with_mock "casting nil attachments", DummyDefinition, [store: fn({%{__struct__: Plug.Upload, path: "/path/to/my/file.png", file_name: "file.png"}, %TestUser{}}) -> {:ok, "file.png"} end] do
changeset = TestUser.changeset(%TestUser{}, %{"avatar" => build_upload("/path/to/my/file.png")})
changeset = TestUser.changeset2(changeset, %{"avatar" => nil})
assert nil == Ecto.Changeset.get_field(changeset, :avatar)
end

test_with_mock "allow_paths => true", DummyDefinition, [store: fn({"/path/to/my/file.png", %TestUser{}}) -> {:ok, "file.png"} end] do
changeset = TestUser.path_changeset(%TestUser{}, %{"avatar" => "/path/to/my/file.png"})
assert called DummyDefinition.store({"/path/to/my/file.png", %TestUser{}})
end
end

0 comments on commit 9719139

Please sign in to comment.