Class: Rotulus::Cursor
- Inherits:
-
Object
- Object
- Rotulus::Cursor
- Defined in:
- lib/rotulus/cursor.rb
Instance Attribute Summary collapse
-
#created_at ⇒ Object
readonly
Returns the value of attribute created_at.
-
#direction ⇒ Object
readonly
Returns the value of attribute direction.
-
#record ⇒ Object
readonly
Returns the value of attribute record.
Class Method Summary collapse
-
.decode(token) ⇒ Object
Decode the given encoded cursor token.
-
.encode(token_data) ⇒ Object
Encode cursor data hash.
-
.for_page_and_token!(page, token) ⇒ Object
Initialize a Cursor instance for the given page instance and encoded token.
Instance Method Summary collapse
-
#expired? ⇒ Boolean
Checks if the cursor is expired.
-
#initialize(record, direction, created_at: nil) ⇒ Cursor
constructor
A new instance of Cursor.
-
#next? ⇒ Boolean
Returns true if the cursor should retrieve the ‘next’ records from the last record of the previous page.
-
#prev? ⇒ Boolean
Returns true if the cursor should retrieve the ‘previous’ records from the first record of a page.
-
#sql ⇒ String
Generate the SQL condition to filter the records of the next/previous page.
-
#state ⇒ String
Generate a ‘state’ string for integrity checking of the reference record, direction, and created_at data from a decoded Cursor token.
-
#to_token ⇒ String
(also: #to_s)
Generate the token: a Base64-encoded string representation of this cursor.
Constructor Details
#initialize(record, direction, created_at: nil) ⇒ Cursor
Returns a new instance of Cursor.
75 76 77 78 79 80 81 |
# File 'lib/rotulus/cursor.rb', line 75 def initialize(record, direction, created_at: nil) @record = record @direction = direction.to_sym @created_at = created_at.presence || Time.current validate! end |
Instance Attribute Details
#created_at ⇒ Object (readonly)
Returns the value of attribute created_at.
65 66 67 |
# File 'lib/rotulus/cursor.rb', line 65 def created_at @created_at end |
#direction ⇒ Object (readonly)
Returns the value of attribute direction.
65 66 67 |
# File 'lib/rotulus/cursor.rb', line 65 def direction @direction end |
#record ⇒ Object (readonly)
Returns the value of attribute record.
65 66 67 |
# File 'lib/rotulus/cursor.rb', line 65 def record @record end |
Class Method Details
.decode(token) ⇒ Object
Decode the given encoded cursor token
@param token [String] Encoded cursor token
@return [Hash] Cursor data hash containing the cursor direction(:next, :prev),
cursor's state, and the ordered column values of the reference record: last record
of the previous page if page direction is `:next` or the first record of the next
page if page direction is `:prev`.
50 51 52 53 54 |
# File 'lib/rotulus/cursor.rb', line 50 def decode(token) Oj.load(Base64.urlsafe_decode64(token)) rescue ArgumentError, Oj::ParseError => e raise InvalidCursor.new("Invalid Cursor: #{e.}") end |
.encode(token_data) ⇒ Object
Encode cursor data hash
@param token_data [Hash] Cursor token data hash
@return token [String] String token for this cursor that can be used as param to Page#at.
60 61 62 |
# File 'lib/rotulus/cursor.rb', line 60 def encode(token_data) Base64.urlsafe_encode64(Oj.dump(token_data, symbol_keys: true)) end |
.for_page_and_token!(page, token) ⇒ Object
Initialize a Cursor instance for the given page instance and encoded token.
@param page [Page] Page instance
@param token [String] Base64-encoded string data
@return [Cursor] Cursor
@raise [InvalidCursor] if the token can't be decoded or if the cursor data was tampered.
@raise [OrderChanged] if token generated from a page with a different `:order` definition.
@raise [QueryChanged] if token generated from a page with a different `:ar_relation`.
15 16 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 |
# File 'lib/rotulus/cursor.rb', line 15 def for_page_and_token!(page, token) data = decode(token) reference_record = Record.new(page, data[:f]) direction = data[:d] created_at = Time.at(data[:c]).utc cursor_state = data[:cs].presence order_state = data[:os].presence query_state = data[:qs].presence cursor = new(reference_record, direction, created_at: created_at) raise InvalidCursor if cursor.state != cursor_state if page.order_state != order_state raise OrderChanged if Rotulus.configuration.restrict_order_change? return nil end if page.query_state != query_state raise QueryChanged if Rotulus.configuration.restrict_query_change? return nil end cursor end |
Instance Method Details
#expired? ⇒ Boolean
Checks if the cursor is expired
129 130 131 132 133 |
# File 'lib/rotulus/cursor.rb', line 129 def expired? return false if config.token_expires_in.nil? || created_at.nil? (created_at + config.token_expires_in) < Time.current end |
#next? ⇒ Boolean
Returns true if the cursor should retrieve the ‘next’ records from the last record of the previous page. Otherwise, returns false.
85 86 87 |
# File 'lib/rotulus/cursor.rb', line 85 def next? direction == :next end |
#prev? ⇒ Boolean
Returns true if the cursor should retrieve the ‘previous’ records from the first record of a page. Otherwise, returns false.
91 92 93 |
# File 'lib/rotulus/cursor.rb', line 91 def prev? !next? end |
#sql ⇒ String
Generate the SQL condition to filter the records of the next/previous page. The condition is generated based on the order definition and the referenced record’s values.
99 100 101 |
# File 'lib/rotulus/cursor.rb', line 99 def sql @sql ||= Arel.sql(record.sql_seek_condition(direction)) end |
#state ⇒ String
Generate a ‘state’ string for integrity checking of the reference record, direction, and created_at data from a decoded Cursor token.
120 121 122 123 124 |
# File 'lib/rotulus/cursor.rb', line 120 def state state_data = "#{record.state}#{direction}#{created_at.to_i}#{secret}" Digest::MD5.hexdigest(state_data) end |
#to_token ⇒ String Also known as: to_s
Generate the token: a Base64-encoded string representation of this cursor
106 107 108 109 110 111 112 113 |
# File 'lib/rotulus/cursor.rb', line 106 def to_token @token ||= self.class.encode(f: record.values.as_json, d: direction, c: created_at.to_i, cs: state, os: page.order_state, qs: page.query_state) end |