All Projects → omohokcoj → Filterable

omohokcoj / Filterable

Licence: mit
Filtering from incoming params in Elixir/Ecto/Phoenix with easy to use DSL.

Programming Languages

elixir
2628 projects

Projects that are alternatives of or similar to Filterable

flop
Filtering, ordering and pagination for Ecto
Stars: ✭ 56 (-32.53%)
Mutual labels:  pagination, filter, ecto
Queryql
Easily add filtering, sorting, and pagination to your Node.js REST API through your old friend: the query string!
Stars: ✭ 76 (-8.43%)
Mutual labels:  filter, pagination, sorting
Tablefilter
A Javascript library making HTML tables filterable and a bit more :)
Stars: ✭ 248 (+198.8%)
Mutual labels:  filter, pagination, sorting
react-strap-table
react table (client and server-side) based on bootstrap.
Stars: ✭ 28 (-66.27%)
Mutual labels:  pagination, sorting, filter
querie
Compose Ecto query from the client side
Stars: ✭ 20 (-75.9%)
Mutual labels:  phoenix, ecto, query-builder
Rummage ecto
Search, Sort and Pagination for ecto queries
Stars: ✭ 190 (+128.92%)
Mutual labels:  ecto, pagination, sorting
Rummage phoenix
Full Phoenix Support for Rummage. It can be used for searching, sorting and paginating collections in phoenix.
Stars: ✭ 144 (+73.49%)
Mutual labels:  phoenix, pagination, sorting
spring-boot-jpa-rest-demo-filter-paging-sorting
Spring Boot Data JPA with Filter, Pagination and Sorting
Stars: ✭ 70 (-15.66%)
Mutual labels:  pagination, sorting, filter
phoenix pagination
Simple pagination for Ecto and Phoenix that uses plain EEx templates.
Stars: ✭ 20 (-75.9%)
Mutual labels:  pagination, phoenix, ecto
query builder
Compose Ecto queries without effort
Stars: ✭ 56 (-32.53%)
Mutual labels:  phoenix, ecto, query-builder
Gridify
Easy and optimized way to apply Filtering, Sorting, and Pagination using text-based data.
Stars: ✭ 372 (+348.19%)
Mutual labels:  pagination, sorting, query-builder
ex sieve
Implement dynamic filtering and sorting API for Ecto queries
Stars: ✭ 37 (-55.42%)
Mutual labels:  phoenix, filter, ecto
one plus n detector
Elixir library to help you detect 1+n queries in applications using Ecto
Stars: ✭ 20 (-75.9%)
Mutual labels:  phoenix, ecto
ecto generator
Generate Ecto schemas from existing database in Phoenix - Elixir
Stars: ✭ 20 (-75.9%)
Mutual labels:  phoenix, ecto
Gridjs
Advanced table plugin
Stars: ✭ 3,231 (+3792.77%)
Mutual labels:  filter, pagination
Graphql To Mongodb
Allows for generic run-time generation of filter types for existing graphql types and parsing client requests to mongodb find queries
Stars: ✭ 261 (+214.46%)
Mutual labels:  filter, pagination
Polymorphic embed
Polymorphic embeds in Ecto
Stars: ✭ 80 (-3.61%)
Mutual labels:  ecto, phoenix
Exopite-Multifilter-Multi-Sorter-WordPress-Plugin
Display and/or sort/filter any page or post types by multiple taxonomies or terms (like post by categories and/or tags) with AJAX. Exopite multifilter, multi-sortable, multi selectable, multi filterable sortable Wordpress Plugin.
Stars: ✭ 18 (-78.31%)
Mutual labels:  pagination, filter
Machinery
State machine thin layer for structs (+ GUI for Phoenix apps)
Stars: ✭ 367 (+342.17%)
Mutual labels:  ecto, phoenix
Paper trail
Track and record all the changes in your database with Ecto. Revert back to anytime in history.
Stars: ✭ 380 (+357.83%)
Mutual labels:  ecto, phoenix

Filterable

Build Status Code Climate Coverage Status Inline docs Hex.pm

Filterable allows to map incoming query parameters to filter functions. The goal is to provide minimal and easy to use DSL for building composable queries using incoming parameters. Filterable doesn't depend on external libraries or frameworks and can be used in Phoenix or pure Elixir projects. Inspired by has_scope.

Installation

Add filterable to your mix.exs.

{:filterable, "~> 0.7.3"}

Usage

Phoenix controller

Put use Filterable.Phoenix.Controller inside Phoenix controller or add it into web.ex. It will extend controller module with filterable macro which allows to define filters. Then use apply_filters function inside controller action to filter using defined filters:

defmodule MyApp.PostController do
  use MyApp.Web, :controller
  use Filterable.Phoenix.Controller

  filterable do
    filter author(query, value, _conn) do
      query |> where(author_name: ^value)
    end

    @options param: :q
    filter search(query, value, _conn) do
      query |> where([u], ilike(u.title, ^"%#{value}%"))
    end

    @options cast: :integer
    filter year(query, value, _conn) do
      query |> where(year: ^value)
    end
  end

  # /posts?q=castle&author=Kafka&year=1926
  def index(conn, params) do
    with {:ok, query, filter_values} <- apply_filters(Post, conn),
         posts                       <- Repo.all(query),
     do: render(conn, "index.json", posts: posts, meta: filter_values)
  end
end

If you prefer to handle errors with exceptions then use apply_filters!:

def index(conn, params) do
  {query, filter_values} = apply_filters!(Post, conn)
  render(conn, "index.json", posts: Repo.all(posts), meta: filter_values)
end

Phoenix model

Put use Filterable.Phoenix.Model inside Ecto model module and define filters using filterable macro:

defmodule MyApp.Post do
  use MyApp.Web, :model
  use Filterable.Phoenix.Model

  filterable do
    filter author(query, value, _conn) do
      query |> where(author_name: ^value)
    end
  end

  schema "posts" do
    ...
  end
end

Then call apply_filters function from model module:

# /posts?author=Tom
def index(conn, params, conn) do
  with {:ok, query, filter_values} <- Post.apply_filters(conn),
       posts                       <- Repo.all(query),
   do: render(conn, "index.json", posts: posts, meta: filter_values)
end

Separate module

Filters could be defined in separate module, just use Filterable.DSL inside module to make it filterable:

defmodule PostFilters do
  use Filterable.DSL
  use Filterable.Ecto.Helpers

  field :author
  field :title

  paginateable per_page: 10

  @options param: :q
  filter search(query, value, _conn) do
    query |> where([u], ilike(u.title, ^"%#{value}%"))
  end

  @options cast: :integer
  filter year(query, value, _conn) do
    query |> where(author_name: ^value)
  end
end

defmodule MyApp.PostController do
  use MyApp.Web, :controller
  use Filterable.Phoenix.Controller

  filterable PostFilters

  # /posts?q=castle&author=Kafka&year=1926
  def index(conn, params) do
    with {:ok, query, filter_values} <- apply_filters(Post, conn),
         posts                       <- Repo.all(query),
     do: render(conn, "index.json", posts: posts, meta: filter_values)
  end
end

Defining filters

Each defined filter can be tuned with @options module attribute. Just set @options attribute before filter definition. Available options are:

:param - allows to set query parameter name, by default same as filter name. Accepts Atom, List, and Keyword values:

# /posts?q=castle
# => #Ecto.Query<from p in Post, where: ilike(u.title, ^"%castle%")>
@options param: :q
filter search(query, value, _conn) do
  query |> where([u], ilike(u.title, ^"%#{value}%"))
end

# /posts?sort=name&order=desc
# => #Ecto.Query<from p in Post, order_by: [desc: p.name]>
@options param: [:sort, :order], cast: :integer
filter search(query, %{sort: field, order: order}, _conn) do
  query |> order_by([{^order, ^field}])
end

# /posts?sort[field]=name&sort[order]=desc
# => #Ecto.Query<from p in Post, order_by: [desc: p.name]>
@options param: [sort: [:field, :order]], cast: :integer
filter search(query, %{field: field, order: order}, _conn) do
  query |> order_by([{^order, ^field}])
end

:default - allows to set default filter value:

# /posts
# => #Ecto.Query<from p in Post, limit: 20>
@options default: 20, cast: :integer
filter limit(query, value, _conn) do
  query |> limit(^value)
end

# /posts
# => #Ecto.Query<from p in Post, order_by: [desc: p.inserted_at]>
@options param: [:sort, :order], default: [sort: :inserted_at, order: :desc], cast: :atom_unchecked
filter search(query, %{sort: field, order: order}, _conn) do
  query |> order_by([{^order, ^field}])
end

:allow_blank - when true then it allows to trigger filter with blank value ("", [], {}, %{}). false by default, so all blank values will be converted to nil:

# /posts?title=""
# => #Ecto.Query<from p in Post>
@options allow_blank: false
filter title(query, value, _conn) do
  query |> where(title: ^value)
end

# /posts?title=""
# => #Ecto.Query<from p in Post, where: p.title == "">
@options allow_blank: true
filter title(query, value, _conn) do
  query |> where(title: ^value)
end

:allow_nil - when true then it allows to trigger filter with nil value, false by default:

# /posts?title=""
# => #Ecto.Query<from p in Post, where: is_nil(p.title)>
# /posts?title=Casle
# => #Ecto.Query<from p in Post, where: p.title == "Casle">
@options allow_nil: true
filter title(query, nil, _conn) do
  query |> where([q], is_nil(q.title))
end
filter title(query, value, _conn) do
  query |> where(title: ^value)
end

:trim - allows to remove leading and trailing whitespaces from string values, true by default:

# /posts?title="   Casle  "
# => #Ecto.Query<from p in Post, where: p.title == "Casle">
filter title(query, value, _conn) do
  query |> where(title: ^value)
end

# /posts?title="   Casle  "
# => #Ecto.Query<from p in Post, where: p.title == "   Casle  ">
@options trim: false
filter title(query, value, _conn) do
  query |> where(title: ^value)
end

:cast - allows to convert value to specific type. Available types are: :integer, :float, :string, {:atom, [...]}, :boolean, :date, :datetime, :atom_unchecked. Casting to atoms is a special case, as atoms are never garbage collected. It is therefore important to give a list of valid atoms. Casting will only work if the given value is in the list of atoms.

Also can accept pointer to function:

# /posts?limit=20
# => #Ecto.Query<from p in Post, limit: 20>
@options cast: :integer
filter limit(query, value, _conn) do
  query |> limit(^value)
end

# /posts?title=Casle
# => #Ecto.Query<from p in Post, where: p.title == "casle">
@options cast: &String.downcase/1
filter title(query, value, _conn) do
  query |> where(title: ^value)
end

:cast_errors - accepts true (default) or false. If true then it returns error if value can't be caster to specific type. If false - it skips filter if filter value can't be casted:

# /posts?inserted_at=Casle
# => {:error, "Unable to cast \"Casle\" to datetime"}
@options cast: :datetime
filter inserted_at(query, value, _conn) do
  query |> where(inserted_at: ^value)
end

# /posts?inserted_at=Casle
# => #Ecto.Query<from p in Post>
@options cast: :datetime, cast_errors: false
filter inserted_at(query, value, _conn) do
  query |> where(inserted_at: ^value)
end

:share - allows to set shared value. When false then filter function will be triggered without shared value argument:

@options share: false
filter title(query, value) do
  query |> where(title: ^value)
end

All these options can be specified in apply_filters function or filterable macro. Then they will take affect on all defined filters:

filterable share: false, cast_errors: false do
  field :title
end

# or

filterable PostFilters, share: false, cast_errors: false

# or

{:ok, query, filter_values} = apply_filters(conn, share: false, cast_errors: false)

Ecto helpers

Filterable.Ecto.Helpers module provides macros which allows to define some popular filters:

field/2 - expands to simple Ecto.Query.where filter:

filterable do
  field :title
  field :stars, cast: :integer
end

Same filters could be built with filter macro:

filterable do
  filter title(query, value, _conn) do
    query |> where(title: ^value)
  end

  @options cast: :integer
  filter stars(query, value, _conn) do
    query |> where(stars: ^value)
  end
end

paginateable/1 - provides pagination logic, Default amount of records per page could be tuned with per_page option. By default it's set to 20:

filterable do
  # /posts?page=3
  # => #Ecto.Query<from p in Post, limit: 10, offset: 20>
  paginateable per_page: 10
end

limitable/1 - provides limit/offset logic:

filterable do
  # /posts?limit=3offset=10
  # => #Ecto.Query<from p in Post, limit: 3, offset: 10>
  limitable limit: 10
end

orderable/1 - provides sorting logic, accepts list of atoms:

filterable do
  # /posts?sort=inserted_at&order=asc
  # => #Ecto.Query<from p in Post, order_by: [asc: p.inserted_at]>
  orderable [:title, :inserted_at]
end

Common usage

Filterable also can be used in non Ecto/Phoenix projects. Put use Filterable.DSL inside module to start defining filters:

defmodule RepoFilters do
  use Filterable.DSL

  filter name(list, value) do
    list |> Enum.filter(& &1.name == value)
  end

  @options cast: :integer
  filter stars(list, value) do
    list |> Enum.filter(& &1.stars >= value)
  end
end

Then filter collection using apply_filters function:

repos = [%{name: "phoenix", stars: 8565}, %{name: "ecto", start: 2349}]

{:ok, result, filter_values} = RepoFilters.apply_filters(repos, %{name: "phoenix", stars: "8000"})
# or
{:ok, result, filter_values} = Filterable.apply_filters(repos, %{name: "phoenix", stars: "8000"}, RepoFilters)

Code formatter

filter macro and phoenix helpers like orderable, paginateable are the part fo DSL so there is no need to wrap them in parentheses.

Just add the following line into formatter configs:

[
  # ...

  import_deps: [:filterable]
]

Similar packages

Contribution

Feel free to send your PR with proposals, improvements or corrections 😉

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].