Class: Apes::PaginationCursor
- Inherits:
-
Object
- Object
- Apes::PaginationCursor
- Defined in:
- lib/apes/pagination_cursor.rb
Overview
A cursor that can be sent to the client, received unmodified and retrieved later to paginate results.
Constant Summary
- DEFAULT_SIZE =
The default size of a pagination page.
25
- TIMESTAMP_FORMAT =
Format to serialize timestamp when using them for pagination.
"%FT%T.%6N%z".freeze
Instance Attribute Summary (collapse)
-
- (IO|String) direction
Which page to get in this iteration.
-
- (IO|String) size
The size of the pagination page.
-
- (Boolean) use_offset
Whether to use offset based pagination rather than collection fields values.
-
- (String) value
The value obtain from previous pagination.
Instance Method Summary (collapse)
-
- (Apes::PaginationCursor) initialize(params = {}, field = :page, count_field = :count)
constructor
Creates a new cursor.
-
- (Boolean) might_exist?(page, collection)
Verifies whether a specific page might exist for the given collection.
-
- (String) operator(order)
Get the operator (
>
or<
) for the query according to the direction and the provided ordering. -
- (String) save(collection, page, field: :id, size: nil, use_offset: nil)
(also: #serialize)
Serializes the cursor to send it to the client.
Constructor Details
- (Apes::PaginationCursor) initialize(params = {}, field = :page, count_field = :count)
Creates a new cursor.
32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/apes/pagination_cursor.rb', line 32 def initialize(params = {}, field = :page, count_field = :count) begin payload = JWT.decode(params[field], jwt_secret, true, {algorithm: "HS256", verify_aud: true, aud: "pagination"}).dig(0, "sub") extract_payload(payload) rescue default_payload end # Sanitization sanitize(count_field, params) end |
Instance Attribute Details
- (IO|String) direction
Returns Which page to get in this iteration.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/apes/pagination_cursor.rb', line 17 class PaginationCursor # The default size of a pagination page. DEFAULT_SIZE = 25 # Format to serialize timestamp when using them for pagination. TIMESTAMP_FORMAT = "%FT%T.%6N%z".freeze attr_accessor :value, :use_offset, :direction, :size # Creates a new cursor. # # @param params [Hash] The request parameters. # @param field [Symbol] The parameters field where to lookup for the serialized cursor. # @param count_field [Symbol] The parameters field where to lookup for the overriding cursor size. # @return [Apes::PaginationCursor] A new cursor instance. def initialize(params = {}, field = :page, count_field = :count) begin payload = JWT.decode(params[field], jwt_secret, true, {algorithm: "HS256", verify_aud: true, aud: "pagination"}).dig(0, "sub") extract_payload(payload) rescue default_payload end # Sanitization sanitize(count_field, params) end # Get the operator (`>` or `<`) for the query according to the direction and the provided ordering. # # @param order [Symbol] The order to use. # @return [String] The operator to use for the query. def operator(order) if direction == "next" order == :asc ? ">" : "<" # Descending order means newer results first else order == :asc ? "<" : ">" # Descending order means newer results first end end # Verifies whether a specific page might exist for the given collection. # # @param page [String] The page to check. It can be `first`, `next`, `prev` or `previous`. # @param collection [Enumerable] The collection to analyze. # @return [Boolean] Returns `true` if the page might exist for the collection, `false` otherwise. def might_exist?(page, collection) case page.to_s when "first" then true when "next" then collection.present? else value.present? && collection.present? # Previous end end # Serializes the cursor to send it to the client. # # @param collection [Enumerable] The collection to analyze. # @param page [String] The page to return. It can be `first`, `next`, `prev` or `previous`. # @param field [Symbol] When not using offset based pagination, the field to consider for generation. # @param size [Fixnum] The number of results to advance when using offset based pagination. # @param use_offset [Boolean] Whether to use offset based pagination. # @return [String] The serialized cursor. def save(collection, page, field: :id, size: nil, use_offset: nil) size ||= self.size use_offset = self.use_offset if use_offset.nil? direction, value = use_offset ? update_with_offset(page, size) : update_with_field(page, collection, field) value = value.strftime(TIMESTAMP_FORMAT) if value.respond_to?(:strftime) JWT.encode({aud: "pagination", sub: {value: value, use_offset: use_offset, direction: direction, size: size}}, jwt_secret, "HS256") end alias_method :serialize, :save private # :nodoc: def default_payload @value = nil @direction = "next" @size = 0 @use_offset = false end # :nodoc: def extract_payload(payload) @value = payload["value"] @direction = payload["direction"] @size = payload["size"] @use_offset = payload["use_offset"] end # :nodoc: def sanitize(count_field, params) @direction = "next" unless ["prev", "previous"].include?(@direction) @size = params[count_field].to_integer if params[count_field].present? @size = DEFAULT_SIZE if @size < 1 end # :nodoc: def update_with_field(type, collection, field) case type.ensure_string when "next" direction = "next" value = collection.last&.send(field) when "prev", "previous" direction = "previous" value = collection.first&.send(field) else # first direction = "next" value = nil end [direction, value] end # :nodoc: def update_with_offset(type, size) case type.ensure_string when "next" direction = "next" value = self.value + size when "prev", "previous" direction = "previous" value = [0, self.value - size].max else # first direction = "next" value = nil end [direction, value] end def jwt_secret Apes::RuntimeConfiguration.jwt_token end end |
- (IO|String) size
Returns The size of the pagination page.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/apes/pagination_cursor.rb', line 17 class PaginationCursor # The default size of a pagination page. DEFAULT_SIZE = 25 # Format to serialize timestamp when using them for pagination. TIMESTAMP_FORMAT = "%FT%T.%6N%z".freeze attr_accessor :value, :use_offset, :direction, :size # Creates a new cursor. # # @param params [Hash] The request parameters. # @param field [Symbol] The parameters field where to lookup for the serialized cursor. # @param count_field [Symbol] The parameters field where to lookup for the overriding cursor size. # @return [Apes::PaginationCursor] A new cursor instance. def initialize(params = {}, field = :page, count_field = :count) begin payload = JWT.decode(params[field], jwt_secret, true, {algorithm: "HS256", verify_aud: true, aud: "pagination"}).dig(0, "sub") extract_payload(payload) rescue default_payload end # Sanitization sanitize(count_field, params) end # Get the operator (`>` or `<`) for the query according to the direction and the provided ordering. # # @param order [Symbol] The order to use. # @return [String] The operator to use for the query. def operator(order) if direction == "next" order == :asc ? ">" : "<" # Descending order means newer results first else order == :asc ? "<" : ">" # Descending order means newer results first end end # Verifies whether a specific page might exist for the given collection. # # @param page [String] The page to check. It can be `first`, `next`, `prev` or `previous`. # @param collection [Enumerable] The collection to analyze. # @return [Boolean] Returns `true` if the page might exist for the collection, `false` otherwise. def might_exist?(page, collection) case page.to_s when "first" then true when "next" then collection.present? else value.present? && collection.present? # Previous end end # Serializes the cursor to send it to the client. # # @param collection [Enumerable] The collection to analyze. # @param page [String] The page to return. It can be `first`, `next`, `prev` or `previous`. # @param field [Symbol] When not using offset based pagination, the field to consider for generation. # @param size [Fixnum] The number of results to advance when using offset based pagination. # @param use_offset [Boolean] Whether to use offset based pagination. # @return [String] The serialized cursor. def save(collection, page, field: :id, size: nil, use_offset: nil) size ||= self.size use_offset = self.use_offset if use_offset.nil? direction, value = use_offset ? update_with_offset(page, size) : update_with_field(page, collection, field) value = value.strftime(TIMESTAMP_FORMAT) if value.respond_to?(:strftime) JWT.encode({aud: "pagination", sub: {value: value, use_offset: use_offset, direction: direction, size: size}}, jwt_secret, "HS256") end alias_method :serialize, :save private # :nodoc: def default_payload @value = nil @direction = "next" @size = 0 @use_offset = false end # :nodoc: def extract_payload(payload) @value = payload["value"] @direction = payload["direction"] @size = payload["size"] @use_offset = payload["use_offset"] end # :nodoc: def sanitize(count_field, params) @direction = "next" unless ["prev", "previous"].include?(@direction) @size = params[count_field].to_integer if params[count_field].present? @size = DEFAULT_SIZE if @size < 1 end # :nodoc: def update_with_field(type, collection, field) case type.ensure_string when "next" direction = "next" value = collection.last&.send(field) when "prev", "previous" direction = "previous" value = collection.first&.send(field) else # first direction = "next" value = nil end [direction, value] end # :nodoc: def update_with_offset(type, size) case type.ensure_string when "next" direction = "next" value = self.value + size when "prev", "previous" direction = "previous" value = [0, self.value - size].max else # first direction = "next" value = nil end [direction, value] end def jwt_secret Apes::RuntimeConfiguration.jwt_token end end |
- (Boolean) use_offset
Returns Whether to use offset based pagination rather than collection fields values.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/apes/pagination_cursor.rb', line 17 class PaginationCursor # The default size of a pagination page. DEFAULT_SIZE = 25 # Format to serialize timestamp when using them for pagination. TIMESTAMP_FORMAT = "%FT%T.%6N%z".freeze attr_accessor :value, :use_offset, :direction, :size # Creates a new cursor. # # @param params [Hash] The request parameters. # @param field [Symbol] The parameters field where to lookup for the serialized cursor. # @param count_field [Symbol] The parameters field where to lookup for the overriding cursor size. # @return [Apes::PaginationCursor] A new cursor instance. def initialize(params = {}, field = :page, count_field = :count) begin payload = JWT.decode(params[field], jwt_secret, true, {algorithm: "HS256", verify_aud: true, aud: "pagination"}).dig(0, "sub") extract_payload(payload) rescue default_payload end # Sanitization sanitize(count_field, params) end # Get the operator (`>` or `<`) for the query according to the direction and the provided ordering. # # @param order [Symbol] The order to use. # @return [String] The operator to use for the query. def operator(order) if direction == "next" order == :asc ? ">" : "<" # Descending order means newer results first else order == :asc ? "<" : ">" # Descending order means newer results first end end # Verifies whether a specific page might exist for the given collection. # # @param page [String] The page to check. It can be `first`, `next`, `prev` or `previous`. # @param collection [Enumerable] The collection to analyze. # @return [Boolean] Returns `true` if the page might exist for the collection, `false` otherwise. def might_exist?(page, collection) case page.to_s when "first" then true when "next" then collection.present? else value.present? && collection.present? # Previous end end # Serializes the cursor to send it to the client. # # @param collection [Enumerable] The collection to analyze. # @param page [String] The page to return. It can be `first`, `next`, `prev` or `previous`. # @param field [Symbol] When not using offset based pagination, the field to consider for generation. # @param size [Fixnum] The number of results to advance when using offset based pagination. # @param use_offset [Boolean] Whether to use offset based pagination. # @return [String] The serialized cursor. def save(collection, page, field: :id, size: nil, use_offset: nil) size ||= self.size use_offset = self.use_offset if use_offset.nil? direction, value = use_offset ? update_with_offset(page, size) : update_with_field(page, collection, field) value = value.strftime(TIMESTAMP_FORMAT) if value.respond_to?(:strftime) JWT.encode({aud: "pagination", sub: {value: value, use_offset: use_offset, direction: direction, size: size}}, jwt_secret, "HS256") end alias_method :serialize, :save private # :nodoc: def default_payload @value = nil @direction = "next" @size = 0 @use_offset = false end # :nodoc: def extract_payload(payload) @value = payload["value"] @direction = payload["direction"] @size = payload["size"] @use_offset = payload["use_offset"] end # :nodoc: def sanitize(count_field, params) @direction = "next" unless ["prev", "previous"].include?(@direction) @size = params[count_field].to_integer if params[count_field].present? @size = DEFAULT_SIZE if @size < 1 end # :nodoc: def update_with_field(type, collection, field) case type.ensure_string when "next" direction = "next" value = collection.last&.send(field) when "prev", "previous" direction = "previous" value = collection.first&.send(field) else # first direction = "next" value = nil end [direction, value] end # :nodoc: def update_with_offset(type, size) case type.ensure_string when "next" direction = "next" value = self.value + size when "prev", "previous" direction = "previous" value = [0, self.value - size].max else # first direction = "next" value = nil end [direction, value] end def jwt_secret Apes::RuntimeConfiguration.jwt_token end end |
- (String) value
Returns The value obtain from previous pagination. It can either be the value of the first or last element in previous iteration.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/apes/pagination_cursor.rb', line 17 class PaginationCursor # The default size of a pagination page. DEFAULT_SIZE = 25 # Format to serialize timestamp when using them for pagination. TIMESTAMP_FORMAT = "%FT%T.%6N%z".freeze attr_accessor :value, :use_offset, :direction, :size # Creates a new cursor. # # @param params [Hash] The request parameters. # @param field [Symbol] The parameters field where to lookup for the serialized cursor. # @param count_field [Symbol] The parameters field where to lookup for the overriding cursor size. # @return [Apes::PaginationCursor] A new cursor instance. def initialize(params = {}, field = :page, count_field = :count) begin payload = JWT.decode(params[field], jwt_secret, true, {algorithm: "HS256", verify_aud: true, aud: "pagination"}).dig(0, "sub") extract_payload(payload) rescue default_payload end # Sanitization sanitize(count_field, params) end # Get the operator (`>` or `<`) for the query according to the direction and the provided ordering. # # @param order [Symbol] The order to use. # @return [String] The operator to use for the query. def operator(order) if direction == "next" order == :asc ? ">" : "<" # Descending order means newer results first else order == :asc ? "<" : ">" # Descending order means newer results first end end # Verifies whether a specific page might exist for the given collection. # # @param page [String] The page to check. It can be `first`, `next`, `prev` or `previous`. # @param collection [Enumerable] The collection to analyze. # @return [Boolean] Returns `true` if the page might exist for the collection, `false` otherwise. def might_exist?(page, collection) case page.to_s when "first" then true when "next" then collection.present? else value.present? && collection.present? # Previous end end # Serializes the cursor to send it to the client. # # @param collection [Enumerable] The collection to analyze. # @param page [String] The page to return. It can be `first`, `next`, `prev` or `previous`. # @param field [Symbol] When not using offset based pagination, the field to consider for generation. # @param size [Fixnum] The number of results to advance when using offset based pagination. # @param use_offset [Boolean] Whether to use offset based pagination. # @return [String] The serialized cursor. def save(collection, page, field: :id, size: nil, use_offset: nil) size ||= self.size use_offset = self.use_offset if use_offset.nil? direction, value = use_offset ? update_with_offset(page, size) : update_with_field(page, collection, field) value = value.strftime(TIMESTAMP_FORMAT) if value.respond_to?(:strftime) JWT.encode({aud: "pagination", sub: {value: value, use_offset: use_offset, direction: direction, size: size}}, jwt_secret, "HS256") end alias_method :serialize, :save private # :nodoc: def default_payload @value = nil @direction = "next" @size = 0 @use_offset = false end # :nodoc: def extract_payload(payload) @value = payload["value"] @direction = payload["direction"] @size = payload["size"] @use_offset = payload["use_offset"] end # :nodoc: def sanitize(count_field, params) @direction = "next" unless ["prev", "previous"].include?(@direction) @size = params[count_field].to_integer if params[count_field].present? @size = DEFAULT_SIZE if @size < 1 end # :nodoc: def update_with_field(type, collection, field) case type.ensure_string when "next" direction = "next" value = collection.last&.send(field) when "prev", "previous" direction = "previous" value = collection.first&.send(field) else # first direction = "next" value = nil end [direction, value] end # :nodoc: def update_with_offset(type, size) case type.ensure_string when "next" direction = "next" value = self.value + size when "prev", "previous" direction = "previous" value = [0, self.value - size].max else # first direction = "next" value = nil end [direction, value] end def jwt_secret Apes::RuntimeConfiguration.jwt_token end end |
Instance Method Details
- (Boolean) might_exist?(page, collection)
Verifies whether a specific page might exist for the given collection.
62 63 64 65 66 67 68 |
# File 'lib/apes/pagination_cursor.rb', line 62 def might_exist?(page, collection) case page.to_s when "first" then true when "next" then collection.present? else value.present? && collection.present? # Previous end end |
- (String) operator(order)
Get the operator (>
or <
) for the query according to the direction and the provided ordering.
49 50 51 52 53 54 55 |
# File 'lib/apes/pagination_cursor.rb', line 49 def operator(order) if direction == "next" order == :asc ? ">" : "<" # Descending order means newer results first else order == :asc ? "<" : ">" # Descending order means newer results first end end |
- (String) save(collection, page, field: :id, size: nil, use_offset: nil) Also known as: serialize
Serializes the cursor to send it to the client.
78 79 80 81 82 83 84 85 86 |
# File 'lib/apes/pagination_cursor.rb', line 78 def save(collection, page, field: :id, size: nil, use_offset: nil) size ||= self.size use_offset = self.use_offset if use_offset.nil? direction, value = use_offset ? update_with_offset(page, size) : update_with_field(page, collection, field) value = value.strftime(TIMESTAMP_FORMAT) if value.respond_to?(:strftime) JWT.encode({aud: "pagination", sub: {value: value, use_offset: use_offset, direction: direction, size: size}}, jwt_secret, "HS256") end |