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 support for array fields #41

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## v0.0.12 (2022-03-07)
- Add support for array fields

## v0.0.11 (2021-08-12)
- Update dependencies
- Allow custom error messages to be set on cast (#35)
Expand Down
2 changes: 1 addition & 1 deletion 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 Down
67 changes: 43 additions & 24 deletions lib/waffle_ecto/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -90,31 +90,50 @@ defmodule Waffle.Ecto.Schema do
def do_apply_changes(%{__meta__: _} = data), do: data

def check_and_apply_scope(params, scope, options) do
Enum.reduce(params, [], 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]

# Allow casting binary data structs
{field, upload = %{filename: filename, binary: binary}}, fields
when is_binary(filename) and is_binary(binary) ->
[{field, {upload, scope}} | fields]

{field, upload = %{filename: filename, path: path}}, fields
when is_binary(filename) and is_binary(path) ->
path = String.trim(path)
upload = %{upload | path: path}
if path_allowed?(path, options), do: [{field, {upload, scope}} | fields], else: fields

# If casting a binary (path), ensure we've explicitly allowed paths
{field, path}, fields when is_binary(path) ->
path = String.trim(path)
if path_allowed?(path, options), do: [{field, {path, scope}} | fields], else: fields
params
|> Enum.reduce([], fn {field, value}, fields ->
[{field, apply_scope(value, scope, options)} | fields]
end)
|> Enum.reject(fn
{_field, :invalid} -> true
{_field, values} when is_list(values) -> Enum.any?(values, & &1 == :invalid)
_else -> false
end)
end

# Don't wrap nil casts in the scope object
def apply_scope(nil, _scope, _options) do
nil
end

def apply_scope(values, scope, options) when is_list(values) do
Enum.map(values, & apply_scope(&1, scope, options))
end

# Allow casting Plug.Uploads
def apply_scope(%{__struct__: Plug.Upload} = upload, scope, _options) do
{upload, scope}
end

# Allow casting binary data structs
def apply_scope(%{filename: filename, binary: binary} = upload, scope, _options) when is_binary(filename) and is_binary(binary) do
{upload, scope}
end

# If casting a binary (path), ensure we've explicitly allowed paths
def apply_scope(%{filename: filename, path: path} = upload, scope, options) when is_binary(filename) and is_binary(path) do
path = String.trim(path)

if path_allowed?(path, options) do
{%{upload | path: path}, scope}
else
:invalid
end
end

def apply_scope(path, scope, options) when is_binary(path) do
path = String.trim(path)
if path_allowed?(path, options), do: {path, scope}, else: :invalid
end

defp path_allowed?(path, options) do
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Waffle.Ecto.Mixfile do
use Mix.Project

@version "0.0.11"
@version "0.0.12"

def project do
[app: :waffle_ecto,
Expand Down
26 changes: 26 additions & 0 deletions test/schema_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ defmodule WaffleTest.Ecto.Schema do
schema "users" do
field(:first_name, :string)
field(:avatar, DummyDefinition.Type)
field(:images, {:array, DummyDefinition.Type})
end

def changeset(user, params \\ :invalid) do
Expand Down Expand Up @@ -39,6 +40,13 @@ defmodule WaffleTest.Ecto.Schema do
|> cast(params, ~w(first_name)a)
|> cast_attachments(params, ~w(avatar)a)
end

def images_changeset(user, params \\ :invalid) do
user
|> cast(params, ~w(first_name)a)
|> cast_attachments(params, ~w(images)a)
|> validate_required(:images)
end
end

def build_upload(path) do
Expand All @@ -63,6 +71,24 @@ defmodule WaffleTest.Ecto.Schema do
%{file_name: "file.png", updated_at: _} = cs.changes.avatar
end

test_with_mock "cascades storage success with an array", DummyDefinition,
store: fn {%{__struct__: Plug.Upload, path: "/path/to/my/file.png", filename: "file.png"},
%TestUser{}} ->
{:ok, "file.png"}
end do
upload1 = build_upload("/path/to/my/file.png")
upload2 = build_upload("/path/to/my/file.png")

cs = TestUser.images_changeset(%TestUser{}, %{"images" => [upload1, upload2]})

assert cs.valid?

[
%{file_name: "file.png", updated_at: _},
%{file_name: "file.png", updated_at: _}
] = cs.changes.images
end

test_with_mock "cascades storage error into an error", DummyDefinition,
store: fn {%{__struct__: Plug.Upload, path: "/path/to/my/file.png", filename: "file.png"},
%TestUser{}} ->
Expand Down