diff --git a/lib/arc_ecto/type.ex b/lib/arc_ecto/type.ex index 98f7115..c729394 100644 --- a/lib/arc_ecto/type.ex +++ b/lib/arc_ecto/type.ex @@ -1,39 +1,34 @@ defmodule Arc.Ecto.Type do def type, do: :string - @filename_with_timestamp ~r{^(.*)\?(\d+)$} + @filename_with_timestamp ~r{^(.*)\?(\d+)(&|$)} + + @saved_versions ~r/(files=)(.*)?&?/ # Support embeds_one/embeds_many def cast(_definition, %{"file_name" => file, "updated_at" => updated_at}) do {:ok, %{file_name: file, updated_at: updated_at}} end + + def cast(_definition, %{"file_name" => file, "updated_at" => updated_at, "saved_versions" => saved_versions}) do + {:ok, %{file_name: file, updated_at: updated_at, saved_versions: saved_versions}} + end + def cast(definition, args) do case definition.store(args) do - {:ok, file} -> {:ok, %{file_name: file, updated_at: Ecto.DateTime.utc}} + {:ok, file, saved_versions} -> {:ok, %{file_name: file, updated_at: Ecto.DateTime.utc, saved_versions: saved_versions}} _ -> :error end end def load(_definition, value) do - {file_name, gsec} = - case Regex.match?(@filename_with_timestamp, value) do - true -> - [_, file_name, gsec] = Regex.run(@filename_with_timestamp, value) - {file_name, gsec} - _ -> {value, nil} - end - - updated_at = case gsec do - gsec when is_binary(gsec) -> - gsec - |> String.to_integer() - |> :calendar.gregorian_seconds_to_datetime() - |> Ecto.DateTime.from_erl() - _ -> - nil + {file_name, gsec} = filename_from(value) + updated_at = updated_time_from(gsec) + saved_versions = saved_versions_from(value) + case is_nil(saved_versions) do + true -> {:ok, %{file_name: file_name, updated_at: updated_at}} + false -> {:ok, %{file_name: file_name, updated_at: updated_at, saved_versions: saved_versions}} end - - {:ok, %{file_name: file_name, updated_at: updated_at}} end def dump(_definition, %{file_name: file_name, updated_at: nil}) do @@ -44,4 +39,43 @@ defmodule Arc.Ecto.Type do gsec = :calendar.datetime_to_gregorian_seconds(Ecto.DateTime.to_erl(updated_at)) {:ok, "#{file_name}?#{gsec}"} end + + def dump(_definition, %{file_name: file_name, updated_at: updated_at, saved_versions: nil}) do + gsec = :calendar.datetime_to_gregorian_seconds(Ecto.DateTime.to_erl(updated_at)) + {:ok, "#{file_name}?#{gsec}"} + end + + def dump(_definition, %{file_name: file_name, updated_at: updated_at, saved_versions: versions}) do + gsec = :calendar.datetime_to_gregorian_seconds(Ecto.DateTime.to_erl(updated_at)) + {:ok, "#{file_name}?#{gsec}&files=#{versions}"} + end + + defp filename_from(value) do + case Regex.match?(@filename_with_timestamp, value) do + true -> + [_, file_name, gsec, _] = Regex.run(@filename_with_timestamp, value) + {file_name, gsec} + _ -> {value, nil} + end + end + + defp saved_versions_from(value) do + case Regex.match?(@saved_versions, value) do + true -> + [_, _, val] = Regex.run(@saved_versions, value) + String.split(val, "::") + _ -> nil + end + end + + defp updated_time_from(gsec) do + case gsec do + gsec when is_binary(gsec) -> + gsec + |> String.to_integer() + |> :calendar.gregorian_seconds_to_datetime() + |> Ecto.DateTime.from_erl() + _ -> nil + end + end end diff --git a/test/schema_test.exs b/test/schema_test.exs index 86f71a1..1e0aa1c 100644 --- a/test/schema_test.exs +++ b/test/schema_test.exs @@ -58,7 +58,7 @@ defmodule ArcTest.Ecto.Schema do assert cs.errors == [avatar: {"can't be blank", []}] end - 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 + 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", "file.png"} end] do upload = build_upload("/path/to/my/file.png") cs = TestUser.changeset(%TestUser{}, %{"avatar" => upload}) assert cs.valid? diff --git a/test/type_test.exs b/test/type_test.exs index 4f5a7ef..f1e96e1 100644 --- a/test/type_test.exs +++ b/test/type_test.exs @@ -25,6 +25,18 @@ defmodule ArcTest.Ecto.Type do assert value == %{file_name: "image.php", updated_at: timestamp} end + test "loads filenames with an output file embedded" do + timestamp = Ecto.DateTime.cast!({{1970, 1, 1}, {0, 0, 0}}) + {:ok, value} = DummyDefinition.Type.load("image.php?62167219200&files=image.png") + assert value == %{file_name: "image.php", updated_at: timestamp, saved_versions: ["image.png"]} + end + + test "loads filenames with multiple output files embedded" do + timestamp = Ecto.DateTime.cast!({{1970, 1, 1}, {0, 0, 0}}) + {:ok, value} = DummyDefinition.Type.load("image.php?62167219200&files=image.png::image2.png::image3.png") + assert value == %{file_name: "image.php", updated_at: timestamp, saved_versions: ["image.png", "image2.png", "image3.png"]} + end + test "loads pathological filenames without timestamps" do {:ok, value} = DummyDefinition.Type.load("image^!?*!=@$.php") assert value == %{file_name: "image^!?*!=@$.php", updated_at: nil}