Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
drewolson committed Apr 1, 2016
0 parents commit 3ef0e5f
Show file tree
Hide file tree
Showing 22 changed files with 546 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
@@ -0,0 +1,5 @@
/_build
/deps
/doc
erl_crash.dump
*.ez
8 changes: 8 additions & 0 deletions .travis.yml
@@ -0,0 +1,8 @@
language: elixir
otp_release:
- 18.2
elixir:
- 1.2.3
before_script:
- export SCRIVENER_ECTO_DB_USER=postgres
- MIX_ENV=test mix scrivener.ecto.db.reset
5 changes: 5 additions & 0 deletions CHANGELOG.md
@@ -0,0 +1,5 @@
# Changelog

## 1.0.0

* Initial release
21 changes: 21 additions & 0 deletions LICENSE
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2016 Andrew Olson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
82 changes: 82 additions & 0 deletions README.md
@@ -0,0 +1,82 @@
# Scrivener.Ecto

[![Build Status](https://travis-ci.org/drewolson/scrivener_ecto.svg)](https://travis-ci.org/drewolson/scrivener_ecto) [![Hex Version](http://img.shields.io/hexpm/v/scrivener_ecto.svg?style=flat)](https://hex.pm/packages/scrivener_ecto) [![Hex docs](http://img.shields.io/badge/hex.pm-docs-green.svg?style=flat)](https://hexdocs.pm/scrivener_ecto)

Scrivener.Ecto allows you to paginate your Ecto queries with Scrivener. It gives you useful information such as the total number of pages, the current page, and the current page's entries. It works nicely with Phoenix as well.

First, you'll want to `use` Scrivener in your application's Ecto Repo. This will add a `paginate` function to your Repo. This `paginate` function expects to be called with, at a minimum, an Ecto query. It will then paginate the query and execute it, returning a `Scrivener.Page`. Defaults for `page_size` can be configued when you `use` Scrivener. If no `page_size` is provided, Scrivener will use `10` by default.

You may also want to call `paginate` with a params map along with your query. If provided with a params map, Scrivener will use the values in the keys `"page"` and `"page_size"` before using any configured defaults.

## Example

```elixir
defmodule MyApp.Repo do
use Ecto.Repo, otp_app: :my_app
use Scrivener, page_size: 10
end
```

```elixir
defmodule MyApp.Person do
use Ecto.Schema

schema "people" do
field :name, :string
field :age, :integer

has_many :friends, MyApp.Person
end
end
```

```elixir
def index(conn, params) do
page = MyApp.Person
|> where([p], p.age > 30)
|> order_by([p], desc: p.age)
|> preload(:friends)
|> MyApp.Repo.paginate(params)

render conn, :index,
people: page.entries,
page_number: page.page_number,
page_size: page.page_size,
total_pages: page.total_pages,
total_entries: page.total_entries
end
```

```elixir
page = MyApp.Person
|> where([p], p.age > 30)
|> order_by([p], desc: p.age)
|> preload(:friends)
|> MyApp.Repo.paginate(page: 2, page_size: 5)
```

## Installation

Add `scrivener_ecto` to your `mix.exs` dependencies.

```elixir
defp deps do
[{:scrivener_ecto, "~> 1.0"}]
end
```

## Contributing

First, you'll need to build the test database.

```elixir
MIX_ENV=test mix scrivener.ecto.db.reset
```

This task assumes you have postgres installed and that your current user can create / drop databases. If you'd prefer to use a different user, you can specify it with the environment variable `SCRIVENER_ECTO_DB_USER`.

With the database built, you can now run the tests.

```elixir
mix test
```
3 changes: 3 additions & 0 deletions config/config.exs
@@ -0,0 +1,3 @@
use Mix.Config

import_config "#{Mix.env}.exs"
1 change: 1 addition & 0 deletions config/dev.exs
@@ -0,0 +1 @@
use Mix.Config
1 change: 1 addition & 0 deletions config/prod.exs
@@ -0,0 +1 @@
use Mix.Config
10 changes: 10 additions & 0 deletions config/test.exs
@@ -0,0 +1,10 @@
use Mix.Config

config :scrivener_ecto, ScrivenerEcto.Repo,
adapter: Ecto.Adapters.Postgres,
pool: Ecto.Adapters.SQL.Sandbox,
database: "scrivener_test",
username: System.get_env("SCRIVENER_ECTO_DB_USER") || System.get_env("USER")

config :logger, :console,
level: :error
13 changes: 13 additions & 0 deletions lib/mix/tasks/scrivener/ecto/db/reset.ex
@@ -0,0 +1,13 @@
defmodule Mix.Tasks.Scrivener.Ecto.Db.Reset do
use Mix.Task

@moduledoc false

def run(_args) do
Logger.configure(level: :error)

Mix.Task.run("ecto.drop", [])
Mix.Task.run("ecto.create", [])
Mix.Task.run("ecto.migrate", [])
end
end
56 changes: 56 additions & 0 deletions lib/scrivener/paginater/ecto/query.ex
@@ -0,0 +1,56 @@
defimpl Scrivener.Paginater, for: Ecto.Query do
import Ecto.Query

alias Scrivener.Config
alias Scrivener.Page

@spec paginate(Ecto.Query.t, Scrivener.Config.t) :: Scrivener.Page.t
def paginate(query, %Config{page_size: page_size, page_number: page_number, module: repo}) do
total_entries = total_entries(query, repo)

%Page{
page_size: page_size,
page_number: page_number,
entries: entries(query, repo, page_number, page_size),
total_entries: total_entries,
total_pages: total_pages(total_entries, page_size)
}
end

defp ceiling(float) do
t = trunc(float)

case float - t do
neg when neg < 0 ->
t
pos when pos > 0 ->
t + 1
_ -> t
end
end

defp entries(query, repo, page_number, page_size) do
offset = page_size * (page_number - 1)

query
|> limit([_], ^page_size)
|> offset([_], ^offset)
|> repo.all
end

defp total_entries(query, repo) do
stripped_query = query
|> exclude(:order_by)
|> exclude(:preload)
|> exclude(:select)

{query_sql, parameters} = Ecto.Adapters.SQL.to_sql(:all, repo, stripped_query)
{:ok, %{num_rows: 1, rows: [[count]]}} = Ecto.Adapters.SQL.query(repo, "SELECT count(*) FROM (#{query_sql}) AS temp", parameters)

count
end

defp total_pages(total_entries, page_size) do
ceiling(total_entries / page_size)
end
end
56 changes: 56 additions & 0 deletions mix.exs
@@ -0,0 +1,56 @@
defmodule Scrivener.Ecto.Mixfile do
use Mix.Project

def project do
[
app: :scrivener_ecto,
version: "1.0.0-dev",
elixir: "~> 1.0",
elixirc_paths: elixirc_paths(Mix.env),
package: package,
description: "Paginate your Ecto queries with Scrivener",
deps: deps,
docs: [
main: "README.md",
readme: "README.md"
]
]
end

def application do
[
applications: applications(Mix.env)
]
end

defp applications(:test), do: [:postgrex, :ecto, :logger]
defp applications(_), do: [:logger]

defp deps do
[
{:scrivener, git: "https://github.com/drewolson/scrivener", branch: "v2"},
{:ecto, "~> 2.0.0-beta"},
{:dialyze, "~> 0.2.0", only: :dev},
{:earmark, ">= 0.0.0", only: :dev},
{:ex_doc, "~> 0.11.0", only: :dev},
{:ex_spec, "~> 1.0", only: :test},
{:postgrex, ">= 0.0.0", optional: true}
]
end

defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]

defp package do
[
maintainers: ["Drew Olson"],
licenses: ["MIT"],
links: %{"github" => "https://github.com/drewolson/scrivener_ecto"},
files: [
"lib/scrivener",
"mix.exs",
"README.md"
]
]
end
end
11 changes: 11 additions & 0 deletions mix.lock
@@ -0,0 +1,11 @@
%{"connection": {:hex, :connection, "1.0.2"},
"db_connection": {:hex, :db_connection, "0.2.5"},
"decimal": {:hex, :decimal, "1.1.1"},
"dialyze": {:hex, :dialyze, "0.2.1"},
"earmark": {:hex, :earmark, "0.2.1"},
"ecto": {:hex, :ecto, "2.0.0-beta.2"},
"ex_doc": {:hex, :ex_doc, "0.11.4"},
"ex_spec": {:hex, :ex_spec, "1.0.0"},
"poolboy": {:hex, :poolboy, "1.5.1"},
"postgrex": {:hex, :postgrex, "0.11.1"},
"scrivener": {:git, "https://github.com/drewolson/scrivener", "18fddf641b77d88ec7462e963198ed086601c2f0", [branch: "v2"]}}
13 changes: 13 additions & 0 deletions priv/repo/migrations/1_create_posts.exs
@@ -0,0 +1,13 @@
defmodule TestRepo.Migrations.CreatePosts do
use Ecto.Migration

def change do
create table(:posts) do
add :title, :string
add :body, :string
add :published, :boolean

timestamps
end
end
end
12 changes: 12 additions & 0 deletions priv/repo/migrations/2_create_comments.exs
@@ -0,0 +1,12 @@
defmodule Scrivener.Repo.Migrations.CreateComments do
use Ecto.Migration

def change do
create table(:comments) do
add :body, :string
add :post_id, :integer

timestamps
end
end
end
10 changes: 10 additions & 0 deletions priv/repo/migrations/3_create_key_values.exs
@@ -0,0 +1,10 @@
defmodule TestRepo.Migrations.CreateKeyValues do
use Ecto.Migration

def change do
create table(:key_values, primary_key: false) do
add :key, :string, primary_key: true
add :value, :string
end
end
end

0 comments on commit 3ef0e5f

Please sign in to comment.