Sort and search does not work with Scrinever pagination

Hello folks! I recently added search and sort features to my application. Today I tried to add pagination for my index page. Pagination works. However, when I use the search or sort feature it does not do what I expect. When I search it displays the correct number of pages but when I click on the next page it just displays paginated all books in the DB. When I use sorting it shows sorted results on the first page but again, the second page is just a second page of paginated records without any order. Could you please look at the code and help me to solve this problem? I’m guessing that my code is far from perfect but I am still learning and I’m open to any suggestions to make changes to the code.

I have followed the hexdocs instructions for scrivener_ecto and scrivener_html. I attach my index code from book_controller.

  def index(conn, params) do
    page =
      Bookstore.Inventory.list_books(params)
      |> BookstoreWeb.ViewHelpers.sort_by_params(params, @book_sort_fields)
      |> Repo.paginate(params)

    # |> Repo.preload(:author)
    # |> Repo.preload(:category)
    # |> Repo.preload(:publisher)

    render(conn, "index.html", books: page.entries, page: page)
  end

EDIT: I got working search and paginating results with:
Book index

<%= pagination_links @page, query: @query, next: "Next", previous: "Prev" %>

Book controller

  def index(conn, params) do
    books = Bookstore.Inventory.list_books(params)

    search_term =
      get_in(params, ["query"])
      |> BookstoreWeb.ViewHelpers.sort_by_params(params, @book_sort_fields)

    # |> Repo.preload(:author)
    # |> Repo.preload(:category)
    # |> Repo.preload(:publisher)

    render(conn, "index.html", books: books.entries, page: books, query: search_term)
  end

Inventory

  def list_books(params) do
    search_term = get_in(params, ["query"])

    Book
    |> Bookstore.Inventory.Book.search(search_term)
    |> preload(:category)
    |> preload(:author)
    |> preload(:publisher)
    |> Repo.paginate(params)
  end

However, when I want to sort search results I got this error:

# unknown bind name :book in query:
When I do not use search and just try to sort books I see this error:

protocol Ecto.Queryable not implemented for nil of type Atom, the given module does not exist. This protocol is implemented for the following type(s): BitString, Tuple, Ecto.Query, Ecto.Query, Ecto.SubQuery, BitString, Tuple, Atom, Ecto.SubQuery, Atom

Both errors point to:

  defp do_sort_by_params(qs, val, ord, allowed) do
    if val in allowed do
      [binding, name] = val |> String.split("__") |> Enum.map(&String.to_atom/1)
      qs |> order_by([{^binding, t}], [{^ord, field(t, ^name)}])  #THIS LINE
    else
      qs
    end
  end

EDIT 2:
I have been tweaking the code one way and another but I’m stuck with Search + pagination working but when I want to sort found books/all books records on the first page are correct. However, on every other page sorting does not apply. After I search and use sort the URL looks like that:

/books?order_by=-book__title&query=se

When I go to the second page order_by is not passed.

books?query=se&page=2

I will attach more code if needed.

Hi @Kapeusz, it seems that you have a lot going on here and it might not be related to Scrivener only.
FYI: I’m using Scrivener in production and the only problems I found with the lib are already being tracked in this few issues. However, my suggestion is that you ask for help on the Scrivener repo if you find bugs or the library documentation is insufficient.

Check if you’re passing the correct params to your functions and if the modules you’re referencing are available and properly aliased. This error indicates that you’re trying to query (or do something with a Queryable) from a nil value, that’s why it’s expecting the protocol Ecto.Queryable to be implemented.

This is roughly what I’m doing today for sorting:

def sort(query, %{"sort" => sort, "direction" => direction}, opts \\ []) do
  sort = String.to_existing_atom(sort)
  direction = String.to_existing_atom(direction)

  query
  |> Ecto.Queryable.to_query()
  |> order_by({^direction, ^sort})
end

And then call from the controller passing the params:

def list_paginated_entities(params) do
  from e in Entity
  |> Repo.sort(params, desc: :inserted_at)
  |> Repo.paginate(params)
end

Now you just have to make sure you are passing the proper options to the Scrivener helper that generates the links (Scrivener.HTML.pagination_links/4 if I’m not mistaken). If you are not getting the proper params in your URLs, check the inputs before calling the functions - implementing some tests could help to find the culprit in those cases.

This sounds like the issue reported in issue with paginate url params · Issue #70 · mgwidmann/scrivener_html · GitHub - the link helpers require you to pass the options through explicitly.

Yes, you are right, thank you for the link to this issue. Right now I have something like:
Index.html.eex

<%= pagination_links @page, order_by: @order_by, query: @query, next: "Next", previous: "Prev" %>

Inventory.ex

 def list_books(params) do
    search_term = get_in(params, ["query"])
    sort_params = get_in(params, ["order_by"])
    Book
    |> Bookstore.Inventory.Book.search(search_term)
    |> BookstoreWeb.ViewHelpers.sort_by_params(sort_params, @book_sort_fields)
    |> preload(:category)
    |> preload(:author)
    |> preload(:publisher)
    |> Repo.paginate(params)
  end

book_controller.ex

  def index(conn, params) do
    search_term = get_in(params, ["query"])
    sort_params = get_in(params, ["order_by"])
    books = Bookstore.Inventory.list_books(params)
    render(conn, "index.html",
      books: books.entries,
      page: books,
      query: search_term,
      order_by: sort_params
    )
  end

I see that parameters are passed and pagination works but sorting still does not work even though URL looks okay.

[debug] Processing with BookstoreWeb.BookController.index/2
  Parameters: %{"order_by" => "-book__title", "query" => "se"}
  Pipelines: [:browser]
http://localhost:4000/books?order_by=-book__title&query=se
And then
http://localhost:4000/books?order_by=-book__title&query=se&page=2

Do you have any idea what I can be doing wrong?

EDIT:
Got it working, no need further help. :slight_smile: