github.com/unionj-cloud/go-doudou/v2@v2.3.5/toolkit/pagination/gorm/README.md (about)

     1  # paginate - Gorm Pagination
     2  
     3  [![Go Reference](https://pkg.go.dev/badge/github.com/morkid/paginate.svg)](https://pkg.go.dev/github.com/morkid/paginate)
     4  [![CircleCI](https://circleci.com/gh/morkid/paginate.svg?style=svg)](https://circleci.com/gh/morkid/paginate)
     5  [![Github Actions](https://github.com/morkid/paginate/workflows/Go/badge.svg)](https://github.com/morkid/paginate/actions)
     6  [![Build Status](https://travis-ci.com/morkid/paginate.svg?branch=master)](https://travis-ci.com/morkid/paginate)
     7  [![Go Report Card](https://goreportcard.com/badge/github.com/morkid/paginate)](https://goreportcard.com/report/github.com/morkid/paginate)
     8  [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/morkid/paginate)](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).