defmodule EctoTurbo do @moduledoc """ A rich ecto component, including search sort and paginate. ## Example ### Category Table Structure Field | Type | Comment ------------- | ------------- | --------- `name` | string | ### Post Table Structure Field | Type | Comment ------------- | ------------- | --------- `name` | string | `body` | text | `price` | float | `category_id` | integer | `available` | boolean | ### Reply Table Structure Field | Type | Comment ------------- | ------------- | --------- `name` | string | `price` | float | `post_id` | integer | Input Search: ```elixir url_query = http://localhost:4000/repies?q[post_name_or_content_like]=elixir ``` Expect output: ```elixir iex> EctoTurbo.turboq(EctoTurbo.Schemas.Reply, %{"q" => %{"post_name_or_content_like" => "elixir"}}) #Ecto.Query ``` """ import Ecto.Query, only: [exclude: 2] alias EctoTurbo.{Builder, Utils} alias EctoTurbo.Config, as: TConfig alias EctoTurbo.Hooks.Paginate @doc """ Returns a result and pageinate info. ## Example iex> EctoTurbo.turbo(EctoTurbo.Schemas.Post, %{"q" => %{"name_or_replies_content_like" => "elixir", "price_eq" => 1}, "s" => "updated_at+asc", "per_page" => 5, "page" => 1}) %{ pagination: %{current_page: 1, current_pages: [1], per_page: 5, next_page: nil, prev_page: nil, total_count: 0, total_pages: 0}, data: [] } """ @spec turbo(any(), map(), keyword()) :: map() def turbo(queryable, params, opts \\ []) do build_opts = uniq_merge(opts, TConfig.defaults()) entry_name = Keyword.get(build_opts, :entry_name, "entries") paginate_name = Keyword.get(build_opts, :paginate_name, "pagination") with_paginate = Keyword.get(build_opts, :with_paginate, true) callback = Keyword.get(build_opts, :callback, fn queryable -> queryable end) prefix = Keyword.get(build_opts, :prefix) queryable = queryable |> turboq(params) |> callback.() |> Map.put(:prefix, prefix) # NOTE it's not the best way, current api needs to be refactored. case with_paginate do true -> %{ entry_name => handle_query(queryable, build_opts), paginate_name => get_paginate(queryable, params, build_opts) } |> Utils.symbolize_keys() false -> queryable |> exclude(:limit) |> exclude(:offset) |> handle_query(build_opts) end end defp uniq_merge(keyword1, keyword2) do keyword2 |> Keyword.merge(keyword1) |> Keyword.new() end @doc """ Returns processed queryable. ## Example iex> EctoTurbo.turboq(EctoTurbo.Schemas.Post, %{"q" => %{"name_or_body_like" => "elixir", "a_eq" => ""}, "s" => "updated_at+asc", "per_page" => 5, "page" => 1}) #Ecto.Query """ @spec turboq(any(), map()) :: Ecto.Query.t() def turboq(queryable, params), do: Builder.run(queryable, params) defp get_paginate(queryable, params, opts), do: Paginate.get_paginate(queryable, params, opts) defp handle_query(queryable, opts) do case Keyword.get(opts, :repo) do nil -> raise "Expected key `repo` in `opts`, got #{inspect(opts)}" repo -> repo.all(queryable) end end end