diff --git a/lib/ecto_shorts/common_filters.ex b/lib/ecto_shorts/common_filters.ex index 50c94b3..518d971 100644 --- a/lib/ecto_shorts/common_filters.ex +++ b/lib/ecto_shorts/common_filters.ex @@ -56,18 +56,26 @@ defmodule EctoShorts.CommonFilters do QueryBuilder } - @type source :: binary() @type params :: map() | keyword() + @type adapter :: module() + @type filter_key :: atom() + @type filter_value :: any() + @type source :: binary() @type query :: Ecto.Query.t() @type queryable :: Ecto.Queryable.t() @type source_queryable :: {source(), queryable()} - @type filter_key :: atom() - @type filter_value :: any() @common_filters QueryBuilder.Common.filters() + @behaviour EctoShorts.QueryBuilder + @doc """ - Converts filter params into a query + Converts filter params into a query. + + ### Examples + + iex> EctoShorts.CommonFilters.convert_params_to_filter(EctoShorts.Support.Schemas.Comment, %{id: 1}) + #Ecto.Query """ @spec convert_params_to_filter( query :: query() | queryable() | source_queryable(), @@ -97,25 +105,29 @@ defmodule EctoShorts.CommonFilters do create_schema_filter(query, filter_key, filter_value) end + @impl true @doc """ - Implementation for `c:EctoShorts.QueryBuilder.create_schema_filter/2`. + Implementation for `c:EctoShorts.QueryBuilder.create_schema_filter/3`. ### Examples iex> EctoShorts.CommonFilters.create_schema_filter(EctoShorts.Support.Schemas.Post, :first, 1_000) + #Ecto.Query + iex> EctoShorts.CommonFilters.create_schema_filter(EctoShorts.Support.Schemas.Post, :comments, %{id: 1}) + #Ecto.Query """ @spec create_schema_filter( query :: query(), filter_key :: filter_key(), filter_value :: filter_value() ) :: query() - def create_schema_filter(query, filter, value) when filter in @common_filters do - QueryBuilder.create_schema_filter(QueryBuilder.Common, {filter, value}, query) + def create_schema_filter(query, filter_key, filter_value) when filter_key in @common_filters do + QueryBuilder.create_schema_filter(QueryBuilder.Common, query, filter_key, filter_value) end - def create_schema_filter(query, filter, value) do - QueryBuilder.create_schema_filter(QueryBuilder.Schema, {filter, value}, query) + def create_schema_filter(query, filter_key, filter_value) do + QueryBuilder.create_schema_filter(QueryBuilder.Schema, query, filter_key, filter_value) end defp ensure_last_is_final_filter(params) do diff --git a/lib/ecto_shorts/query_builder.ex b/lib/ecto_shorts/query_builder.ex index 40f5f1a..fa4b25e 100644 --- a/lib/ecto_shorts/query_builder.ex +++ b/lib/ecto_shorts/query_builder.ex @@ -1,20 +1,59 @@ defmodule EctoShorts.QueryBuilder do - @moduledoc "Behaviour for query building from filter tuples" + @moduledoc """ + Specifies the query builder API required from adapters. + """ + @moduledoc since: "2.5.0" - @type filter_tuple :: {filter_type :: atom, value :: any} - @type accumulator_query :: Ecto.Query.t + @type adapter :: module() + @type filter_key :: atom() + @type filter_value :: any() + @type query :: Ecto.Query.t() + @type queryable :: Ecto.Queryable.t() - @doc "Adds to accumulator query with filter_type and value" - @callback create_schema_filter(filter_tuple, accumulator_query) :: Ecto.Query.t + @doc """ + Adds an expression to they query given a filter key and value. - @spec create_schema_filter(module, filter_tuple, accumulator_query) :: Ecto.Query.t - def create_schema_filter(builder, filter_tuple, query) do - builder.create_schema_filter(filter_tuple, query) - end + The `Ecto.Query` returned should should be same as if it was + written using the `Ecto.Query` dsl. + + For example the following function call + + ```elixir + iex> Ecto.Query.from(c in EctoShorts.Support.Schemas.Comment, where: c.id == 1) + #Ecto.Query + ``` + + is equivalent to + + ```elixir + iex> EctoShorts.QueryBuilder.create_schema_filter( + ...> EctoShorts.QueryBuilder.Schema, + ...> EctoShorts.Support.Schemas.Comment, + ...> :id, + ...> 1 + ...> ) + #Ecto.Query + ``` + """ + @callback create_schema_filter(query(), filter_key(), filter_value()) :: query() - @spec query_schema(Ecto.Queryable.t) :: Ecto.Queryable.t() - @doc "Pulls the schema from a query" - def query_schema(%{from: %{source: {_, schema}}}), do: query_schema(schema) - def query_schema(%{from: %{query: %{from: {_, schema}}}}), do: schema - def query_schema(query), do: query + @doc """ + Invokes the callback function `c:EctoShorts.QueryBuilder.create_schema_filter/3`. + + Returns an `Ecto.Query`. + + ### Examples + + iex> EctoShorts.QueryBuilder.create_schema_filter( + ...> EctoShorts.QueryBuilder.Common, + ...> EctoShorts.Support.Schemas.Comment, + ...> :first, + ...> 1_000 + ...> ) + #Ecto.Query + """ + @spec create_schema_filter(adapter(), query(), filter_key(), filter_value()) :: query() + def create_schema_filter(adapter, query, filter_key, filter_value) do + adapter.create_schema_filter(query, filter_key, filter_value) + end end diff --git a/lib/ecto_shorts/query_builder/common.ex b/lib/ecto_shorts/query_builder/common.ex index e5182c9..58ac8c0 100644 --- a/lib/ecto_shorts/query_builder/common.ex +++ b/lib/ecto_shorts/query_builder/common.ex @@ -3,14 +3,24 @@ defmodule EctoShorts.QueryBuilder.Common do This module contains query building parts for common things such as preload, start/end date and others """ - + @moduledoc since: "2.5.0" import Logger, only: [debug: 1] - import Ecto.Query, only: [ - offset: 2, preload: 2, where: 3, limit: 2, - exclude: 2, from: 2, subquery: 1, order_by: 2 - ] - alias EctoShorts.QueryBuilder + alias EctoShorts.{ + QueryBuilder, + QueryHelpers + } + + alias Ecto.Query + require Ecto.Query + + @type filter_key :: atom() + @type filter_value :: any() + @type filters :: list(atom()) + @type source :: binary() + @type query :: Ecto.Query.t() + @type queryable :: Ecto.Queryable.t() + @type source_queryable :: {source(), queryable()} @behaviour QueryBuilder @@ -29,51 +39,73 @@ defmodule EctoShorts.QueryBuilder.Common do :order_by ] - @spec filters :: list(atom) + @doc """ + Returns the list of supported filters. + + ### Examples + + iex> EctoShorts.QueryBuilder.Common.filters() + [ + :preload, + :start_date, + :end_date, + :before, + :after, + :ids, + :first, + :last, + :limit, + :offset, + :search, + :order_by + ] + """ + @spec filters :: filters() def filters, do: @filters - @impl QueryBuilder - def create_schema_filter({:preload, val}, query), do: preload(query, ^val) + @impl true + @doc """ + Implementation for `c:EctoShorts.QueryBuilder.create_schema_filter/3`. + + ### Examples + + iex> EctoShorts.QueryBuilder.Common.create_schema_filter(EctoShorts.Support.Schemas.Post, :ids, [1]) + """ + @spec create_schema_filter( + query :: query(), + filter_key :: filter_key(), + filter_value :: filter_value() + ) :: query() + def create_schema_filter(query, :preload, val), do: Query.preload(query, ^val) - @impl QueryBuilder - def create_schema_filter({:start_date, val}, query), do: where(query, [m], m.inserted_at >= ^(val)) + def create_schema_filter(query, :start_date, val), do: Query.where(query, [m], m.inserted_at >= ^(val)) - @impl QueryBuilder - def create_schema_filter({:end_date, val}, query), do: where(query, [m], m.inserted_at <= ^val) + def create_schema_filter(query, :end_date, val), do: Query.where(query, [m], m.inserted_at <= ^val) - @impl QueryBuilder - def create_schema_filter({:before, id}, query), do: where(query, [m], m.id < ^id) + def create_schema_filter(query, :before, id), do: Query.where(query, [m], m.id < ^id) - @impl QueryBuilder - def create_schema_filter({:after, id}, query), do: where(query, [m], m.id > ^id) + def create_schema_filter(query, :after, id), do: Query.where(query, [m], m.id > ^id) - @impl QueryBuilder - def create_schema_filter({:ids, ids}, query), do: where(query, [m], m.id in ^ids) + def create_schema_filter(query, :ids, ids), do: Query.where(query, [m], m.id in ^ids) - @impl QueryBuilder - def create_schema_filter({:offset, val}, query), do: offset(query, ^val) + def create_schema_filter(query, :offset, val), do: Query.offset(query, ^val) - @impl QueryBuilder - def create_schema_filter({:limit, val}, query), do: limit(query, ^val) + def create_schema_filter(query, :limit, val), do: Query.limit(query, ^val) - @impl QueryBuilder - def create_schema_filter({:first, val}, query), do: limit(query, ^val) + def create_schema_filter(query, :first, val), do: Query.limit(query, ^val) - @impl QueryBuilder - def create_schema_filter({:order_by, val}, query), do: order_by(query, ^val) + def create_schema_filter(query, :order_by, val), do: Query.order_by(query, ^val) - @impl QueryBuilder - def create_schema_filter({:last, val}, query) do + def create_schema_filter(query, :last, val) do query - |> exclude(:order_by) - |> from(order_by: [desc: :inserted_at], limit: ^val) - |> subquery - |> order_by(:id) + |> Query.exclude(:order_by) + |> Query.from(order_by: [desc: :inserted_at], limit: ^val) + |> Query.subquery() + |> Query.order_by(:id) end - @impl QueryBuilder - def create_schema_filter({:search, val}, query) do - schema = QueryBuilder.query_schema(query) + def create_schema_filter(query, :search, val) do + schema = QueryHelpers.get_queryable(query) if function_exported?(schema, :by_search, 2) do schema.by_search(query, val) diff --git a/lib/ecto_shorts/query_builder/schema.ex b/lib/ecto_shorts/query_builder/schema.ex index 9d31846..aa3a351 100644 --- a/lib/ecto_shorts/query_builder/schema.ex +++ b/lib/ecto_shorts/query_builder/schema.ex @@ -4,35 +4,79 @@ defmodule EctoShorts.QueryBuilder.Schema do when passed a query it can pull the schema from it and attempt to filter on any natural field """ - alias EctoShorts.QueryBuilder + @moduledoc since: "2.5.0" + + alias EctoShorts.{ + QueryBuilder, + QueryHelpers + } + alias EctoShorts.QueryBuilder.Schema.ComparisonFilter - require Logger + alias Ecto.Query require Ecto.Query + require Logger + + @type filter_key :: atom() + @type filter_value :: any() + @type filters :: list(atom()) + @type source :: binary() + @type query :: Ecto.Query.t() + @type queryable :: Ecto.Queryable.t() + @type source_queryable :: {source(), queryable()} + @behaviour QueryBuilder - @impl QueryBuilder - def create_schema_filter({filter_field, val}, query) do - create_schema_filter( - {filter_field, val}, - QueryBuilder.query_schema(query), - query - ) + @impl true + @doc """ + Implementation for `c:EctoShorts.QueryBuilder.create_schema_filter/3`. + + ### Examples + + iex> EctoShorts.QueryBuilder.Schema.create_schema_filter(EctoShorts.Support.Schemas.Post, :comments, %{id: 1}) + """ + @spec create_schema_filter( + query :: query(), + filter_key :: filter_key(), + filter_value :: filter_value() + ) :: query() + def create_schema_filter(query, filter_key, filter_value) do + queryable = QueryHelpers.get_queryable(query) + + create_schema_filter(queryable, query, filter_key, filter_value) end - def create_schema_filter({filter_field, val}, schema, query) do + @doc """ + Builds an ecto query for the given `Ecto.Schema`. + + ### Examples + + iex> EctoShorts.QueryBuilder.Schema.create_schema_filter( + ...> EctoShorts.Support.Schemas.Post, + ...> Ecto.Query.from(EctoShorts.Support.Schemas.Post), + ...> :comments, + ...> %{id: 1} + ...> ) + """ + @spec create_schema_filter( + queryable :: queryable(), + query :: query(), + filter_key :: filter_key(), + filter_value :: filter_value() + ) :: query() + def create_schema_filter(queryable, query, filter_key, filter_value) do cond do - filter_field in schema.__schema__(:query_fields) -> - create_schema_query_field_filter(query, schema, filter_field, val) + filter_key in queryable.__schema__(:query_fields) -> + create_schema_query_field_filter(queryable, query, filter_key, filter_value) - filter_field in schema.__schema__(:associations) -> - relational_schema = ecto_association_queryable!(schema, filter_field) + filter_key in queryable.__schema__(:associations) -> + assoc_schema = ecto_association_queryable!(queryable, filter_key) - create_schema_assocation_filter(query, filter_field, val, schema, relational_schema) + create_schema_assocation_filter(queryable, query, filter_key, filter_value, assoc_schema) true -> - Logger.debug("[EctoShorts] #{Atom.to_string(filter_field)} is neither a field nor has a valid association for #{schema.__schema__(:source)} where filter") + Logger.debug("[EctoShorts] #{Atom.to_string(filter_key)} is neither a field nor has a valid association for #{queryable.__schema__(:source)} where filter") query end @@ -51,28 +95,29 @@ defmodule EctoShorts.QueryBuilder.Schema do end end - defp create_schema_query_field_filter(query, schema, filter_field, val) do - case schema.__schema__(:type, filter_field) do + defp create_schema_query_field_filter(queryable, query, filter_key, filter_value) do + case queryable.__schema__(:type, filter_key) do {:array, _} -> - ComparisonFilter.build_array(query, schema.__schema__(:field_source, filter_field), val) + ComparisonFilter.build_array(query, queryable.__schema__(:field_source, filter_key), filter_value) _ -> - ComparisonFilter.build(query, schema.__schema__(:field_source, filter_field), val) + ComparisonFilter.build(query, queryable.__schema__(:field_source, filter_key), filter_value) end end - defp create_schema_assocation_filter(query, filter_field, val, _schema, relational_schema) do - binding_alias = :"ecto_shorts_#{filter_field}" + defp create_schema_assocation_filter(_queryable, query, filter_key, filter_value, assoc_schema) do + binding_alias = :"ecto_shorts_#{filter_key}" query - |> Ecto.Query.with_named_binding(binding_alias, fn query, binding_alias -> - Ecto.Query.join( + |> Query.with_named_binding(binding_alias, fn query, binding_alias -> + Query.join( query, :inner, [scm], - assoc in assoc(scm, ^filter_field), as: ^binding_alias) + assoc in assoc(scm, ^filter_key), as: ^binding_alias + ) end) - |> ComparisonFilter.build_relational(binding_alias, val, relational_schema) + |> ComparisonFilter.build_relational(binding_alias, filter_value, assoc_schema) end end diff --git a/lib/ecto_shorts/query_builder/schema/comparison_filter.ex b/lib/ecto_shorts/query_builder/schema/comparison_filter.ex index a89c165..1d7905e 100644 --- a/lib/ecto_shorts/query_builder/schema/comparison_filter.ex +++ b/lib/ecto_shorts/query_builder/schema/comparison_filter.ex @@ -5,152 +5,154 @@ defmodule EctoShorts.QueryBuilder.Schema.ComparisonFilter do # values which then forces us to define two separate versions require Logger - import Ecto.Query, only: [where: 3] + + alias Ecto.Query + require Ecto.Query # Non relational fields - def build(query, filter_field, val) when is_list(val) do - where(query, [scm], field(scm, ^filter_field) in ^val) + def build(query, filter_key, filter_value) when is_list(filter_value) do + Query.where(query, [scm], field(scm, ^filter_key) in ^filter_value) end - def build(query, filter_field, %NaiveDateTime{} = val) do - where(query, [scm], field(scm, ^filter_field) == ^val) + def build(query, filter_key, %NaiveDateTime{} = filter_value) do + Query.where(query, [scm], field(scm, ^filter_key) == ^filter_value) end - def build(query, filter_field, %DateTime{} = val) do - where(query, [scm], field(scm, ^filter_field) == ^val) + def build(query, filter_key, %DateTime{} = filter_value) do + Query.where(query, [scm], field(scm, ^filter_key) == ^filter_value) end - def build(_query, _filter_field, nil) do + def build(_query, _filter_key, nil) do raise ArgumentError, message: "comparison with nil is forbidden as it is unsafe. If you want to check if a value is nil, use %{==: nil} or %{!=: nil} instead" end - def build(query, filter_field, filters) when is_map(filters) do + def build(query, filter_key, filters) when is_map(filters) do Enum.reduce(filters, query, fn ({filter_type, value}, query) -> - build_schema_field_filters(query, nil, filter_field, filter_type, value) + build_schema_field_filters(query, nil, filter_key, filter_type, value) end) end - def build(query, filter_field, {:lower, val}) do - where(query, [scm], fragment("lower(?)", field(scm, ^filter_field)) == ^val) + def build(query, filter_key, {:lower, filter_value}) do + Query.where(query, [scm], fragment("lower(?)", field(scm, ^filter_key)) == ^filter_value) end - def build(query, filter_field, {:upper, val}) do - where(query, [scm], fragment("upper(?)", field(scm, ^filter_field)) == ^val) + def build(query, filter_key, {:upper, filter_value}) do + Query.where(query, [scm], fragment("upper(?)", field(scm, ^filter_key)) == ^filter_value) end - def build(query, filter_field, val) do - where(query, [scm], field(scm, ^filter_field) == ^val) + def build(query, filter_key, filter_value) do + Query.where(query, [scm], field(scm, ^filter_key) == ^filter_value) end - def build_array(query, filter_field, val) when is_list(val) do - where(query, [scm], field(scm, ^filter_field) == ^val) + def build_array(query, filter_key, filter_value) when is_list(filter_value) do + Query.where(query, [scm], field(scm, ^filter_key) == ^filter_value) end - def build_array(query, filter_field, filters) when is_map(filters) do - build(query, filter_field, filters) + def build_array(query, filter_key, filters) when is_map(filters) do + build(query, filter_key, filters) end - def build_array(query, filter_field, val) do - where(query, [scm], ^val in field(scm, ^filter_field)) + def build_array(query, filter_key, filter_value) do + Query.where(query, [scm], ^filter_value in field(scm, ^filter_key)) end - defp build_schema_field_filters(query, binding_alias, filter_field, :==, nil) do + defp build_schema_field_filters(query, binding_alias, filter_key, :==, nil) do if binding_alias do - where(query, [{^binding_alias, scm}], is_nil(field(scm, ^filter_field))) + Query.where(query, [{^binding_alias, scm}], is_nil(field(scm, ^filter_key))) else - where(query, [scm], is_nil(field(scm, ^filter_field))) + Query.where(query, [scm], is_nil(field(scm, ^filter_key))) end end - defp build_schema_field_filters(query, binding_alias, filter_field, :!=, nil) do + defp build_schema_field_filters(query, binding_alias, filter_key, :!=, nil) do if binding_alias do - where(query, [{^binding_alias, scm}], not is_nil(field(scm, ^filter_field))) + Query.where(query, [{^binding_alias, scm}], not is_nil(field(scm, ^filter_key))) else - where(query, [scm], not is_nil(field(scm, ^filter_field))) + Query.where(query, [scm], not is_nil(field(scm, ^filter_key))) end end - defp build_schema_field_filters(query, binding_alias, filter_field, :!=, val) when is_list(val) do + defp build_schema_field_filters(query, binding_alias, filter_key, :!=, filter_value) when is_list(filter_value) do if binding_alias do - where(query, [{^binding_alias, scm}], field(scm, ^filter_field) not in ^val) + Query.where(query, [{^binding_alias, scm}], field(scm, ^filter_key) not in ^filter_value) else - where(query, [scm], field(scm, ^filter_field) not in ^val) + Query.where(query, [scm], field(scm, ^filter_key) not in ^filter_value) end end - defp build_schema_field_filters(query, binding_alias, filter_field, :!=, {:lower, val}) do + defp build_schema_field_filters(query, binding_alias, filter_key, :!=, {:lower, filter_value}) do if binding_alias do - where(query, [{^binding_alias, scm}], fragment("lower(?)", field(scm, ^filter_field)) != ^val) + Query.where(query, [{^binding_alias, scm}], fragment("lower(?)", field(scm, ^filter_key)) != ^filter_value) else - where(query, [scm], fragment("lower(?)", field(scm, ^filter_field)) != ^val) + Query.where(query, [scm], fragment("lower(?)", field(scm, ^filter_key)) != ^filter_value) end end - defp build_schema_field_filters(query, binding_alias, filter_field, :!=, {:upper, val}) do + defp build_schema_field_filters(query, binding_alias, filter_key, :!=, {:upper, filter_value}) do if binding_alias do - where(query, [{^binding_alias, scm}], fragment("upper(?)", field(scm, ^filter_field)) != ^val) + Query.where(query, [{^binding_alias, scm}], fragment("upper(?)", field(scm, ^filter_key)) != ^filter_value) else - where(query, [scm], fragment("upper(?)", field(scm, ^filter_field)) != ^val) + Query.where(query, [scm], fragment("upper(?)", field(scm, ^filter_key)) != ^filter_value) end end - defp build_schema_field_filters(query, binding_alias, filter_field, :!=, val) do + defp build_schema_field_filters(query, binding_alias, filter_key, :!=, filter_value) do if binding_alias do - where(query, [{^binding_alias, scm}], field(scm, ^filter_field) != ^val) + Query.where(query, [{^binding_alias, scm}], field(scm, ^filter_key) != ^filter_value) else - where(query, [scm], field(scm, ^filter_field) != ^val) + Query.where(query, [scm], field(scm, ^filter_key) != ^filter_value) end end - defp build_schema_field_filters(query, binding_alias, filter_field, :gt, val) do + defp build_schema_field_filters(query, binding_alias, filter_key, :gt, filter_value) do if binding_alias do - where(query, [{^binding_alias, scm}], field(scm, ^filter_field) > ^val) + Query.where(query, [{^binding_alias, scm}], field(scm, ^filter_key) > ^filter_value) else - where(query, [scm], field(scm, ^filter_field) > ^val) + Query.where(query, [scm], field(scm, ^filter_key) > ^filter_value) end end - defp build_schema_field_filters(query, binding_alias, filter_field, :lt, val) do + defp build_schema_field_filters(query, binding_alias, filter_key, :lt, filter_value) do if binding_alias do - where(query, [{^binding_alias, scm}], field(scm, ^filter_field) < ^val) + Query.where(query, [{^binding_alias, scm}], field(scm, ^filter_key) < ^filter_value) else - where(query, [scm], field(scm, ^filter_field) < ^val) + Query.where(query, [scm], field(scm, ^filter_key) < ^filter_value) end end - defp build_schema_field_filters(query, binding_alias, filter_field, :gte, val) do + defp build_schema_field_filters(query, binding_alias, filter_key, :gte, filter_value) do if binding_alias do - where(query, [{^binding_alias, scm}], field(scm, ^filter_field) >= ^val) + Query.where(query, [{^binding_alias, scm}], field(scm, ^filter_key) >= ^filter_value) else - where(query, [scm], field(scm, ^filter_field) >= ^val) + Query.where(query, [scm], field(scm, ^filter_key) >= ^filter_value) end end - defp build_schema_field_filters(query, binding_alias, filter_field, :lte, val) do + defp build_schema_field_filters(query, binding_alias, filter_key, :lte, filter_value) do if binding_alias do - where(query, [{^binding_alias, scm}], field(scm, ^filter_field) <= ^val) + Query.where(query, [{^binding_alias, scm}], field(scm, ^filter_key) <= ^filter_value) else - where(query, [scm], field(scm, ^filter_field) <= ^val) + Query.where(query, [scm], field(scm, ^filter_key) <= ^filter_value) end end - defp build_schema_field_filters(query, binding_alias, filter_field, :like, val) do - search_query = "%#{val}%" + defp build_schema_field_filters(query, binding_alias, filter_key, :like, filter_value) do + search_query = "%#{filter_value}%" if binding_alias do - where(query, [{^binding_alias, scm}], like(field(scm, ^filter_field), ^search_query)) + Query.where(query, [{^binding_alias, scm}], like(field(scm, ^filter_key), ^search_query)) else - where(query, [scm], like(field(scm, ^filter_field), ^search_query)) + Query.where(query, [scm], like(field(scm, ^filter_key), ^search_query)) end end - defp build_schema_field_filters(query, binding_alias, filter_field, :ilike, val) do - search_query = "%#{val}%" + defp build_schema_field_filters(query, binding_alias, filter_key, :ilike, filter_value) do + search_query = "%#{filter_value}%" if binding_alias do - where(query, [{^binding_alias, scm}], ilike(field(scm, ^filter_field), ^search_query)) + Query.where(query, [{^binding_alias, scm}], ilike(field(scm, ^filter_key), ^search_query)) else - where(query, [scm], ilike(field(scm, ^filter_field), ^search_query)) + Query.where(query, [scm], ilike(field(scm, ^filter_key), ^search_query)) end end @@ -170,16 +172,16 @@ defmodule EctoShorts.QueryBuilder.Schema.ComparisonFilter do raise ArgumentError, message: "must provide a map for associations to filter on\ngiven #{inspect value}" end - defp build_relational_filter(query, binding_alias, filter_field, val, _relational_schema) when is_list(val) do - where(query, [{^binding_alias, scm}], field(scm, ^filter_field) in ^val) + defp build_relational_filter(query, binding_alias, filter_key, filter_value, _relational_schema) when is_list(filter_value) do + Query.where(query, [{^binding_alias, scm}], field(scm, ^filter_key) in ^filter_value) end - defp build_relational_filter(query, binding_alias, filter_field, %NaiveDateTime{} = val, _relational_schema) do - where(query, [{^binding_alias, scm}], field(scm, ^filter_field) == ^val) + defp build_relational_filter(query, binding_alias, filter_key, %NaiveDateTime{} = filter_value, _relational_schema) do + Query.where(query, [{^binding_alias, scm}], field(scm, ^filter_key) == ^filter_value) end - defp build_relational_filter(query, binding_alias, filter_field, %DateTime{} = val, _relational_schema) do - where(query, [{^binding_alias, scm}], field(scm, ^filter_field) == ^val) + defp build_relational_filter(query, binding_alias, filter_key, %DateTime{} = filter_value, _relational_schema) do + Query.where(query, [{^binding_alias, scm}], field(scm, ^filter_key) == ^filter_value) end defp build_relational_filter(query, binding_alias, field_key, filters, relational_schema) when is_map(filters) do @@ -209,8 +211,8 @@ defmodule EctoShorts.QueryBuilder.Schema.ComparisonFilter do end end - defp build_relational_filter(query, binding_alias, filter_field, val, _relational_schema) do - where(query, [{^binding_alias, scm}], field(scm, ^filter_field) == ^val) + defp build_relational_filter(query, binding_alias, filter_key, filter_value, _relational_schema) do + Query.where(query, [{^binding_alias, scm}], field(scm, ^filter_key) == ^filter_value) end defp ecto_association_queryable!(schema, field_key) do diff --git a/test/ecto_shorts/query_builder/common_test.exs b/test/ecto_shorts/query_builder/common_test.exs index 3a0556d..d5f4942 100644 --- a/test/ecto_shorts/query_builder/common_test.exs +++ b/test/ecto_shorts/query_builder/common_test.exs @@ -26,9 +26,9 @@ defmodule EctoShorts.QueryBuilder.CommonTest do describe "create_schema_filters: " do test "returns query without changes when passed {:search, term()}" do - expected_query = Comment + query = Comment - assert ^expected_query = Common.create_schema_filter({:search, %{id: 1}}, expected_query) + assert ^query = Common.create_schema_filter(query, :search, %{id: 1}) end end end diff --git a/test/ecto_shorts/query_builder/schema_test.exs b/test/ecto_shorts/query_builder/schema_test.exs index ae4ee0b..2d1b172 100644 --- a/test/ecto_shorts/query_builder/schema_test.exs +++ b/test/ecto_shorts/query_builder/schema_test.exs @@ -1,5 +1,8 @@ defmodule EctoShorts.QueryBuilder.SchemaTest do use ExUnit.Case, async: true + + require Ecto.Query + doctest EctoShorts.QueryBuilder.Schema alias EctoShorts.QueryBuilder.Schema @@ -7,7 +10,7 @@ defmodule EctoShorts.QueryBuilder.SchemaTest do describe "create_schema_filter: " do test "returns a query where record matches query field value" do - query = Schema.create_schema_filter({:id, 1}, Post) + query = Schema.create_schema_filter(Post, :id, 1) assert %Ecto.Query{ aliases: %{}, @@ -29,7 +32,7 @@ defmodule EctoShorts.QueryBuilder.SchemaTest do end test "returns a query where record matches association params" do - query = Schema.create_schema_filter({:comments, %{id: 1}}, Post) + query = Schema.create_schema_filter(Post, :comments, %{id: 1}) assert %Ecto.Query{ aliases: %{ @@ -68,13 +71,13 @@ defmodule EctoShorts.QueryBuilder.SchemaTest do end test "returns query without changes when key is not a valid field" do - expected_query = Post + query = Post - assert ^expected_query = Schema.create_schema_filter({:invalid_association, 1}, expected_query) + assert ^query = Schema.create_schema_filter(query, :invalid_association, 1) end test "returns query that joins on has_many through association" do - query = Schema.create_schema_filter({:authors, %{id: 1}}, Post) + query = Schema.create_schema_filter(Post, :authors, %{id: 1}) assert %Ecto.Query{ aliases: %{ @@ -113,7 +116,7 @@ defmodule EctoShorts.QueryBuilder.SchemaTest do end test "returns query that matches on record by an array query field" do - query = Schema.create_schema_filter({:tags, ["tag"]}, Comment) + query = Schema.create_schema_filter(Comment, :tags, ["tag"]) assert %Ecto.Query{ aliases: %{}, diff --git a/test/ecto_shorts/query_builder_test.exs b/test/ecto_shorts/query_builder_test.exs index b18bc34..f24abb0 100644 --- a/test/ecto_shorts/query_builder_test.exs +++ b/test/ecto_shorts/query_builder_test.exs @@ -1,4 +1,30 @@ defmodule EctoShorts.QueryBuilderTest do - use ExUnit.Case, async: true + use EctoShorts.DataCase + + require Ecto.Query + doctest EctoShorts.QueryBuilder + + alias Ecto.Query + alias EctoShorts.QueryBuilder + alias EctoShorts.Support.Schemas.Comment + alias EctoShorts.Support.Repo + + describe "create_schema_filter: " do + test "returns the result of the ecto query dsl" do + comment = + %Comment{} + |> Comment.changeset() + |> Repo.insert!() + + comment_id = comment.id + + ecto_query = Query.from(c in Comment, where: c.id == ^comment_id) + + builder_query = + QueryBuilder.create_schema_filter(QueryBuilder.Schema, Comment, :id, comment_id) + + assert Repo.one(ecto_query) === Repo.one(builder_query) + end + end end diff --git a/test/test_helper.exs b/test/test_helper.exs index cee5f2a..5359bfd 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,5 +1,9 @@ ExUnit.start() +if System.get_env("CI") do + Code.put_compiler_option(:warnings_as_errors, true) +end + {:ok, _} = Application.ensure_all_started(:postgrex) {:ok, _} = EctoShorts.Support.Repo.start_link() @@ -14,7 +18,3 @@ ExUnit.start() pool: Ecto.Adapters.SQL.Sandbox, pool_size: 5 ) - -if System.get_env("CI") do - Code.put_compiler_option(:warnings_as_errors, true) -end