github.com/unionj-cloud/go-doudou/v2@v2.3.5/toolkit/pagination/gorm/README.md (about) 1 # paginate - Gorm Pagination 2 3 [](https://pkg.go.dev/github.com/morkid/paginate) 4 [](https://circleci.com/gh/morkid/paginate) 5 [](https://github.com/morkid/paginate/actions) 6 [](https://travis-ci.com/morkid/paginate) 7 [](https://goreportcard.com/report/github.com/morkid/paginate) 8 [](https://github.com/morkid/paginate/releases) 9 10 Simple way to paginate [Gorm](https://github.com/go-gorm/gorm) result. **paginate** is compatible with [net/http](https://golang.org/pkg/net/http/) and [fasthttp](https://github.com/valyala/fasthttp). This library also supports many frameworks are based on net/http or fasthttp. 11 12 ## Table Of Contents 13 - [Installation](#installation) 14 - [Configuration](#configuration) 15 - [Paginate using http request](#paginate-using-http-request) 16 - [Example usage](#example-usage) 17 - [net/http](#nethttp-example) 18 - [Fasthttp](#fasthttp-example) 19 - [Mux Router](#mux-router-example) 20 - [Fiber](#fiber-example) 21 - [Echo](#echo-example) 22 - [Gin](#gin-example) 23 - [Martini](#martini-example) 24 - [Beego](#beego-example) 25 - [jQuery DataTable Integration](#jquery-datatable-integration) 26 - [jQuery Select2 Integration](#jquery-select2-integration) 27 - [Filter format](#filter-format) 28 - [Customize default configuration](#customize-default-configuration) 29 - [Override results](#override-results) 30 - [Field Selector](#field-selector) 31 - [Dynamic Field Selector](#dynamic-field-selector) 32 - [Speed up response with cache](#speed-up-response-with-cache) 33 - [In Memory Cache](#in-memory-cache) 34 - [Disk Cache](#disk-cache) 35 - [Redis Cache](#redis-cache) 36 - [Elasticsearch Cache](#elasticsearch-cache) 37 - [Custom cache](#custom-cache) 38 - [Clean up cache](#clean-up-cache) 39 - [Limitations](#limitations) 40 - [License](#license) 41 42 ## Installation 43 44 ```bash 45 go get -u github.com/morkid/paginate 46 ``` 47 48 ## Configuration 49 50 ```go 51 var db *gorm.DB = ... 52 var req *http.Request = ... 53 // or 54 // var req *fasthttp.Request 55 56 model := db.Where("id > ?", 1).Model(&Article{}) 57 pg := paginate.New() 58 page := pg.Response(model, req, &[]Article{}) 59 // or 60 page := pg.With(model).Request(req).Response(&[]Article{}) 61 62 log.Println(page.Total) 63 log.Println(page.Items) 64 log.Println(page.First) 65 log.Println(page.Last) 66 67 ``` 68 you can customize config with `paginate.Config` struct. 69 ```go 70 pg := paginate.New(&paginate.Config{ 71 DefaultSize: 50, 72 }) 73 ``` 74 see more about [customize default configuration](#customize-default-configuration). 75 76 > Note that `Response` was marked as a deprecated function. Please use `With` instead. 77 > Old: `pg.Response(model, req, &[]Article{})`, 78 > New: `pg.With(model).Request(req).Response(&[]Article{})` 79 80 ## Paginate using http request 81 example paging, sorting and filtering: 82 1. `http://localhost:3000/?size=10&page=0&sort=-name` 83 produces: 84 ```sql 85 SELECT * FROM user ORDER BY name DESC LIMIT 10 OFFSET 0 86 ``` 87 `JSON` response: 88 ```js 89 { 90 // result items 91 "items": [ 92 { 93 "id": 1, 94 "name": "john", 95 "age": 20 96 } 97 ], 98 "page": 0, // current selected page 99 "size": 10, // current limit or size per page 100 "max_page": 0, // maximum page 101 "total_pages": 1, // total pages 102 "total": 1, // total matches including next page 103 "visible": 1, // total visible on current page 104 "last": true, // if response is first page 105 "first": true // if response is last page 106 } 107 ``` 108 2. `http://localhost:3000/?size=10&page=1&sort=-name,id` 109 produces: 110 ```sql 111 SELECT * FROM user ORDER BY name DESC, id ASC LIMIT 10 OFFSET 10 112 ``` 113 3. `http://localhost:3000/?filters=["name","john"]` 114 produces: 115 ```sql 116 SELECT * FROM user WHERE name = 'john' LIMIT 10 OFFSET 0 117 ``` 118 4. `http://localhost:3000/?filters=["name","like","john"]` 119 produces: 120 ```sql 121 SELECT * FROM user WHERE name LIKE '%john%' LIMIT 10 OFFSET 0 122 ``` 123 5. `http://localhost:3000/?filters=["age","between",[20, 25]]` 124 produces: 125 ```sql 126 SELECT * FROM user WHERE ( age BETWEEN 20 AND 25 ) LIMIT 10 OFFSET 0 127 ``` 128 6. `http://localhost:3000/?filters=[["name","like","john%25"],["OR"],["age","between",[20, 25]]]` 129 produces: 130 ```sql 131 SELECT * FROM user WHERE ( 132 (name LIKE '%john\%%' ESCAPE '\') OR (age BETWEEN (20 AND 25)) 133 ) LIMIT 10 OFFSET 0 134 ``` 135 7. `http://localhost:3000/?filters=[[["name","like","john"],["AND"],["name","not like","doe"]],["OR"],["age","between",[20, 25]]]` 136 produces: 137 ```sql 138 SELECT * FROM user WHERE ( 139 ( 140 (name LIKE '%john%') 141 AND 142 (name NOT LIKE '%doe%') 143 ) 144 OR 145 (age BETWEEN (20 AND 25)) 146 ) LIMIT 10 OFFSET 0 147 ``` 148 8. `http://localhost:3000/?filters=["name","IS NOT",null]` 149 produces: 150 ```sql 151 SELECT * FROM user WHERE name IS NOT NULL LIMIT 10 OFFSET 0 152 ``` 153 9. Using `POST` method: 154 ```bash 155 curl -X POST \ 156 -H 'Content-type: application/json' \ 157 -d '{"page":"1","size":"20","sort":"-name","filters":["name","john"]}' \ 158 http://localhost:3000/ 159 ``` 160 161 ## Example usage 162 163 ### NetHTTP Example 164 165 ```go 166 package main 167 168 import ( 169 "github.com/morkid/paginate" 170 ... 171 ) 172 173 func main() { 174 // var db *gorm.DB 175 pg := paginate.New() 176 177 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 178 model := db.Joins("User").Model(&Article{}) 179 paginated := pg.Response(model, r, &[]Article{}) 180 j, _ := json.Marshal(paginated) 181 w.Header().Set("Content-type", "application/json") 182 w.Write(j) 183 }) 184 185 log.Fatal(http.ListenAndServe(":3000", nil)) 186 } 187 ``` 188 189 ### Fasthttp Example 190 191 ```go 192 package main 193 194 import ( 195 "github.com/morkid/paginate" 196 ... 197 ) 198 199 func main() { 200 // var db *gorm.DB 201 pg := paginate.New() 202 203 fasthttp.ListenAndServe(":3000", func(ctx *fasthttp.RequestCtx) { 204 model := db.Joins("User").Model(&Article{}) 205 paginated := pg.Response(model, &ctx.Request, &[]Article{}) 206 j, _ := json.Marshal(paginated) 207 ctx.SetContentType("application/json") 208 ctx.SetBody(j) 209 }) 210 } 211 ``` 212 213 ### Mux Router Example 214 ```go 215 package main 216 217 import ( 218 "github.com/morkid/paginate" 219 ... 220 ) 221 222 func main() { 223 // var db *gorm.DB 224 pg := paginate.New() 225 app := mux.NewRouter() 226 app.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 227 model := db.Joins("User").Model(&Article{}) 228 paginated := pg.Response(model, req, &[]Article{}) 229 j, _ := json.Marshal(paginated) 230 w.Header().Set("Content-type", "application/json") 231 w.Write(j) 232 }).Methods("GET") 233 http.Handle("/", app) 234 http.ListenAndServe(":3000", nil) 235 } 236 ``` 237 238 ### Fiber example 239 240 ```go 241 package main 242 243 import ( 244 "github.com/morkid/paginate" 245 ... 246 ) 247 248 func main() { 249 // var db *gorm.DB 250 pg := paginate.New() 251 app := fiber.New() 252 app.Get("/", func(c *fiber.Ctx) error { 253 model := db.Joins("User").Model(&Article{}) 254 return c.JSON(pg.Response(model, c.Request(), &[]Article{})) 255 }) 256 257 app.Listen(":3000") 258 } 259 ``` 260 261 ### Echo example 262 263 ```go 264 package main 265 266 import ( 267 "github.com/morkid/paginate" 268 ... 269 ) 270 271 func main() { 272 // var db *gorm.DB 273 pg := paginate.New() 274 app := echo.New() 275 app.GET("/", func(c echo.Context) error { 276 model := db.Joins("User").Model(&Article{}) 277 return c.JSON(200, pg.Response(model, c.Request(), &[]Article{})) 278 }) 279 280 app.Logger.Fatal(app.Start(":3000")) 281 } 282 ``` 283 284 ### Gin Example 285 286 ```go 287 package main 288 289 import ( 290 "github.com/morkid/paginate" 291 ... 292 ) 293 294 func main() { 295 // var db *gorm.DB 296 pg := paginate.New() 297 app := gin.Default() 298 app.GET("/", func(c *gin.Context) { 299 model := db.Joins("User").Model(&Article{}) 300 c.JSON(200, pg.Response(model, c.Request, &[]Article{})) 301 }) 302 app.Run(":3000") 303 } 304 305 ``` 306 307 ### Martini Example 308 309 ```go 310 package main 311 312 import ( 313 "github.com/morkid/paginate" 314 ... 315 ) 316 317 func main() { 318 // var db *gorm.DB 319 pg := paginate.New() 320 app := martini.Classic() 321 app.Use(render.Renderer()) 322 app.Get("/", func(req *http.Request, r render.Render) { 323 model := db.Joins("User").Model(&Article{}) 324 r.JSON(200, pg.Response(model, req, &[]Article{})) 325 }) 326 app.Run() 327 } 328 ``` 329 ### Beego Example 330 331 ```go 332 package main 333 334 import ( 335 "github.com/morkid/paginate" 336 ... 337 ) 338 339 func main() { 340 // var db *gorm.DB 341 pg := paginate.New() 342 web.Get("/", func(c *context.Context) { 343 model := db.Joins("User").Model(&Article{}) 344 c.Output.JSON( 345 pg.Response(model, c.Request, &[]Article{}), false, false) 346 }) 347 web.Run(":3000") 348 } 349 ``` 350 351 ### jQuery DataTable Integration 352 353 ```js 354 var logicalOperator = "OR" 355 356 $('#myTable').DataTable({ 357 358 columns: [ 359 { 360 title: "Author", 361 data: "user.name" 362 }, { 363 title: "Title", 364 data: "title" 365 } 366 ], 367 368 processing: true, 369 370 serverSide: true, 371 372 ajax: { 373 cache: true, 374 url: "http://localhost:3000/articles", 375 dataSrc: function(json) { 376 json.recordsTotal = json.visible 377 json.recordsFiltered = json.total 378 return json.items 379 }, 380 data: function(params) { 381 var custom = { 382 page: !params.start ? 0 : Math.round(params.start / params.length), 383 size: params.length 384 } 385 386 if (params.order.length > 0) { 387 var sorts = [] 388 for (var o in params.order) { 389 var order = params.order[o] 390 if (params.columns[order.column].orderable != false) { 391 var sort = order.dir != 'desc' ? '' : '-' 392 sort += params.columns[order.column].data 393 sorts.push(sort) 394 } 395 } 396 custom.sort = sorts.join() 397 } 398 399 if (params.search.value) { 400 var columns = [] 401 for (var c in params.columns) { 402 var col = params.columns[c] 403 if (col.searchable == false) { 404 continue 405 } 406 columns.push(JSON.stringify([col.data, "like", encodeURIComponent(params.search.value.toLowerCase())])) 407 } 408 custom.filters = '[' + columns.join(',["' + logicalOperator + '"],') + ']' 409 } 410 411 return custom 412 } 413 }, 414 }) 415 ``` 416 417 ### jQuery Select2 Integration 418 419 ```js 420 $('#mySelect').select2({ 421 ajax: { 422 url: "http://localhost:3000/users", 423 processResults: function(json) { 424 json.items.forEach(function(item) { 425 item.text = item.name 426 }) 427 // optional 428 if (json.first) json.items.unshift({id: 0, text: 'All'}) 429 430 return { 431 results: json.items, 432 pagination: { 433 more: json.last == false 434 } 435 } 436 }, 437 data: function(params) { 438 var filters = [ 439 ["name", "like", params.term] 440 ] 441 442 return { 443 filters: params.term ? JSON.stringify(filters) : "", 444 sort: "name", 445 page: params.page && params.page - 1 ? params.page - 1 : 0 446 } 447 }, 448 } 449 }) 450 ``` 451 452 453 ## Filter format 454 455 The format of filter param is a json encoded of multidimensional array. 456 Maximum array members is three, first index is `column_name`, second index is `operator` and third index is `values`, you can also pass array to values. 457 458 ```js 459 // Format: 460 ["column_name", "operator", "values"] 461 462 // Example: 463 ["age", "=", 20] 464 // Shortcut: 465 ["age", 20] 466 467 // Produces: 468 // WHERE age = 20 469 ``` 470 471 Single array member is known as **Logical Operator**. 472 ```js 473 // Example 474 [["age", "=", 20],["or"],["age", "=", 25]] 475 476 // Produces: 477 // WHERE age = 20 OR age = 25 478 ``` 479 480 You are allowed to send array inside a value. 481 ```js 482 ["age", "between", [20, 30] ] 483 // Produces: 484 // WHERE age BETWEEN 20 AND 30 485 486 ["age", "not in", [20, 21, 22, 23, 24, 25, 26, 26] ] 487 // Produces: 488 // WHERE age NOT IN(20, 21, 22, 23, 24, 25, 26, 26) 489 ``` 490 491 You can filter nested condition with deep array. 492 ```js 493 [ 494 [ 495 ["age", ">", 20], 496 ["and"] 497 ["age", "<", 30] 498 ], 499 ["and"], 500 ["name", "like", "john"], 501 ["and"], 502 ["name", "like", "doe"] 503 ] 504 // Produces: 505 // WHERE ( (age > 20 AND age < 20) and name like '%john%' and name like '%doe%' ) 506 ``` 507 508 For `null` value, you can send string `"null"` or `null` value, *(lower)* 509 ```js 510 // Wrong request 511 [ "age", "is", NULL ] 512 [ "age", "is", Null ] 513 [ "age", "is not", NULL ] 514 [ "age", "is not", Null ] 515 516 // Right request 517 [ "age", "is", "NULL" ] 518 [ "age", "is", "Null" ] 519 [ "age", "is", "null" ] 520 [ "age", "is", null ] 521 [ "age", null ] 522 [ "age", "is not", "NULL" ] 523 [ "age", "is not", "Null" ] 524 [ "age", "is not", "null" ] 525 [ "age", "is not", null ] 526 ``` 527 528 ## Customize default configuration 529 530 You can customize the default configuration with `paginate.Config` struct. 531 532 ```go 533 pg := paginate.New(&paginate.Config{ 534 DefaultSize: 50, 535 }) 536 ``` 537 538 Config | Type | Default | Description 539 ------------------ | ---------- | --------------------- | ------------- 540 Operator | `string` | `OR` | Default conditional operator if no operator specified.<br>For example<br>`GET /user?filters=[["name","like","jo"],["age",">",20]]`,<br>produces<br>`SELECT * FROM user where name like '%jo' OR age > 20` 541 FieldWrapper | `string` | `LOWER(%s)` | FieldWrapper for `LIKE` operator *(for postgres default is: `LOWER((%s)::text)`)* 542 DefaultSize | `int64` | `10` | Default size or limit per page 543 SmartSearch | `bool` | `false` | Enable smart search *(Experimental feature)* 544 CustomParamEnabled | `bool` | `false` | Enable custom request parameter 545 FieldSelectorEnabled | `bool` | `false` | Enable partial response with specific fields. Comma separated per field. eg: `?fields=title,user.name` 546 SortParams | `[]string` | `[]string{"sort"}` | if `CustomParamEnabled` is `true`,<br>you can set the `SortParams` with custom parameter names.<br>For example: `[]string{"sorting", "ordering", "other_alternative_param"}`.<br>The following requests will capture same result<br>`?sorting=-name`<br>or `?ordering=-name`<br>or `?other_alternative_param=-name`<br>or `?sort=-name` 547 PageParams | `[]string` | `[]string{"page"}` | if `CustomParamEnabled` is `true`,<br>you can set the `PageParams` with custom parameter names.<br>For example:<br>`[]string{"number", "num", "other_alternative_param"}`.<br>The following requests will capture same result `?number=0`<br>or `?num=0`<br>or `?other_alternative_param=0`<br>or `?page=0` 548 SizeParams | `[]string` | `[]string{"size"}` | if `CustomParamEnabled` is `true`,<br>you can set the `SizeParams` with custom parameter names.<br>For example:<br>`[]string{"limit", "max", "other_alternative_param"}`.<br>The following requests will capture same result `?limit=50`<br>or `?limit=50`<br>or `?other_alternative_param=50`<br>or `?max=50` 549 OrderParams | `[]string` | `[]string{"order"}` | if `CustomParamEnabled` is `true`,<br>you can set the `OrderParams` with custom parameter names.<br>For example:<br>`[]string{"order", "direction", "other_alternative_param"}`.<br>The following requests will capture same result `?order=desc`<br>or `?direction=desc`<br>or `?other_alternative_param=desc` 550 FilterParams | `[]string` | `[]string{"filters"}` | if `CustomParamEnabled` is `true`,<br>you can set the `FilterParams` with custom parameter names.<br>For example:<br>`[]string{"search", "find", "other_alternative_param"}`.<br>The following requests will capture same result<br>`?search=["name","john"]`<br>or `?find=["name","john"]`<br>or `?other_alternative_param=["name","john"]`<br>or `?filters=["name","john"]` 551 FieldsParams | `[]string` | `[]string{"fields"}` | if `FieldSelectorEnabled` and `CustomParamEnabled` is `true`,<br>you can set the `FieldsParams` with custom parameter names.<br>For example:<br>`[]string{"fields", "columns", "other_alternative_param"}`.<br>The following requests will capture same result `?fields=title,user.name`<br>or `?columns=title,user.name`<br>or `?other_alternative_param=title,user.name` 552 CacheAdapter | `*gocache.AdapterInterface` | `nil` | the cache adapter, see more about [cache config](#speed-up-response-with-cache). 553 554 ## Override results 555 556 You can override result with custom function. 557 558 ```go 559 // var db = *gorm.DB 560 // var httpRequest ... net/http or fasthttp instance 561 // Example override function 562 override := func(article *Article) { 563 if article.UserID > 0 { 564 article.Title = fmt.Sprintf( 565 "%s written by %s", article.Title, article.User.Name) 566 } 567 } 568 569 var articles []Article 570 model := db.Joins("User").Model(&Article{}) 571 572 pg := paginate.New() 573 result := pg.Response(model, httpRequest, &articles) 574 for index := range articles { 575 override(&articles[index]) 576 } 577 578 log.Println(result.Items) 579 580 ``` 581 582 ## Field selector 583 To implement a custom field selector, struct properties must have a json tag with omitempty. 584 585 ```go 586 // real gorm model 587 type User { 588 gorm.Model 589 Name string `json:"name"` 590 Age int64 `json:"age"` 591 } 592 593 // fake gorm model 594 type UserNullable { 595 ID *string `json:"id,omitempty"` 596 CreatedAt *time.Time `json:"created_at,omitempty"` 597 UpdatedAt *time.Time `json:"updated_at,omitempty"` 598 Name *string `json:"name,omitempty"` 599 Age *int64 `json:"age,omitempty"` 600 } 601 ``` 602 603 ```go 604 // usage 605 nameAndIDOnly := []string{"name","id"} 606 model := db.Model(&User{}) 607 608 page := pg.With(model). 609 Request(req). 610 Fields(nameAndIDOnly). 611 Response([]&UserNullable{}) 612 ``` 613 614 ```javascript 615 // response 616 { 617 "items": [ 618 { 619 "id": 1, 620 "name": "John" 621 } 622 ], 623 ... 624 } 625 ``` 626 ## Dynamic field selector 627 If the request contains query parameter `fields` (eg: `?fieilds=name,id`), then the response will show only `name` and `id`. To activate this feature, please set `FieldSelectorEnabled` to `true`. 628 ```go 629 config := paginate.Config{ 630 FieldSelectorEnabled: true, 631 } 632 633 pg := paginate.New(config) 634 ``` 635 636 ## Speed up response with cache 637 You can speed up results without looking database directly with cache adapter. See more about [cache adapter](https://github.com/morkid/gocache). 638 639 ### In memory cache 640 in memory cache is not recommended for production environment: 641 ```go 642 import ( 643 "github.com/morkid/gocache" 644 ... 645 ) 646 647 func main() { 648 ... 649 adapterConfig := gocache.InMemoryCacheConfig{ 650 ExpiresIn: 1 * time.Hour, 651 } 652 pg := paginate.New(&paginate.Config{ 653 CacheAdapter: gocache.NewInMemoryCache(adapterConfig), 654 }) 655 656 page := pg.With(model). 657 Request(req). 658 Cache("article"). // set cache name 659 Response(&[]Article{}) 660 ... 661 } 662 ``` 663 664 ### Disk cache 665 Disk cache will create a file for every single request. You can use disk cache if you don't care about inode. 666 ```go 667 import ( 668 "github.com/morkid/gocache" 669 ... 670 ) 671 672 func main() { 673 adapterConfig := gocache.DiskCacheConfig{ 674 Directory: "/writable/path/to/my-cache-dir", 675 ExpiresIn: 1 * time.Hour, 676 } 677 pg := paginate.New(&paginate.Config{ 678 CacheAdapter: gocache.NewDiskCache(adapterConfig), 679 }) 680 681 page := pg.With(model). 682 Request(req). 683 Cache("article"). // set cache name 684 Response(&[]Article{}) 685 ... 686 } 687 ``` 688 689 ### Redis cache 690 Redis cache require [redis client](https://github.com/go-redis/redis) for golang. 691 ```go 692 import ( 693 cache "github.com/morkid/gocache-redis/v8" 694 "github.com/go-redis/redis/v8" 695 ... 696 ) 697 698 func main() { 699 client := redis.NewClient(&redis.Options{ 700 Addr: "localhost:6379", 701 Password: "", 702 DB: 0, 703 }) 704 705 adapterConfig := cache.RedisCacheConfig{ 706 Client: client, 707 ExpiresIn: 1 * time.Hour, 708 } 709 pg := paginate.New(&paginate.Config{ 710 CacheAdapter: cache.NewRedisCache(adapterConfig), 711 }) 712 713 page := pg.With(model). 714 Request(req). 715 Cache("article"). 716 Response(&[]Article{}) 717 ... 718 } 719 ``` 720 > if your code already adopts another redis client, you can implement the [redis adapter](https://github.com/morkid/gocache-redis) according to its version. See more about [redis adapter](https://github.com/morkid/gocache-redis). 721 722 ### Elasticsearch cache 723 Elasticsearch cache require official [elasticsearch client](https://github.com/elastic/go-elasticsearch) for golang. 724 ```go 725 import ( 726 cache "github.com/morkid/gocache-elasticsearch/v7" 727 "github.com/elastic/go-elasticsearch/v7" 728 ... 729 ) 730 731 func main() { 732 config := elasticsearch.Config{ 733 Addresses: []string{ 734 "http://localhost:9200", 735 }, 736 } 737 es, err := elasticsearch.NewClient(config) 738 if nil != err { 739 panic(err) 740 } 741 742 adapterConfig := cache.ElasticCacheConfig{ 743 Client: es, 744 Index: "exampleproject", 745 ExpiresIn: 1 * time.Hour, 746 } 747 pg := paginate.New(&paginate.Config{ 748 CacheAdapter: cache.NewElasticCache(adapterConfig), 749 }) 750 751 page := pg.With(model). 752 Request(req). 753 Cache("article"). 754 Response(&[]Article{}) 755 ... 756 } 757 ``` 758 > if your code already adopts another elasticsearch client, you can implement the [elasticsearch adapter](https://github.com/morkid/gocache-elasticsearch) according to its version. See more about [elasticsearch adapter](https://github.com/morkid/gocache-elasticsearch). 759 760 ### Custom cache 761 Create your own cache adapter by implementing [gocache AdapterInterface](https://github.com/morkid/gocache/blob/master/gocache.go). See more about [cache adapter](https://github.com/morkid/gocache). 762 ```go 763 // AdapterInterface interface 764 type AdapterInterface interface { 765 // Set cache with key 766 Set(key string, value string) error 767 // Get cache by key 768 Get(key string) (string, error) 769 // IsValid check if cache is valid 770 IsValid(key string) bool 771 // Clear clear cache by key 772 Clear(key string) error 773 // ClearPrefix clear cache by key prefix 774 ClearPrefix(keyPrefix string) error 775 // Clear all cache 776 ClearAll() error 777 } 778 ``` 779 780 ### Clean up cache 781 Clear cache by cache name 782 ```go 783 pg.ClearCache("article") 784 ``` 785 Clear multiple cache 786 ```go 787 pg.ClearCache("cache1", "cache2", "cache3") 788 ``` 789 790 Clear all cache 791 ```go 792 pg.ClearAllCache() 793 ``` 794 795 796 ## Limitations 797 798 Paginate doesn't support has many relationship. You can make API with separated endpoints for parent and child: 799 ```javascript 800 GET /users 801 802 { 803 "items": [ 804 { 805 "id": 1, 806 "name": "john", 807 "age": 20, 808 "addresses": [...] // doesn't support 809 } 810 ], 811 ... 812 } 813 ``` 814 815 Best practice: 816 817 ```javascript 818 GET /users 819 { 820 "items": [ 821 { 822 "id": 1, 823 "name": "john", 824 "age": 20 825 } 826 ], 827 ... 828 } 829 830 GET /users/1/addresses 831 { 832 "items": [ 833 { 834 "id": 1, 835 "name": "home", 836 "street": "home street" 837 "user": { 838 "id": 1, 839 "name": "john", 840 "age": 20 841 } 842 } 843 ], 844 ... 845 } 846 ``` 847 848 Paginate doesn't support for customized json or table field name. 849 Make sure your struct properties have same name with gorm column and json property before you expose them. 850 851 Example bad configuration: 852 853 ```go 854 855 type User struct { 856 gorm.Model 857 UserName string `gorm:"column:nickname" json:"name"` 858 UserAddress string `gorm:"column:user_address" json:"address"` 859 } 860 861 // request: GET /path/to/endpoint?sort=-name,address 862 // response: "items": [] with sql error (column name not found) 863 ``` 864 865 Best practice: 866 ```go 867 type User struct { 868 gorm.Model 869 Name string `gorm:"column:name" json:"name"` 870 Address string `gorm:"column:address" json:"address"` 871 } 872 873 ``` 874 875 ## License 876 877 Published under the [MIT License](https://github.com/morkid/paginate/blob/master/LICENSE).