はじめに
今回、pagination-goというライブラリを知ったので、公式のサンプルコードを実行してあそんでみました。
公式のドキュメントが英語で、Google翻訳と相性が悪い感じだったので、公式のサンプルの実行手順をまとめて共有させて頂きます。公式はこちら。
インストール方法
$ go get -u github.com/gemcook/pagination-go
$ 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
{
"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 |
パラメータをいじって、再度アクセスすると、ページネーションするのにいい感じに整形されて返されているのが確認できるかと思います。
色々触ってみて遊んでみました。
{
"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
}
]
}
}
{
"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言語の理解を深めて、ライブラリの中身の解説とかも、できるようになりたいところです。
最後まで、ありがとうございました。