From 077a75d73df70d3470fc52778a68c6372bc50051 Mon Sep 17 00:00:00 2001 From: Jean Parpaillon Date: Thu, 14 Feb 2019 14:09:23 +0100 Subject: [PATCH 01/10] Adapt to Ecto 3.x --- config/test.exs | 3 +- lib/ecto_poly.ex | 133 ++++++++++++++++---------------------- mix.exs | 20 +++--- mix.lock | 25 +++---- test/ecto_poly_test.exs | 97 ++++++++++++++++----------- test/support/repo.ex | 4 +- test/support/test_case.ex | 4 +- 7 files changed, 146 insertions(+), 140 deletions(-) diff --git a/config/test.exs b/config/test.exs index ae88b7e..26394fd 100644 --- a/config/test.exs +++ b/config/test.exs @@ -5,8 +5,7 @@ config :ecto_poly, ecto_repos: [EctoPoly.TestRepo] config :ecto_poly, EctoPoly.TestRepo, adapter: Ecto.Adapters.Postgres, username: "postgres", - password: "mysecretpassword", + password: "postgres", database: "postgres", hostname: "postgres", pool: Ecto.Adapters.SQL.Sandbox - diff --git a/lib/ecto_poly.ex b/lib/ecto_poly.ex index 5b36820..11e16d5 100644 --- a/lib/ecto_poly.ex +++ b/lib/ecto_poly.ex @@ -5,7 +5,7 @@ defmodule EctoPoly do @doc """ # Arguments - + * `types`: Keyword list of `{name, type}`. The `name` is stored in the database in order to identify what `type` to use in runtime. * `type_field`: Name of the field used to the type of that particular object. Default is `:__type__`. @@ -21,11 +21,11 @@ defmodule EctoPoly do defmacro __using__(opts) do env = __CALLER__ - type_field = + type_field = opts |> Keyword.get(:type_field, :__type__) |> Macro.expand(env) - |> Atom.to_string + |> Atom.to_string() types = opts @@ -53,8 +53,8 @@ defmodule EctoPoly do name = data |> Map.get(@type_field) - |> String.to_existing_atom - + |> String.to_existing_atom() + fields = data |> Map.delete(@type_field) @@ -99,17 +99,19 @@ defmodule EctoPoly do |> is_schema? |> loader(name, value_type) end + defp loader(true, name, value_type) do quote do defp load(unquote(name), fields) do result = unquote(value_type) - |> Ecto.Schema.__unsafe_load__(fields |> Map.new, &EctoPoly.load_value/2) + |> Ecto.Schema.Loader.unsafe_load(fields |> Map.new(), &EctoPoly.load_value/2) {:ok, result} end end end + defp loader(false, name, value_type) do quote do defp load(unquote(name), fields) do @@ -125,29 +127,32 @@ defmodule EctoPoly do |> is_schema? |> dumper(name, value_type) end + defp dumper(true, name, value_type) do quote do def dump(value = %unquote(value_type){}) do - embed_type = {:embed, %Ecto.Embedded{ - cardinality: :one, - related: unquote(value_type), - field: :data, - }} + embed_type = + {:embed, + %Ecto.Embedded{ + cardinality: :one, + related: unquote(value_type), + field: :data + }} with {:ok, result} <- Ecto.Type.dump(embed_type, value, &EctoPoly.dump_value/2), - result = result |> Map.put(@type_field, Atom.to_string(unquote(name))) - do + result = result |> Map.put(@type_field, Atom.to_string(unquote(name))) do {:ok, result} end end end end + defp dumper(false, name, value_type) do quote do def dump(value = %unquote(value_type){}) do result = value - |> Map.from_struct + |> Map.from_struct() |> Map.put(@type_field, Atom.to_string(unquote(name))) {:ok, result} @@ -157,10 +162,11 @@ defmodule EctoPoly do defp build_union_type(types) do types - |> Enum.reduce(nil, fn (x, acc) -> + |> Enum.reduce(nil, fn x, acc -> case acc do nil -> x + value -> {:|, [], [value, x]} end @@ -179,12 +185,12 @@ defmodule EctoPoly do @doc false def dump_value(type, value) do with {:ok, value} <- Ecto.Type.dump(type, value, &dump_value/2), - {:ok, value} <- transform_dump(type, value) - do + {:ok, value} <- transform_dump(type, value) do {:ok, value} else {:error, error} -> {:error, error} + :error -> :error end @@ -193,12 +199,12 @@ defmodule EctoPoly do @doc false def load_value(type, value) do with {:ok, value} <- transform_load(type, value), - {:ok, value} <- Ecto.Type.load(type, value, &load_value/2) - do + {:ok, value} <- Ecto.Type.load(type, value, &load_value/2) do {:ok, value} else {:error, error} -> {:error, error} + :error -> :error end @@ -207,75 +213,48 @@ defmodule EctoPoly do defp transform_dump(type, value), do: do_transform_dump(Ecto.Type.type(type), value) defp do_transform_dump(_, nil), do: {:ok, nil} defp do_transform_dump(:decimal, value), do: {:ok, Decimal.to_string(value)} - defp do_transform_dump(:time, {hour, minute, second, microsecond}) do - result = - %Time{hour: hour, minute: minute, second: second, microsecond: {microsecond, 6}} - |> Time.to_iso8601 - {:ok, result} - end - defp do_transform_dump(:naive_datetime, {{year, month, day}, {hour, minute, second, microsecond}}) do - result = - %NaiveDateTime{year: year, month: month, day: day, - hour: hour, minute: minute, second: second, microsecond: {microsecond, 6}} - |> NaiveDateTime.to_iso8601 + defp do_transform_dump(:time, %Time{} = t), do: {:ok, t} - {:ok, result} - end - defp do_transform_dump(:utc_datetime, {{year, month, day}, {hour, minute, second, microsecond}}) do - result = - %DateTime{year: year, month: month, day: day, - hour: hour, minute: minute, second: second, microsecond: {microsecond, 6}, - std_offset: 0, utc_offset: 0, zone_abbr: "UTC", time_zone: "Etc/UTC"} - |> DateTime.to_iso8601 - - {:ok, result} - end - defp do_transform_dump(:date, {year, month, day}) do - result = - %Date{year: year, month: month, day: day} - |> Date.to_iso8601 + defp do_transform_dump(:time_usec, %Time{} = t), do: {:ok, t} + + defp do_transform_dump(:naive_datetime, %NaiveDateTime{} = dt), do: {:ok, dt} + + defp do_transform_dump(:naive_datetime_usec, %NaiveDateTime{} = dt), do: {:ok, dt} + + defp do_transform_dump(:utc_datetime, %DateTime{} = dt), do: {:ok, dt} + + defp do_transform_dump(:utc_datetime_usec, %DateTime{} = dt), do: {:ok, dt} + + defp do_transform_dump(:date, %Date{} = d), do: {:ok, d} - {:ok, result} - end defp do_transform_dump(_, value), do: {:ok, value} - defp transform_load(type, value), do: do_transform_load(Ecto.Type.type(type), value) + def transform_load(type, value), do: do_transform_load(Ecto.Type.type(type), value) defp do_transform_load(_, nil), do: {:ok, nil} defp do_transform_load(:decimal, value), do: {:ok, Decimal.new(value)} - defp do_transform_load(:time, value) do - with {:ok, %{ - hour: hour, minute: minute, second: second, microsecond: {microsecond, 6} - }} = value |> Time.from_iso8601 - do - {:ok, {hour, minute, second, microsecond}} - end - end - defp do_transform_load(:naive_datetime, value) do - with {:ok, %{ - year: year, month: month, day: day, - hour: hour, minute: minute, second: second, microsecond: {microsecond, 6} - }} = value |> NaiveDateTime.from_iso8601 - do - {:ok, {{year, month, day}, {hour, minute, second, microsecond}}} - end - end + + defp do_transform_load(:time, value), do: value |> Time.from_iso8601() + + defp do_transform_load(:time_usec, value), do: value |> Time.from_iso8601() + + defp do_transform_load(:naive_datetime, value), do: value |> NaiveDateTime.from_iso8601() + + defp do_transform_load(:naive_datetime_usec, value), do: value |> NaiveDateTime.from_iso8601() + defp do_transform_load(:utc_datetime, value) do - with {:ok, %{ - year: year, month: month, day: day, - hour: hour, minute: minute, second: second, microsecond: {microsecond, 6} - }, _} <- value |> DateTime.from_iso8601 - do - {:ok, {{year, month, day}, {hour, minute, second, microsecond}}} + with {:ok, dt, _} <- value |> DateTime.from_iso8601() do + {:ok, dt} end end - defp do_transform_load(:date, value) do - with {:ok, %{ - year: year, month: month, day: day, - }, _} <- value |> Date.from_iso8601 - do - {:ok, {year, month, day}} + + defp do_transform_load(:utc_datetime_usec, value) do + with {:ok, dt, _} <- value |> DateTime.from_iso8601() do + {:ok, dt} end end + + defp do_transform_load(:date, value), do: value |> Date.from_iso8601() + defp do_transform_load(_, value), do: {:ok, value} end diff --git a/mix.exs b/mix.exs index 022fb91..171238e 100644 --- a/mix.exs +++ b/mix.exs @@ -11,8 +11,8 @@ defmodule EctoPoly.Mixfile do version: @version, elixir: "~> 1.5", app: :ecto_poly, - start_permanent: Mix.env == :prod, - elixirc_paths: elixirc_paths(Mix.env), + start_permanent: Mix.env() == :prod, + elixirc_paths: elixirc_paths(Mix.env()), package: package(), aliases: aliases(), deps: deps(), @@ -26,14 +26,18 @@ defmodule EctoPoly.Mixfile do defp deps do [ - {:ecto, "~> 2.0"}, - {:postgrex, "~> 0.11"}, - {:poison, "~> 3.0"}, + {:ecto, ">= 2.0.0"}, + {:postgrex, "~> 0.11", only: [:dev, :test]}, + {:jason, "~> 1.1"}, {:ex_doc, "~> 0.19", only: :docs}, - {:inch_ex, ">= 0.0.0", only: :docs}, - ] + {:inch_ex, ">= 0.0.0", only: :docs} + ] ++ deps(Mix.env()) end + defp deps(env) when env in [:dev, :test], do: [{:ecto_sql, "~> 3.0"}] + + defp deps(_), do: [{:ecto, "~> 3.0"}] + defp package do [ licenses: ["MIT"], @@ -56,5 +60,5 @@ defmodule EctoPoly.Mixfile do end defp elixirc_paths(:test), do: ["lib", "test/support"] - defp elixirc_paths(_), do: ["lib"] + defp elixirc_paths(_), do: ["lib"] end diff --git a/mix.lock b/mix.lock index 91da1ce..1e5b40d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,15 +1,18 @@ %{ + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, - "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, - "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, - "earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"}, - "ecto": {:hex, :ecto, "2.2.10", "e7366dc82f48f8dd78fcbf3ab50985ceeb11cb3dc93435147c6e13f2cda0992e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, - "inch_ex": {:hex, :inch_ex, "1.0.1", "1f0af1a83cec8e56f6fc91738a09c838e858db3d78ef5f2ec040fe4d5a62dabf", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, - "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"}, + "db_connection": {:hex, :db_connection, "2.0.5", "ddb2ba6761a08b2bb9ca0e7d260e8f4dd39067426d835c24491a321b7f92a4da", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, + "decimal": {:hex, :decimal, "1.6.0", "bfd84d90ff966e1f5d4370bdd3943432d8f65f07d3bab48001aebd7030590dcc", [:mix], [], "hexpm"}, + "earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm"}, + "ecto": {:hex, :ecto, "3.0.7", "44dda84ac6b17bbbdeb8ac5dfef08b7da253b37a453c34ab1a98de7f7e5fec7f", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, + "ecto_sql": {:hex, :ecto_sql, "3.0.5", "7e44172b4f7aca4469f38d7f6a3da394dbf43a1bcf0ca975e958cb957becd74e", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.6", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.19.3", "3c7b0f02851f5fc13b040e8e925051452e41248f685e40250d7e40b07b9f8c10", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, + "inch_ex": {:hex, :inch_ex, "2.0.0", "24268a9284a1751f2ceda569cd978e1fa394c977c45c331bb52a405de544f4de", [:mix], [{:bunt, "~> 0.2", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, + "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, + "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, - "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, - "postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, + "postgrex": {:hex, :postgrex, "0.14.1", "63247d4a5ad6b9de57a0bac5d807e1c32d41e39c04b8a4156a26c63bcd8a2e49", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, + "telemetry": {:hex, :telemetry, "0.3.0", "099a7f3ce31e4780f971b4630a3c22ec66d22208bc090fe33a2a3a6a67754a73", [:rebar3], [], "hexpm"}, } diff --git a/test/ecto_poly_test.exs b/test/ecto_poly_test.exs index 4a72cd7..1948eec 100644 --- a/test/ecto_poly_test.exs +++ b/test/ecto_poly_test.exs @@ -12,11 +12,11 @@ defmodule EctoPolyTest do |> TestSchema.changeset(%{ channel: %TestEmailChannel{email: "foo"} }) - |> TestRepo.insert! + |> TestRepo.insert!() assert result.channel == %TestEmailChannel{ - email: "foo", - } + email: "foo" + } end test "when loading" do @@ -25,44 +25,56 @@ defmodule EctoPolyTest do |> TestSchema.changeset(%{ channel: %TestEmailChannel{email: "foo"} }) - |> TestRepo.insert! + |> TestRepo.insert!() - result = TestRepo.one(from o in TestSchema, where: o.id == ^obj.id) + result = TestRepo.one(from(o in TestSchema, where: o.id == ^obj.id)) assert %TestSchema{ - channel: %TestEmailChannel{ - email: "foo", - }, - } = result + channel: %TestEmailChannel{ + email: "foo" + } + } = result end test "when querying" do - value = :rand.uniform |> Float.to_string + value = :rand.uniform() |> Float.to_string() %TestSchema{} |> TestSchema.changeset(%{ channel: %TestEmailChannel{email: value} }) - |> TestRepo.insert! + |> TestRepo.insert!() - result = TestRepo.one(from o in TestSchema, where: fragment("?->>'__type__' = ?", o.channel, "email") and fragment("?->>'email' = ?", o.channel, ^value)) + result = + TestRepo.one( + from(o in TestSchema, + where: + fragment("?->>'__type__' = ?", o.channel, "email") and + fragment("?->>'email' = ?", o.channel, ^value) + ) + ) assert %TestSchema{ - channel: %TestEmailChannel{ - email: ^value, - }, - } = result + channel: %TestEmailChannel{ + email: ^value + } + } = result end end describe "with schema" do test "when saving and loading" do - date = DateTime.utc_now() - dates = [NaiveDateTime.utc_now(), NaiveDateTime.utc_now()] + date = DateTime.utc_now() |> DateTime.truncate(:second) + + dates = + [NaiveDateTime.utc_now(), NaiveDateTime.utc_now()] + |> Enum.map(&NaiveDateTime.truncate(&1, :second)) + day = Date.utc_today() + time_by_name = %{ - "lol" => Time.utc_now(), - "wtf" => Time.utc_now(), + "lol" => Time.utc_now() |> Time.truncate(:second), + "wtf" => Time.utc_now() |> Time.truncate(:second) } result = @@ -77,13 +89,13 @@ defmodule EctoPolyTest do dates: dates, the_day: day, time_by_name: time_by_name, - price: Decimal.new(10.00), + price: Decimal.from_float(10.00) } - }, + } }) - |> TestRepo.insert! + |> TestRepo.insert!() - loaded = TestRepo.one(from o in TestSchema, where: o.id == ^result.id) + loaded = TestRepo.one(from(o in TestSchema, where: o.id == ^result.id)) expected = %TestSmsChannel{ number: "+11234567890", @@ -94,7 +106,7 @@ defmodule EctoPolyTest do dates: dates, the_day: day, time_by_name: time_by_name, - price: Decimal.new(10.00), + price: Decimal.from_float(10.00) } } @@ -103,7 +115,7 @@ defmodule EctoPolyTest do end test "when querying" do - value = :rand.uniform |> Float.to_string + value = :rand.uniform() |> Float.to_string() %TestSchema{} |> TestSchema.changeset(%{ @@ -111,23 +123,30 @@ defmodule EctoPolyTest do number: value, provider: %TwilioSmsProvider{ key_id: "id", - key_secret: "secret", - }, - }, + key_secret: "secret" + } + } }) - |> TestRepo.insert! + |> TestRepo.insert!() - result = TestRepo.one(from o in TestSchema, where: fragment("?->>'__type__' = ?", o.channel, "sms") and fragment("?->>'number' = ?", o.channel, ^value)) + result = + TestRepo.one( + from(o in TestSchema, + where: + fragment("?->>'__type__' = ?", o.channel, "sms") and + fragment("?->>'number' = ?", o.channel, ^value) + ) + ) assert %TestSchema{ - channel: %TestSmsChannel{ - number: ^value, - provider: %TwilioSmsProvider{ - key_id: "id", - key_secret: "secret", - } - } - } = result + channel: %TestSmsChannel{ + number: ^value, + provider: %TwilioSmsProvider{ + key_id: "id", + key_secret: "secret" + } + } + } = result end end diff --git a/test/support/repo.ex b/test/support/repo.ex index 1ddfa42..c5daf4e 100644 --- a/test/support/repo.ex +++ b/test/support/repo.ex @@ -1,3 +1,5 @@ defmodule EctoPoly.TestRepo do - use Ecto.Repo, otp_app: :ecto_poly + use Ecto.Repo, + otp_app: :ecto_poly, + adapter: Ecto.Adapters.Postgres end diff --git a/test/support/test_case.ex b/test/support/test_case.ex index 3a20c01..174021d 100644 --- a/test/support/test_case.ex +++ b/test/support/test_case.ex @@ -3,7 +3,7 @@ defmodule EctoPoly.TestCase do setup tags do ensure_started!() - + :ok = Ecto.Adapters.SQL.Sandbox.checkout(EctoPoly.TestRepo) unless tags[:async] do @@ -14,6 +14,6 @@ defmodule EctoPoly.TestCase do end defp ensure_started!() do - Mix.Ecto.ensure_started(EctoPoly.TestRepo, []) + Mix.EctoSQL.ensure_started(EctoPoly.TestRepo, []) end end From 991dc79058aa2f2e0c28a9d32a105b8de3f21a93 Mon Sep 17 00:00:00 2001 From: Jean Parpaillon Date: Thu, 14 Feb 2019 14:09:55 +0100 Subject: [PATCH 02/10] Ignore elixir_ls files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 12179ea..15f7210 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ erl_crash.dump # Also ignore archive artifacts (built via "mix archive.build"). *.ez + +/.elixir_ls From 0fea0df4d14db7888a02d40edc23ac19edb46da5 Mon Sep 17 00:00:00 2001 From: Jean Parpaillon Date: Fri, 8 Mar 2019 14:53:20 +0100 Subject: [PATCH 03/10] Add .formatter.exs --- .formatter.exs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .formatter.exs diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..4452707 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,50 @@ +[ + inputs: [ + "apps/*/{config,lib,priv,test}/**/*.{ex,exs}", + "apps/*/mix.exs", + "mix.exs", + ".formatter.exs", + ".iex.exs" + ], + locals_without_parens: [ + add: :*, + arg: :*, + belongs_to: :*, + can: :*, + config: :*, + create: :*, + description: :*, + embeds_many: :*, + embeds_one: :*, + field: :*, + forward: :*, + gen_enum: :*, + get: :*, + has_many: :*, + has_one: :*, + import_fields: :*, + import_types: :*, + interface: :*, + many_to_many: :*, + middleware: :*, + named: :*, + payload_object: :*, + pipe_through: :*, + plug: 1, + policy: :*, + reload: :*, + render_error: :*, + resolve_type: :*, + resolve: :*, + response: :*, + serialize: :*, + socket: :*, + store_named: :*, + trigger: :*, + types: :*, + value: :*, + whereis_name: :*, + whereis: :*, + set: :* + ] +] From eba312f46c5740c9421c106132e2f10fa3575f17 Mon Sep 17 00:00:00 2001 From: Jean Parpaillon Date: Fri, 8 Mar 2019 14:53:24 +0100 Subject: [PATCH 04/10] Add EctoPoly.cast/3 function: cast given field as given EctoPoly type See #3 --- lib/ecto_poly.ex | 109 +++++++++++++++++++++++++++++++ test/ecto_poly_test.exs | 81 +++++++++++++++++++++++ test/support/test_sms_channel.ex | 12 ++++ 3 files changed, 202 insertions(+) diff --git a/lib/ecto_poly.ex b/lib/ecto_poly.ex index 11e16d5..0f4eaf6 100644 --- a/lib/ecto_poly.ex +++ b/lib/ecto_poly.ex @@ -3,6 +3,8 @@ defmodule EctoPoly do Creates a polymorphic embedded type """ + alias Ecto.Changeset + @doc """ # Arguments @@ -43,12 +45,16 @@ defmodule EctoPoly do @type t :: unquote(union_type) + alias Ecto.Changeset + def type, do: :map EctoPoly.__casters__(unquote(types)) EctoPoly.__dumpers__(unquote(types)) EctoPoly.__loaders__(unquote(types)) + def __types__, do: unquote(types) + def load(data) when is_map(data) do name = data @@ -88,6 +94,31 @@ defmodule EctoPoly do |> Enum.map(&loader/1) end + @doc """ + Casts the given poly with the changeset parameters. + """ + @spec cast(Changeset.t(), atom, atom) :: Changeset.t() + def cast(%Changeset{data: data, types: types}, _key, _typename) + when data == nil or types == nil do + raise ArgumentError, + "cast/2 expects the changeset to be cast. " <> + "Please call cast/4 before calling cast/2" + end + + def cast(%Changeset{params: params, types: types} = changeset, key, typename) do + case types[key] do + nil -> + raise ArgumentError, "invalid field: #{key}" + + poly -> + {key, param_key} = cast_key(key) + do_cast(changeset, key, params[param_key], poly, typename) + end + end + + ### + ### Priv + ### defp caster({_, value_type}) do quote do def cast(value = %unquote(value_type){}), do: {:ok, value} @@ -257,4 +288,82 @@ defmodule EctoPoly do defp do_transform_load(:date, value), do: value |> Date.from_iso8601() defp do_transform_load(_, value), do: {:ok, value} + + def cast_key(key) when is_atom(key) do + {key, Atom.to_string(key)} + end + + defp do_cast(changeset, _, nil, _, _), do: changeset + + defp do_cast(changeset, key, param, poly, typename) do + case poly.cast(param) do + {:ok, value} -> + Changeset.put_change(changeset, key, value) + + :error -> + type = + poly.__types__ + |> Enum.find(fn + {^typename, _type} -> true + {_, ^typename} -> true + _ -> false + end) + |> case do + nil -> nil + {_, module} -> module + end + + do_changeset(changeset, key, param, type, typename) + end + end + + defp do_changeset(_changeset, _key, _param, nil, typename) do + raise ArgumentError, "invalid type: #{typename}" + end + + defp do_changeset(changeset, key, param, module, _typename) do + %Changeset{changes: changes, data: data} = changeset + on_cast = on_cast_default(module) + original = Map.get(data, key) + + struct = + case original do + nil -> struct(module) + _ -> original + end + + {change, valid?} = + case on_cast.(struct, param) do + %Changeset{valid?: false} = change -> + {change, false} + + change -> + {Changeset.apply_changes(change), changeset.valid?} + end + + %{changeset | changes: Map.put(changes, key, change), valid?: valid?} + end + + defp on_cast_default(module) do + fn struct, param -> + try do + module.changeset(struct, param) + rescue + e in UndefinedFunctionError -> + case System.stacktrace() do + [{^module, :changeset, args_or_arity, _} | _] + when args_or_arity == 2 + when length(args_or_arity) == 2 -> + raise ArgumentError, """ + the module #{inspect(module)} does not define a changeset/2 + function, which is used by EctoPoly.cast/3. You need to + implement the #{module}.changeset/2 function. + """ + + stacktrace -> + reraise e, stacktrace + end + end + end + end end diff --git a/test/ecto_poly_test.exs b/test/ecto_poly_test.exs index 1948eec..1ce73af 100644 --- a/test/ecto_poly_test.exs +++ b/test/ecto_poly_test.exs @@ -3,6 +3,7 @@ defmodule EctoPolyTest do use EctoPoly.TestCase + alias Ecto.Changeset alias EctoPoly.{TestRepo, TestSchema, TestEmailChannel, TestSmsChannel, TwilioSmsProvider} describe "with simple struct" do @@ -160,4 +161,84 @@ defmodule EctoPolyTest do refute changeset.valid? assert [channel: {"is invalid", _}] = changeset.errors end + + describe "cast_poly" do + test "not a poly type" do + params = %{ + channel: %{ + number: "0123456789" + } + } + + assert_raise ArgumentError, "invalid type: unknown", fn -> + %TestSchema{} + |> Changeset.cast(params, []) + |> EctoPoly.cast(:channel, :unknown) + end + end + + test "valid by name" do + number = "0123456789" + + params = %{ + channel: %{ + number: number + } + } + + result = + %TestSchema{} + |> Changeset.cast(params, []) + |> EctoPoly.cast(:channel, :sms) + |> TestRepo.insert!() + + assert match?(%TestSchema{channel: %TestSmsChannel{number: ^number}}, result) + end + + test "valid by type" do + number = "0123456789" + + params = %{ + channel: %{ + number: number + } + } + + result = + %TestSchema{} + |> Changeset.cast(params, []) + |> EctoPoly.cast(:channel, TestSmsChannel) + |> TestRepo.insert!() + + assert match?(%TestSchema{channel: %TestSmsChannel{number: ^number}}, result) + end + + test "invalid changeset" do + number = "123456789" + + params = %{ + channel: %{ + number: number + } + } + + result = + %TestSchema{} + |> Changeset.cast(params, []) + |> EctoPoly.cast(:channel, TestSmsChannel) + |> TestRepo.insert() + + assert match?( + {:error, + %Changeset{ + changes: %{ + channel: %Changeset{ + errors: [number: {"number must start with '0'", []}] + } + } + }}, + result + ) + end + end end diff --git a/test/support/test_sms_channel.ex b/test/support/test_sms_channel.ex index 7b29f49..e03cbce 100644 --- a/test/support/test_sms_channel.ex +++ b/test/support/test_sms_channel.ex @@ -1,8 +1,20 @@ defmodule EctoPoly.TestSmsChannel do use Ecto.Schema + alias Ecto.Changeset + embedded_schema do field :number, :string field :provider, EctoPoly.SmsProvider end + + def changeset(struct, params) do + struct + |> Changeset.change() + |> Changeset.cast(params, [:number]) + |> Changeset.validate_change(:number, fn + :number, "0" <> _rest -> [] + :number, _ -> [number: "number must start with '0'"] + end) + end end From b1d0d3ab1b758c95ce76e6171c49e0cc2ae833fb Mon Sep 17 00:00:00 2001 From: Jean Parpaillon Date: Thu, 27 Jun 2019 15:40:15 +0200 Subject: [PATCH 05/10] Add `with` option to `EctoPoly.cast/{2,3}` function See `Ecto.cast_assoc` doc for semantic --- lib/ecto_poly.ex | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/ecto_poly.ex b/lib/ecto_poly.ex index 0f4eaf6..410bb51 100644 --- a/lib/ecto_poly.ex +++ b/lib/ecto_poly.ex @@ -98,21 +98,24 @@ defmodule EctoPoly do Casts the given poly with the changeset parameters. """ @spec cast(Changeset.t(), atom, atom) :: Changeset.t() - def cast(%Changeset{data: data, types: types}, _key, _typename) + @spec cast(Changeset.t(), atom, atom, Keyword.t()) :: Changeset.t() + def cast(changes, key, typename, opts \\ []) + + def cast(%Changeset{data: data, types: types}, _key, _typename, _opts) when data == nil or types == nil do raise ArgumentError, "cast/2 expects the changeset to be cast. " <> "Please call cast/4 before calling cast/2" end - def cast(%Changeset{params: params, types: types} = changeset, key, typename) do + def cast(%Changeset{params: params, types: types} = changeset, key, typename, opts) do case types[key] do nil -> raise ArgumentError, "invalid field: #{key}" poly -> {key, param_key} = cast_key(key) - do_cast(changeset, key, params[param_key], poly, typename) + do_cast(changeset, key, params[param_key], poly, typename, opts) end end @@ -293,9 +296,9 @@ defmodule EctoPoly do {key, Atom.to_string(key)} end - defp do_cast(changeset, _, nil, _, _), do: changeset + defp do_cast(changeset, _, nil, _, _, _), do: changeset - defp do_cast(changeset, key, param, poly, typename) do + defp do_cast(changeset, key, param, poly, typename, opts) do case poly.cast(param) do {:ok, value} -> Changeset.put_change(changeset, key, value) @@ -313,17 +316,17 @@ defmodule EctoPoly do {_, module} -> module end - do_changeset(changeset, key, param, type, typename) + do_changeset(changeset, key, param, type, typename, opts) end end - defp do_changeset(_changeset, _key, _param, nil, typename) do + defp do_changeset(_changeset, _key, _param, nil, typename, _opts) do raise ArgumentError, "invalid type: #{typename}" end - defp do_changeset(changeset, key, param, module, _typename) do + defp do_changeset(changeset, key, param, module, _typename, opts) do %Changeset{changes: changes, data: data} = changeset - on_cast = on_cast_default(module) + on_cast = on_cast_fun(module, opts) original = Map.get(data, key) struct = @@ -344,6 +347,18 @@ defmodule EctoPoly do %{changeset | changes: Map.put(changes, key, change), valid?: valid?} end + defp on_cast_fun(module, opts) do + opts + |> Keyword.get(:with) + |> case do + nil -> + on_cast_default(module) + + fun -> + fun + end + end + defp on_cast_default(module) do fn struct, param -> try do From f3b97179afc236d80f903b17cc01c862f8ee92fe Mon Sep 17 00:00:00 2001 From: Pierre Martin Date: Mon, 30 Sep 2019 14:14:39 +0100 Subject: [PATCH 06/10] Uses Ecto.Poly instead of just @behaviour to obtain default implementations. --- lib/ecto_poly.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ecto_poly.ex b/lib/ecto_poly.ex index 410bb51..97e6d73 100644 --- a/lib/ecto_poly.ex +++ b/lib/ecto_poly.ex @@ -40,7 +40,8 @@ defmodule EctoPoly do union_type = build_union_type(types) quote do - @behaviour Ecto.Type + use Ecto.Type + @type_field unquote(type_field) @type t :: unquote(union_type) From a6784fb5f1d5781c0120a15ff1cb93e039779c5b Mon Sep 17 00:00:00 2001 From: Pierre Martin Date: Mon, 19 Oct 2020 13:37:58 +0100 Subject: [PATCH 07/10] Updates dependencies. --- mix.lock | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/mix.lock b/mix.lock index 1e5b40d..3199cd7 100644 --- a/mix.lock +++ b/mix.lock @@ -1,18 +1,17 @@ %{ - "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, - "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, - "db_connection": {:hex, :db_connection, "2.0.5", "ddb2ba6761a08b2bb9ca0e7d260e8f4dd39067426d835c24491a321b7f92a4da", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, - "decimal": {:hex, :decimal, "1.6.0", "bfd84d90ff966e1f5d4370bdd3943432d8f65f07d3bab48001aebd7030590dcc", [:mix], [], "hexpm"}, - "earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm"}, - "ecto": {:hex, :ecto, "3.0.7", "44dda84ac6b17bbbdeb8ac5dfef08b7da253b37a453c34ab1a98de7f7e5fec7f", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, - "ecto_sql": {:hex, :ecto_sql, "3.0.5", "7e44172b4f7aca4469f38d7f6a3da394dbf43a1bcf0ca975e958cb957becd74e", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.6", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.19.3", "3c7b0f02851f5fc13b040e8e925051452e41248f685e40250d7e40b07b9f8c10", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, - "inch_ex": {:hex, :inch_ex, "2.0.0", "24268a9284a1751f2ceda569cd978e1fa394c977c45c331bb52a405de544f4de", [:mix], [{:bunt, "~> 0.2", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, - "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, - "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, - "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, - "postgrex": {:hex, :postgrex, "0.14.1", "63247d4a5ad6b9de57a0bac5d807e1c32d41e39c04b8a4156a26c63bcd8a2e49", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, - "telemetry": {:hex, :telemetry, "0.3.0", "099a7f3ce31e4780f971b4630a3c22ec66d22208bc090fe33a2a3a6a67754a73", [:rebar3], [], "hexpm"}, + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, + "db_connection": {:hex, :db_connection, "2.3.0", "d56ef906956a37959bcb385704fc04035f4f43c0f560dd23e00740daf8028c49", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "dcc082b8f723de9a630451b49fdbd7a59b065c4b38176fb147aaf773574d4520"}, + "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"}, + "ecto": {:hex, :ecto, "3.5.2", "4e2c15b117a0e9918860cd1859bfa1791c587b78fca812bc8d373e0498d1f828", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fd92cfb1e300dd8093e7607158760f734147dc9cc4a60743573e0ec137a7df11"}, + "ecto_sql": {:hex, :ecto_sql, "3.5.1", "7c03f302caa3c2bbc4f5397281a5d0d8653f246d47c353e3cd46750b16ad310c", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.5.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "338150ecb2398f013f98e4a70d5413b70fed4b6383d4f7c400314d315cdf87a9"}, + "ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"}, + "inch_ex": {:hex, :inch_ex, "2.0.0", "24268a9284a1751f2ceda569cd978e1fa394c977c45c331bb52a405de544f4de", [:mix], [{:bunt, "~> 0.2", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "96d0ec5ecac8cf63142d02f16b7ab7152cf0f0f1a185a80161b758383c9399a8"}, + "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, + "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.15.0", "98312c9f0d3730fde4049985a1105da5155bfe5c11e47bdc7406d88e01e4219b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "75ffa34ab1056b7e24844c90bfc62aaf6f3a37a15faa76b07bc5eba27e4a8b4a"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, + "postgrex": {:hex, :postgrex, "0.15.7", "724410acd48abac529d0faa6c2a379fb8ae2088e31247687b16cacc0e0883372", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "88310c010ff047cecd73d5ceca1d99205e4b1ab1b9abfdab7e00f5c9d20ef8f9"}, + "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, } From baad070743eb9dbb7d7a8683a88e115fc47b31d7 Mon Sep 17 00:00:00 2001 From: Pierre Martin Date: Mon, 19 Oct 2020 13:38:21 +0100 Subject: [PATCH 08/10] Adds a helper module with a dependency version check function. --- lib/ecto_poly/helpers.ex | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 lib/ecto_poly/helpers.ex diff --git a/lib/ecto_poly/helpers.ex b/lib/ecto_poly/helpers.ex new file mode 100644 index 0000000..1b01f60 --- /dev/null +++ b/lib/ecto_poly/helpers.ex @@ -0,0 +1,16 @@ +defmodule EctoPoly.Helpers do + @moduledoc false + + @spec dependency_vsn_match?(atom(), binary()) :: boolean() + def dependency_vsn_match?(dep, req) do + case :application.get_key(dep, :vsn) do + {:ok, actual} -> + actual + |> List.to_string() + |> Version.match?(req) + + _any -> + false + end + end +end From 79322dad6f7ad0d4dfe993bd843b7556f79746f1 Mon Sep 17 00:00:00 2001 From: Pierre Martin Date: Mon, 19 Oct 2020 13:42:08 +0100 Subject: [PATCH 09/10] Adapts to Ecto 3.5 while remaining 3.4 backwards-compatible, bumps version, formats. --- lib/ecto_poly.ex | 34 +++++++++++---------- lib/ecto_poly/helpers.ex | 15 +++++----- mix.exs | 47 +++++++++++++----------------- test/support/repo.ex | 4 +-- test/support/sms_provider.ex | 5 +--- test/support/test_case.ex | 6 ++-- test/support/test_channel_data.ex | 7 +---- test/support/test_email_channel.ex | 4 +-- 8 files changed, 52 insertions(+), 70 deletions(-) diff --git a/lib/ecto_poly.ex b/lib/ecto_poly.ex index 97e6d73..b82863b 100644 --- a/lib/ecto_poly.ex +++ b/lib/ecto_poly.ex @@ -95,9 +95,7 @@ defmodule EctoPoly do |> Enum.map(&loader/1) end - @doc """ - Casts the given poly with the changeset parameters. - """ + @doc "Casts the given poly with the changeset parameters." @spec cast(Changeset.t(), atom, atom) :: Changeset.t() @spec cast(Changeset.t(), atom, atom, Keyword.t()) :: Changeset.t() def cast(changes, key, typename, opts \\ []) @@ -120,9 +118,8 @@ defmodule EctoPoly do end end - ### - ### Priv - ### + ## Private. + defp caster({_, value_type}) do quote do def cast(value = %unquote(value_type){}), do: {:ok, value} @@ -164,17 +161,24 @@ defmodule EctoPoly do end defp dumper(true, name, value_type) do + embedded_structure = + if __MODULE__.Helpers.dependency_vsn_match?(:ecto, "~> 3.5.0") do + quote(do: {:parameterized, Ecto.Embedded, var!(struct)}) + else + quote(do: {:embed, var!(struct)}) + end + quote do def dump(value = %unquote(value_type){}) do - embed_type = - {:embed, - %Ecto.Embedded{ - cardinality: :one, - related: unquote(value_type), - field: :data - }} - - with {:ok, result} <- Ecto.Type.dump(embed_type, value, &EctoPoly.dump_value/2), + var!(struct) = %Ecto.Embedded{ + cardinality: :one, + field: :data, + related: unquote(value_type), + } + + embedded_type = unquote(embedded_structure) + + with {:ok, result} <- Ecto.Type.dump(embedded_type, value, &EctoPoly.dump_value/2), result = result |> Map.put(@type_field, Atom.to_string(unquote(name))) do {:ok, result} end diff --git a/lib/ecto_poly/helpers.ex b/lib/ecto_poly/helpers.ex index 1b01f60..548133d 100644 --- a/lib/ecto_poly/helpers.ex +++ b/lib/ecto_poly/helpers.ex @@ -3,14 +3,13 @@ defmodule EctoPoly.Helpers do @spec dependency_vsn_match?(atom(), binary()) :: boolean() def dependency_vsn_match?(dep, req) do - case :application.get_key(dep, :vsn) do - {:ok, actual} -> - actual - |> List.to_string() - |> Version.match?(req) - - _any -> - false + with :ok <- Application.ensure_loaded(dep), + vsn when is_list(vsn) <- Application.spec(dep, :vsn) do + vsn + |> List.to_string() + |> Version.match?(req) + else + _ -> false end end end diff --git a/mix.exs b/mix.exs index 171238e..fbc5c62 100644 --- a/mix.exs +++ b/mix.exs @@ -1,11 +1,11 @@ defmodule EctoPoly.Mixfile do use Mix.Project - @version "1.0.4" + @version "1.0.5" @github "https://github.com/greenboxal/phoenix_bert" - def project do - [ + def project(), + do: [ name: "Ecto Poly", description: "Polymorphic embeds for Ecto", version: @version, @@ -18,47 +18,40 @@ defmodule EctoPoly.Mixfile do deps: deps(), docs: docs() ] - end - def application do - [extra_applications: [:logger]] - end + def application(), + do: [extra_applications: [:logger]] - defp deps do - [ + defp deps(), + do: [ {:ecto, ">= 2.0.0"}, {:postgrex, "~> 0.11", only: [:dev, :test]}, - {:jason, "~> 1.1"}, + {:ecto_sql, "~> 3.5", only: [:dev, :test]}, {:ex_doc, "~> 0.19", only: :docs}, {:inch_ex, ">= 0.0.0", only: :docs} - ] ++ deps(Mix.env()) - end - - defp deps(env) when env in [:dev, :test], do: [{:ecto_sql, "~> 3.0"}] - - defp deps(_), do: [{:ecto, "~> 3.0"}] + ] - defp package do - [ + defp package(), + do: [ licenses: ["MIT"], maintainers: ["Jonathan Lima"], links: %{"Github" => @github} ] - end - defp docs do - [ + defp docs(), + do: [ extras: ["README.md"], main: "readme", source_ref: "v#{@version}", source_url: @github ] - end - defp aliases do - [test: ["ecto.create --quiet", "ecto.migrate", "test"]] - end + defp aliases, + do: [test: ["ecto.create --quiet", "ecto.migrate", "test"]] + + defp elixirc_paths(:test), + do: ["lib", "test/support"] - defp elixirc_paths(:test), do: ["lib", "test/support"] - defp elixirc_paths(_), do: ["lib"] + defp elixirc_paths(_), + do: ["lib"] end diff --git a/test/support/repo.ex b/test/support/repo.ex index c5daf4e..50e7ae2 100644 --- a/test/support/repo.ex +++ b/test/support/repo.ex @@ -1,5 +1,3 @@ defmodule EctoPoly.TestRepo do - use Ecto.Repo, - otp_app: :ecto_poly, - adapter: Ecto.Adapters.Postgres + use Ecto.Repo, otp_app: :ecto_poly, adapter: Ecto.Adapters.Postgres end diff --git a/test/support/sms_provider.ex b/test/support/sms_provider.ex index e3c7fae..95f9ef1 100644 --- a/test/support/sms_provider.ex +++ b/test/support/sms_provider.ex @@ -1,6 +1,3 @@ defmodule EctoPoly.SmsProvider do - use EctoPoly, types: [ - twilio: EctoPoly.TwilioSmsProvider, - test: EctoPoly.TestSmsProvider, - ] + use EctoPoly, types: [twilio: EctoPoly.TwilioSmsProvider, test: EctoPoly.TestSmsProvider] end diff --git a/test/support/test_case.ex b/test/support/test_case.ex index 174021d..56bdb28 100644 --- a/test/support/test_case.ex +++ b/test/support/test_case.ex @@ -5,7 +5,6 @@ defmodule EctoPoly.TestCase do ensure_started!() :ok = Ecto.Adapters.SQL.Sandbox.checkout(EctoPoly.TestRepo) - unless tags[:async] do Ecto.Adapters.SQL.Sandbox.mode(EctoPoly.TestRepo, {:shared, self()}) end @@ -13,7 +12,6 @@ defmodule EctoPoly.TestCase do :ok end - defp ensure_started!() do - Mix.EctoSQL.ensure_started(EctoPoly.TestRepo, []) - end + defp ensure_started!(), + do: Mix.EctoSQL.ensure_started(EctoPoly.TestRepo, []) end diff --git a/test/support/test_channel_data.ex b/test/support/test_channel_data.ex index 5e29154..d4bbe6c 100644 --- a/test/support/test_channel_data.ex +++ b/test/support/test_channel_data.ex @@ -1,8 +1,3 @@ defmodule EctoPoly.TestChannelData do - alias EctoPoly, as: EP - - use EctoPoly, types: [ - sms: EP.TestSmsChannel, - email: EP.TestEmailChannel, - ] + use EctoPoly, types: [sms: EctoPoly.TestSmsChannel, email: EctoPoly.TestEmailChannel] end diff --git a/test/support/test_email_channel.ex b/test/support/test_email_channel.ex index 57435cc..d2da0d1 100644 --- a/test/support/test_email_channel.ex +++ b/test/support/test_email_channel.ex @@ -1,5 +1,3 @@ defmodule EctoPoly.TestEmailChannel do - defstruct [ - :email - ] + defstruct [:email] end From ff7a1ece854bcb454d15356136c8d531e01c5f40 Mon Sep 17 00:00:00 2001 From: Pierre Martin Date: Tue, 20 Oct 2020 15:53:44 +0100 Subject: [PATCH 10/10] Prepares for packaging. --- mix.exs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mix.exs b/mix.exs index fbc5c62..c5d32ce 100644 --- a/mix.exs +++ b/mix.exs @@ -1,16 +1,18 @@ defmodule EctoPoly.Mixfile do use Mix.Project - @version "1.0.5" - @github "https://github.com/greenboxal/phoenix_bert" + @version "1.0.6" + @name "Ecto Poly" + @description "Polymorphic embeds for Ecto. Forked from ecto_poly to make it compatible with Ecto 3.5+." + @github "https://github.com/tableturn/ecto_poly" def project(), do: [ - name: "Ecto Poly", - description: "Polymorphic embeds for Ecto", + name: @name, + description: @description, version: @version, elixir: "~> 1.5", - app: :ecto_poly, + app: :ecto_poly_armory, start_permanent: Mix.env() == :prod, elixirc_paths: elixirc_paths(Mix.env()), package: package(), @@ -34,7 +36,7 @@ defmodule EctoPoly.Mixfile do defp package(), do: [ licenses: ["MIT"], - maintainers: ["Jonathan Lima"], + maintainers: ["Pierre Martin", "Jean Parpaillon"], links: %{"Github" => @github} ]