diff --git a/lib/paginator/ecto/order_by.ex b/lib/paginator/ecto/order_by.ex new file mode 100644 index 0000000..ddeaaab --- /dev/null +++ b/lib/paginator/ecto/order_by.ex @@ -0,0 +1,32 @@ +defmodule Paginator.Ecto.OrderBy do + @moduledoc false + + # import Ecto.Query + + def infer_order_by(queryable) do + queryable + |> get_order_by_expressions() + |> make_cursor_field_list() + end + + defp get_order_by_expressions(queryable) do + queryable.order_bys + |> Enum.reduce([], fn x, acc -> x.expr ++ acc end) + end + + defp make_cursor_field_list(expressions) do + expressions + |> Enum.map(fn {key, value} -> + # gets the field name atom from the query struct + + case value do + # https://github.com/elixir-ecto/ecto/blob/1533413/lib/ecto/query/builder/order_by.ex#L121 + {{:., [], [{:&, [], [0]}, field]}, [], []} -> + {field, key} + + _ -> + raise "Unsupported `order_by` syntax, could not infer cursor fields for Paginator. Please supply `cursor_fields` manually." + end + end) + end +end diff --git a/test/paginator/order_by_test.exs b/test/paginator/order_by_test.exs new file mode 100644 index 0000000..cdf7e9d --- /dev/null +++ b/test/paginator/order_by_test.exs @@ -0,0 +1,48 @@ +defmodule Paginator.OrderByTest do + use ExUnit.Case, async: true + + alias Paginator.Ecto.OrderBy + import Ecto.Query + + defp assert_result(result, list) do + assert(result == list) + end + + describe "OrderBy.infer_order_by/1" do + test "parses single order_by with default direction" do + from(p in "payments", + order_by: p.charged_at + ) + |> OrderBy.infer_order_by() + |> assert_result(charged_at: :asc) + end + + test "parses multiple order_bys with default direction" do + from(p in "payments", + order_by: [p.amount, p.charged_at] + ) + |> OrderBy.infer_order_by() + |> assert_result(amount: :asc, charged_at: :asc) + end + + test "parses multiple seperate order_bys with a given direction" do + from(p in "payments", + order_by: [desc: p.charged_at], + order_by: [asc: p.amount] + ) + |> OrderBy.infer_order_by() + |> assert_result(amount: :asc, charged_at: :desc) + end + + test "rejects order_by with a fragment" do + assert_raise RuntimeError, + "Unsupported `order_by` syntax, could not infer cursor fields for Paginator. Please supply `cursor_fields` manually.", + fn -> + from(p in "payments", + order_by: fragment("amount") + ) + |> OrderBy.infer_order_by() + end + end + end +end