Dissolver v0.9.4 Dissolver View Source

NOTE: This is a wip repo. So be warned there maybe be bugs.

This project is a fork of https://github.com/elixirdrops/kerosene.
I thought to take it over because it does not look as if its being activly developed and I wanted more features.

TODO:

  • [x] Lazy query - Instead of pagination calling Repo.all it will return an Ecto query. This is useful for subqueries where you will passying the query to something like a preload.
  • [x] Custom themes - Now you can pass a module as the source of your theme.
  • [ ] Refactor all the test so that the modules only exposed required interfaces.
  • [ ] The way this lib queries for total counts is a bit odd since it's trying to account for groub_by and multi sourced froms. I'm going to see if we can't make this cleaner.
  • [ ] Refactor namespace of functions. General clean up of internal methods.

CICoverage Status

Pagination for Ecto and Phoenix.

Installation

add Dissolver to your mix.exs dependencies:

def deps do
  [
    {:dissolver, "~> 0.9.4"}
  ]
end

Next provide Dissolver your Repo module via the config. Add the following to your config:

...
config :dissolver,
  repo: MyApp.Repo
  per_page: 2

import_config "#{Mix.env()}.exs"

For more information about the configuration options look at the Configurations section

Now you are ready to start using Dissolver.

Usage

Start paginating your queries

def index(conn, params) do
  {products, paginator} =
  Product
  |> Product.with_lowest_price
  |> Dissolver.paginate(params)

  render(conn, "index.html", products: products, paginator: paginator)
end

Add the view helper to your view

defmodule MyApp.ProductView do
  use MyApp.Web, :view
  import Dissolver.HTML
end

Generate the links using the view helper in your template

<%= paginate @conn, @paginator %>

Importing Dissolver.HTML provides your template access to Dissolver.HTML.paginate/3 as the prior example shows. The Dissolver.HTML.paginate/3 can take a host of options to aid the theme from how many links to show (window: integer) to what the link labels should read.

By default the theme used to generate the html is the Dissolver.HTML.Simple theme. It will only provide the very basic prev|next buttons. For more theme options, including providing your own, read the following Configurations

Configuration

This module uses the following that can be set as globals in your config/config.ex configurations

JSON API Support.

defmodule MyApp.ProductView do
  use MyApp.Web, :view
  import Dissolver.JSON

  def render("index.json", %{products: products, dissolver: dissolver, conn: conn}) do
    %{data: render_many(products, MyApp.ProductView, "product.json"),
      pagination: paginate(conn, dissolver)}
  end

  def render("product.json", %{product: product}) do
    %{id: product.id,
      name: product.name,
      description: product.description,
      price: product.price}
  end
end

Lazy Example.

Say you have a tag that has many posts and you want to paginate the post as they relate to a given tag.

Heres an example of using the lazy option so that Dissolver.paginate returns a query rather than a result.

def get_post_for_tag!(tag_name, params \ %{}) do
    total_count =
      from(t in "tags",
        join: pt in "post_tags",
        on: pt.tag_id == t.id,
        where: t.name == ^tag_name,
        select: count()
      )
      |> Repo.one()

    {posts_query, paginator} =
      from(p in Post, order_by: [desc: :inserted_at], preload: [:tags])
      |> Dissolver.paginate(params, total_count: total_count, lazy: true)

    tag =
      from(t in Tag, where: t.name == ^tag_name, preload: [posts: ^posts_query])
      |> Repo.one!()

    {tag, paginator}
  end

You will notice that in this case I also have to supply total_count since Dissolver.paginate does not currently have a way to scope total_count of a given tag since they query is applied after Dissolver.paginate was called. So for this use case we just supply the total count up front. For my controller it looks just like most.

def show_post(conn, %{"tag" => tag} = params) do
  {tag, paginator} = Blog.get_post_for_tag!(tag, params)
  render(conn, "show_posts.html", tag: tag, paginator: paginator)
end

You can also send in options to paginate helper look at the docs for more details.

Contributing

If you can start by writing an issue ticket. Then if you like feel free to fork and submit a PR for review.

Acknowledgement

I would like to Thank

  • Matt (@mgwidmann)
  • Drew Olson (@drewolson)
  • Akira Matsuda (@amatsuda)

License

Please take a look at LICENSE.md

Link to this section Summary

Link to this section Functions

Link to this function

paginate(query, params, opts \\ [])

View Source
paginate(Ecto.Query.t(), map(), nil | keyword()) ::
  {list(), Dissolver.Paginator.t()}