defmodule EctoShorts.QueryBuilder.Schema do @moduledoc """ This module contains query building parts for schemas themselves, when passed a query it can pull the schema from it and attempt to filter on any natural field """ require Logger require Ecto.Query alias EctoShorts.QueryBuilder alias EctoShorts.QueryBuilder.Schema.ComparisonFilter @behaviour QueryBuilder @impl QueryBuilder def create_schema_filter({filter_field, val}, query) do create_schema_filter( {filter_field, val}, QueryBuilder.query_schema(query), query ) end def create_schema_filter({filter_field, val}, schema, query) do cond do filter_field in schema.__schema__(:query_fields) -> case schema.__schema__(:type, filter_field) do {:array, _} -> ComparisonFilter.build_array(query, schema.__schema__(:field_source, filter_field), val) _ -> ComparisonFilter.build(query, schema.__schema__(:field_source, filter_field), val) end filter_field in schema.__schema__(:associations) -> binding_alias = :"ecto_shorts_#{filter_field}" query = Ecto.Query.join( query, :inner, [scm], assoc in assoc(scm, ^filter_field) ) query = %{query | aliases: add_relational_alias(query, binding_alias), joins: add_join_alias(query, filter_field, binding_alias) } ComparisonFilter.build_relational(query, binding_alias, val) true -> Logger.debug("[EctoShorts] #{Atom.to_string(filter_field)} is not a field for #{schema.__schema__(:source)} where filter") query end end defp add_relational_alias(query, new_alias) do if query.aliases[new_alias] do raise ArgumentError, message: "already defined #{new_alias} as an alias within query #{inspect query}" else # Here we need to put the size of current aliases plus one to indicate the next value Map.put(query.aliases, new_alias, map_size(query.aliases) + 1) end end defp add_join_alias(query, filter_field, binding_alias) do Enum.map(query.joins, fn join -> if elem(join.assoc, 1) === filter_field do %{join | as: binding_alias} else join end end) end end