Flop.Schema protocol (Flop v0.12.0) View Source
This protocol allows you to set query options in your Ecto schemas.
Usage
Derive Flop.Schema
in your Ecto schema and set the filterable and sortable
fields.
defmodule Flop.Pet do
use Ecto.Schema
@derive {
Flop.Schema,
filterable: [:name, :species],
sortable: [:name, :age]
}
schema "pets" do
field :name, :string
field :age, :integer
field :species, :string
end
end
After that, you can pass the module as the :for
option to Flop.validate/2
.
iex> Flop.validate(%Flop{order_by: [:name]}, for: Flop.Pet)
{:ok,
%Flop{
filters: [],
limit: nil,
offset: nil,
order_by: [:name],
order_directions: nil,
page: nil,
page_size: nil
}}
iex> {:error, changeset} = Flop.validate(
...> %Flop{order_by: [:species]}, for: Flop.Pet
...> )
iex> changeset.valid?
false
iex> changeset.errors
[
order_by: {"has an invalid entry",
[validation: :subset, enum: [:name, :age]]}
]
Default and maximum limits
To define a default or maximum limit, you can set the default_limit
and
max_limit
option when deriving Flop.Schema
. The maximum limit will be
validated and the default limit applied by Flop.validate/1
.
@derive {
Flop.Schema,
filterable: [:name, :species],
sortable: [:name, :age],
max_limit: 100,
default_limit: 50
}
Default sort order
To define a default sort order, you can set the default_order_by
and
default_order_directions
options when deriving Flop.Schema
. The default
values are applied by Flop.validate/1
. If no order directions are set,
:asc
is assumed for all fields.
@derive {
Flop.Schema,
filterable: [:name, :species],
sortable: [:name, :age],
default_order_by: [:name, :age],
default_order_directions: [:asc, :desc]
}
Restricting pagination types
By default, page
/page_size
, offset
/limit
and cursor-based pagination
(first
/after
and last
/before
) are enabled. If you want to restrict the
pagination type for a schema, you can do that by setting the
pagination_types
option.
@derive {
Flop.Schema,
filterable: [:name, :species],
sortable: [:name, :age],
pagination_types: [:first, :last]
}
See also Flop.option/0
and Flop.pagination_type/0
. Setting the value
to nil
allows all pagination types.
Compound fields
Sometimes you might need to apply a search term to multiple fields at once, e.g. you might want to search in both the family name and given name field. You can do that with Flop by defining a compound field.
@derive {
Flop.Schema,
filterable: [:full_name],
sortable: [],
compound_fields: [full_name: [:family_name, :given_name]]
}
This allows you to use the field name :full_name
as any other field in the
filters.
params = %{
filters: [%{
field: :full_name,
op: :==,
value: "margo"
}]
}
This would translate to:
WHERE family_name='margo' OR given_name ='martindale'
Partial matches and splitting of the search term can be achieved with one of the ilike operators.
params = %{
filters: [%{
field: :full_name,
op: :ilike_and,
value: "margo martindale"
}]
}
This would translate to:
WHERE (family_name ilike '%margo%' OR given_name ='%margo%')
AND (family_name ilike '%martindale%' OR given_name ='%martindale%')
Filter operator rules
:=~
,:like
,:like_and
,:like_or
,:ilike
,:ilike_and
,:ilike_or
- The filter value is split at whitespace characters as usual. The filter matches for a value if it matches for any of the fields.:empty
- Matches if all fields of the compound field arenil
.:not_empty
- Matches if any field of the compound field is notnil
.:==
,:!=
,:<=
,:<
,:>=
,:>
,:in
- The filter value is normalized by splitting the string at whitespaces and joining it with a space. The values of all fields of the compound field are split by whitespace character and joined with a space, and the resulting values are joined with a space again. This will be added in a future version. These filter operators are ignored for compound fields at the moment.
Join fields
If you need filter or order across tables, you can define join fields.
Note: Support for ordering by join fields will be added in a future version.
As an example, let's define these schemas:
schema "owners" do
field :name, :string
field :email, :string
has_many :pets, Pet
end
schema "pets" do
field :name, :string
field :species, :string
belongs_to :owner, Owner
end
And now we want to find all owners that have pets of the species
"E. africanus"
. To do this, first we need to define a join field on the
Owner
schema.
@derive {
Flop.Schema,
filterable: [:pet_species],
sortable: [:pet_species],
join_fields: [pet_species: {:pets, :species}]
}
In this case, :pet_species
would be the alias of the field that you can
refer to in the filter and order parameters. In {:pets, :species}
, :pets
refers to a named binding and :species
refers to a field in that binding.
After setting up the join fields, you can write a query like this:
params = %{
filters: [%{field: :pet_species, op: :==, value: "E. africanus"}]
}
Owner
|> join(:left, [o], p in assoc(o, :pets), as: :pets)
|> Flop.validate_and_run!(params, for: Owner)
Note that Flop doesn't create the join clauses for you. The named bindings already have to be present in the query you pass to the Flop functions.
Link to this section Summary
Functions
Returns the default limit of a schema.
Returns the default order of a schema.
Returns the field type in a schema.
Returns the filterable fields of a schema.
Returns the maximum limit of a schema.
Returns the allowed pagination types of a schema.
Returns the sortable fields of a schema.
Link to this section Types
Specs
t() :: term()
Link to this section Functions
Specs
default_limit(any()) :: pos_integer() | nil
Returns the default limit of a schema.
iex> Flop.Schema.default_limit(%Flop.Fruit{})
50
Specs
default_order(any()) :: %{ order_by: [atom()] | nil, order_directions: [Flop.order_direction()] | nil }
Returns the default order of a schema.
iex> Flop.Schema.default_order(%Flop.Fruit{})
%{order_by: [:name], order_directions: [:asc]}
Specs
Returns the field type in a schema.
{:normal, atom}
- An ordinary field on the schema. The second tuple element is the field name.{:compound, [atom]}
- A combination of fields defined with thecompound_fields
option. The list of atoms refers to the list of fields that are included.{:join, {atom, atom}}
- A field from a named binding as defined with thejoin_fields
option. The first atom refers to the binding name, the second atom refers to the field.
Specs
Returns the filterable fields of a schema.
iex> Flop.Schema.filterable(%Flop.Pet{})
[
:age,
:full_name,
:name,
:owner_age,
:owner_name,
:pet_and_owner_name,
:species
]
Specs
max_limit(any()) :: pos_integer() | nil
Returns the maximum limit of a schema.
iex> Flop.Schema.max_limit(%Flop.Pet{})
1000
Specs
pagination_types(any()) :: [Flop.pagination_type()] | nil
Returns the allowed pagination types of a schema.
iex> Flop.Schema.pagination_types(%Flop.Fruit{})
[:first, :last, :offset]
Specs
Returns the sortable fields of a schema.
iex> Flop.Schema.sortable(%Flop.Pet{})
[:name, :age]