LoginSignup
5
5

More than 3 years have passed since last update.

[Golang]ページャーに便利なライブラリ、「pagination-go」を触ってみた

Last updated at Posted at 2019-09-17

はじめに

今回、pagination-goというライブラリを知ったので、公式のサンプルコードを実行してあそんでみました。
公式のドキュメントが英語で、Google翻訳と相性が悪い感じだったので、公式のサンプルの実行手順をまとめて共有させて頂きます。公式はこちら

インストール方法

go getでのインストールコマンド
$ go get -u github.com/gemcook/pagination-go
depでのインストールコマンド
$ dep ensure -add github.com/gemcook/pagination-go

動作確認方法

以下のサンプルコードを全文コピペして、main.goなど適当な名前で保存してください。

公式のサンプルコード

公式より全文
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "net/url"
    "sort"
    "strconv"
    "strings"

    pagination "github.com/gemcook/pagination-go"
)

type fruit struct {
    Name  string
    Price int
}

var dummyFruits = []fruit{
    fruit{"Apple", 112},
    fruit{"Pear", 245},
    fruit{"Banana", 60},
    fruit{"Orange", 80},
    fruit{"Kiwi", 106},
    fruit{"Strawberry", 350},
    fruit{"Grape", 400},
    fruit{"Grapefruit", 150},
    fruit{"Pineapple", 200},
    fruit{"Cherry", 140},
    fruit{"Mango", 199},
}

type fruitsRepository struct {
    priceLowerLimit  int
    priceHigherLimit int
}

func newFruitsRepository() *fruitsRepository {
    return &fruitsRepository{
        priceLowerLimit:  -1 << 31,
        priceHigherLimit: 1<<31 - 1,
    }
}

func (fr *fruitsRepository) GetFruits(orders []*pagination.Order) []fruit {
    result := make([]fruit, 0)
    for _, f := range dummyFruits {
        if fr.priceHigherLimit >= f.Price && f.Price >= fr.priceLowerLimit {
            result = append(result, f)
        }
    }

    for _, o := range orders {
        if o.ColumnName != "price" {
            continue
        }
        sort.SliceStable(result, func(i, j int) bool {
            if o.Direction == pagination.DirectionAsc {
                return result[i].Price < result[j].Price
            }

            return result[i].Price > result[j].Price
        })
    }

    return result
}

type fruitCondition struct {
    PriceLowerLimit  *int
    PriceHigherLimit *int
}

func newFruitCondition(low, high int) *fruitCondition {
    return &fruitCondition{
        PriceLowerLimit:  &low,
        PriceHigherLimit: &high,
    }
}

func parseFruitCondition(queryStr string) *fruitCondition {
    u, err := url.Parse(queryStr)
    if err != nil {
        fmt.Println(err)
        low := -1 << 31
        high := 1<<31 - 1
        return newFruitCondition(low, high)
    }
    query := u.Query()

    if s := query.Get("price_range"); s != "" {
        prices := strings.Split(s, ",")
        low, err := strconv.Atoi(prices[0])
        if err != nil {
            panic(err)
        }
        high, err := strconv.Atoi(prices[1])
        if err != nil {
            panic(err)
        }
        return newFruitCondition(low, high)
    }

    low := -1 << 31
    high := 1<<31 - 1
    return newFruitCondition(low, high)
}

type fruitFetcher struct {
    repo *fruitsRepository
}

func newFruitFetcher() *fruitFetcher {
    return &fruitFetcher{
        repo: &fruitsRepository{},
    }
}

func (ff *fruitFetcher) applyCondition(cond *fruitCondition) {
    if cond.PriceHigherLimit != nil {
        ff.repo.priceHigherLimit = *cond.PriceHigherLimit
    }
    if cond.PriceLowerLimit != nil {
        ff.repo.priceLowerLimit = *cond.PriceLowerLimit
    }
}

func (ff *fruitFetcher) Count(cond interface{}) (int, error) {
    if cond != nil {
        ff.applyCondition(cond.(*fruitCondition))
    }
    orders := make([]*pagination.Order, 0, 0)
    fruits := ff.repo.GetFruits(orders)
    return len(fruits), nil
}

func (ff *fruitFetcher) FetchPage(cond interface{}, input *pagination.PageFetchInput, result *pagination.PageFetchResult) error {
    if cond != nil {
        ff.applyCondition(cond.(*fruitCondition))
    }
    fruits := ff.repo.GetFruits(input.Orders)
    var toIndex int
    toIndex = input.Offset + input.Limit
    if toIndex > len(fruits) {
        toIndex = len(fruits)
    }
    for _, fruit := range fruits[input.Offset:toIndex] {
        *result = append(*result, fruit)
    }
    return nil
}

func handler(w http.ResponseWriter, r *http.Request) {
    // RequestURI: https://example.com/fruits?limit=10&page=1&price_range=100,300&sort=+price
    p := pagination.ParseQuery(r.URL.RequestURI())
    cond := parseFruitCondition(r.URL.RequestURI())
    fetcher := newFruitFetcher()

    totalCount, totalPages, res, err := pagination.Fetch(fetcher, &pagination.Setting{
        Limit:  p.Limit,
        Page:   p.Page,
        Cond:   cond,
        Orders: p.Sort,
    })

    if err != nil {
        w.Header().Set("Content-Type", "text/html; charset=utf8")
        w.WriteHeader(400)
        fmt.Fprintf(w, "something wrong: %v", err)
        return
    }

    w.Header().Set("X-Total-Count", strconv.Itoa(totalCount))
    w.Header().Set("X-Total-Pages", strconv.Itoa(totalPages))
    w.Header().Set("Access-Control-Expose-Headers", "X-Total-Count,X-Total-Pages")
    w.Header().Set("Content-Type", "application/json; charset=utf-8")
    w.WriteHeader(200)
    resJSON, _ := json.Marshal(res)
    w.Write(resJSON)
}

func main() {
    http.HandleFunc("/fruits", handler)
    fmt.Println("server is listening on port 8080")
    fmt.Println("try http://localhost:8080/fruits?limit=2&page=1&price_range=100,300&sort=+price")
    http.ListenAndServe(":8080", nil)
}

手順

ターミナルやコマンドプロンプト等で、カレントディレクトリをファイル保存したところまで移動し、以下のコマンドを入力して実行します。

実行コマンド
$ go run main.go

main.goのところは、違う名前で保存されている方は、そのファイル名で実行して下さい。

実行したら表示されるもの
server is listening on port 8080
try http://localhost:8080/fruits?limit=2&page=1&price_range=100,300&sort=+price

一瞬エラーコードかと思いきや、URLが書かれていてそこにアクセスすればOKです。
抜粋すると下のURLです。

http://localhost:8080/fruits?limit=2&page=1&price_range=100,300&sort=+price

ブラウザ出力結果(JSON整形済)
{
    "pages": {
        "active": [
            {
                "Name": "Kiwi",
                "Price": 106
            },
            {
                "Name": "Apple",
                "Price": 112
            }
        ],
        "after_distant": null,
        "after_near": [
            {
                "Name": "Pear",
                "Price": 245
            }
        ],
        "before_distant": [
            {
                "Name": "Cherry",
                "Price": 140
            },
            {
                "Name": "Grapefruit",
                "Price": 150
            }
        ],
        "before_near": [
            {
                "Name": "Mango",
                "Price": 199
            },
            {
                "Name": "Pineapple",
                "Price": 200
            }
        ],
        "first": [
            {
                "Name": "Kiwi",
                "Price": 106
            },
            {
                "Name": "Apple",
                "Price": 112
            }
        ],
        "last": [
            {
                "Name": "Pear",
                "Price": 245
            }
        ]
    }
}

ページネーションに使いやすそうな形で、きっちりとJSONファイルが返っています。
これは便利そう。

ページネーションの詳細を設定するには

先ほどアクセスしたURLのうち、
limit=2&page=1&price_range=100,300&sort=+price
のパラメーターを、以下表に沿って変更するだけでOKです。

query parameter Mapped field required expected value default value
limit Limit no positive integer 10
page Page no positive integer (1~) 1
pagination Enabled no boolean true

パラメータをいじって、再度アクセスすると、ページネーションするのにいい感じに整形されて返されているのが確認できるかと思います。
色々触ってみて遊んでみました。

limit=2&page=1&price_range=300,350&sort=+price
{
    "pages": {
        "active": [
            {
                "Name": "Strawberry",
                "Price": 350
            }
        ],
        "after_distant": null,
        "after_near": null,
        "before_distant": null,
        "before_near": null,
        "first": [
            {
                "Name": "Strawberry",
                "Price": 350
            }
        ],
        "last": [
            {
                "Name": "Strawberry",
                "Price": 350
            }
        ]
    }
}
limit=4&page=1&price_range=0,500&sort=+price
{
    "pages": {
        "active": [
            {
                "Name": "Banana",
                "Price": 60
            },
            {
                "Name": "Orange",
                "Price": 80
            },
            {
                "Name": "Kiwi",
                "Price": 106
            },
            {
                "Name": "Apple",
                "Price": 112
            }
        ],
        "after_distant": null,
        "after_near": null,
        "before_distant": [
            {
                "Name": "Cherry",
                "Price": 140
            },
            {
                "Name": "Grapefruit",
                "Price": 150
            },
            {
                "Name": "Mango",
                "Price": 199
            },
            {
                "Name": "Pineapple",
                "Price": 200
            }
        ],
        "before_near": [
            {
                "Name": "Pear",
                "Price": 245
            },
            {
                "Name": "Strawberry",
                "Price": 350
            },
            {
                "Name": "Grape",
                "Price": 400
            }
        ],
        "first": [
            {
                "Name": "Banana",
                "Price": 60
            },
            {
                "Name": "Orange",
                "Price": 80
            },
            {
                "Name": "Kiwi",
                "Price": 106
            },
            {
                "Name": "Apple",
                "Price": 112
            }
        ],
        "last": [
            {
                "Name": "Pear",
                "Price": 245
            },
            {
                "Name": "Strawberry",
                "Price": 350
            },
            {
                "Name": "Grape",
                "Price": 400
            }
        ]
    }
}

簡単に色々できておもしろい。

最後に

今回は、公式のライブラ入りを触ってみただけですが、共有させて頂きました。
もう少しGo言語の理解を深めて、ライブラリの中身の解説とかも、できるようになりたいところです。

最後まで、ありがとうございました。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5