Class: ParamsReady::Pagination::KeysetPagination

Constant Summary

Constants inherited from ParamsReady::Parameter::AbstractStructParameter

ParamsReady::Parameter::AbstractStructParameter::EMPTY_HASH

Instance Attribute Summary

Attributes inherited from ParamsReady::Parameter::AbstractParameter

#definition

Instance Method Summary collapse

Methods included from AbstractPagination

#first_page, #last_page, #num_pages

Methods included from Marshaller::ParameterModule

#marshal

Methods inherited from ParamsReady::Parameter::AbstractStructParameter

#[], #[]=, #find_in_hash, #for_frontend, #for_model, #for_output, #wrap_output

Methods included from ParamsReady::Parameter::ComplexParameter

#update_child

Methods inherited from ParamsReady::Parameter::Parameter

#allows_undefined?, #definite_default?, #eligible_for_output?, #find_in_hash, #format, #format_self_permitted, #freeze, #hash, #hash_key, #initialize, #inspect_content, #is_default?, #is_definite?, #is_nil?, #is_undefined?, #memo, #memo!, #nil_default?, #populate_other, #set_from_input, #set_value, #to_hash_if_eligible, #unwrap, #unwrap_or, #wrap_output

Methods inherited from ParamsReady::Parameter::AbstractParameter

#==, #dup, #initialize, #inspect, intent_for_children, #match?, #populate, #to_hash, #update_if_applicable, #update_in

Methods included from Extensions::Freezer

#freeze_variable, #freeze_variables, #variables_to_freeze

Methods included from ParamsReady::Parameter::FromHash

#set_from_hash

Methods included from Extensions::Freezer::InstanceMethods

#freeze

Constructor Details

This class inherits a constructor from ParamsReady::Parameter::Parameter

Instance Method Details

#after_page_value(keyset) ⇒ Object



130
131
132
133
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 130

def after_page_value(keyset)
  keyset ||= {}
  { limit: limit, direction: :aft, keyset: keyset }
end

#before_page_value(keyset) ⇒ Object



125
126
127
128
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 125

def before_page_value(keyset)
  keyset ||= {}
  { limit: limit, direction: :bfr, keyset: keyset }
end

#cursorObject



170
171
172
173
174
175
176
177
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 170

def cursor
  return nil unless is_definite?

  keyset = self[:keyset]
  keyset.names.keys.map do |column_name|
    keyset[column_name].unwrap
  end
end

#cursor_columnsObject



151
152
153
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 151

def cursor_columns
  self[:keyset].names.keys
end

#cursor_columns_arel(ordering, arel_table, context, columns: cursor_columns) ⇒ Object



155
156
157
158
159
160
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 155

def cursor_columns_arel(ordering, arel_table, context, columns: cursor_columns)
  columns.map do |name|
    column = ordering.definition.columns[name]
    column.attribute(name, arel_table, context)
  end
end

#cursor_predicates(direction, keyset, ordering, arel_table, context) ⇒ Object



112
113
114
115
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 112

def cursor_predicates(direction, keyset, ordering, arel_table, context)
  direction = Direction.instance(direction)
  direction.cursor_predicates(keyset, ordering, arel_table, context)
end

#directionObject



143
144
145
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 143

def direction
  self[:direction].unwrap
end

#exists_predicate(subselect, ordering, arel_table) ⇒ Object



70
71
72
73
74
75
76
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 70

def exists_predicate(subselect, ordering, arel_table)
  table_alias = self.table_alias(arel_table)
  aliased = arel_table.alias(table_alias)
  select_manager = Arel::SelectManager.new.from(subselect.as(table_alias))
  related = related_clause(arel_table, aliased, ordering.definition.primary_keys)
  select_manager.where(related).project('1').exists
end

#first_page_valueObject



117
118
119
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 117

def first_page_value
  { limit: limit, direction: :aft, keyset: {} }
end

#keysetObject



147
148
149
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 147

def keyset
  self[:keyset].unwrap
end

#keyset_query(query, limit, direction, keyset, ordering, arel_table, context) ⇒ Object



17
18
19
20
21
22
23
24
25
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 17

def keyset_query(query, limit, direction, keyset, ordering, arel_table, context)
  cursor, grouping = cursor_predicates(direction, keyset, ordering, arel_table, context)
  cte = cursor.cte_for_query(query, arel_table) unless cursor.nil?
  query = query.where(grouping) unless grouping.nil?

  query = query.with(cte) unless cte.nil?
  ordered = query.order(ordering_arel(direction, ordering, arel_table, context))
  ordered.take(limit)
end

#keysets_for_relation(relation, limit, direction, keyset, ordering, context) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 27

def keysets_for_relation(relation, limit, direction, keyset, ordering, context)
  arel_table = relation.arel_table

  cursor, predicates = cursor_predicates(direction, keyset, ordering, arel_table, context)
  full_query = relation.where(predicates)
                       .reorder(ordering_arel(direction, ordering, arel_table, context))
                       .limit(limit)
                       .select(*cursor_columns_arel(ordering, arel_table, context))
  full_query = Arel::Nodes::SqlLiteral.new(full_query.to_sql)
  with_cte(relation, full_query, cursor)
end

#last_page_valueObject



121
122
123
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 121

def last_page_value
  { limit: limit, direction: :bfr, keyset: {} }
end

#limitObject



135
136
137
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 135

def limit
  self[:limit].unwrap
end

#limit_keyObject



139
140
141
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 139

def limit_key
  :limit
end

#ordering_arel(direction, ordering, arel_table, context) ⇒ Object



107
108
109
110
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 107

def ordering_arel(direction, ordering, arel_table, context)
  inverted = Direction.instance(direction).invert_ordering?
  ordering.to_arel(arel_table, context: context, inverted: inverted)
end

#paginate_query(query, ordering, arel_table, context) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 55

def paginate_query(query, ordering, arel_table, context)
  cursor, predicates = cursor_predicates(direction, keyset, ordering, arel_table, context)
  cte = cursor.cte_for_query(query, arel_table) unless cursor.nil?
  subquery = query.deep_dup
  subquery = subquery.where(predicates) unless predicates.nil?
  subquery = subquery.with(cte) unless cte.nil?

  subselect = subquery.order(ordering_arel(direction, ordering, arel_table, context))
    .take(limit)
    .project(primary_keys_arel(ordering, arel_table, context))

  exists = exists_predicate(subselect, ordering, arel_table)
  query.where(exists)
end

#paginate_relation(relation, ordering, context) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 39

def paginate_relation(relation, ordering, context)
  arel_table = relation.arel_table
  cursor, predicates = cursor_predicates(direction, keyset, ordering, arel_table, context)

  subselect = relation.where(predicates)
                      .reorder(ordering_arel(direction, ordering, arel_table, context))
                      .limit(limit)
                      .select(primary_keys_arel(ordering, arel_table, context))

  subselect_sql = Arel::Nodes::SqlLiteral.new(subselect.to_sql)
  subselect_sql = with_cte_grouped(relation, subselect_sql, cursor)

  exists = exists_predicate(subselect_sql, ordering, arel_table)
  relation.where(exists)
end

#primary_keys_arel(ordering, arel_table, context) ⇒ Object



162
163
164
165
166
167
168
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 162

def primary_keys_arel(ordering, arel_table, context)
  columns = cursor_columns.lazy.select do |name|
    ordering.definition.primary_keys.member? name
  end

  cursor_columns_arel(ordering, arel_table, context, columns: columns).force
end


78
79
80
81
82
83
84
85
86
87
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 78

def related_clause(arel_table, aliased, primary_keys)
  cursor_columns.reduce(nil) do |clause, name|
    next clause unless primary_keys.member?(name)

    predicate = arel_table[name].eq(aliased[name])
    next predicate if clause.nil?

    next clause.and(predicate)
  end
end

#select_keysets(query, limit, direction, keyset, ordering, arel_table, context) ⇒ Object



12
13
14
15
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 12

def select_keysets(query, limit, direction, keyset, ordering, arel_table, context)
  query = keyset_query(query, limit, direction, keyset, ordering, arel_table, context)
  query.project(*cursor_columns_arel(ordering, arel_table, context))
end

#table_alias(arel_table) ⇒ Object



103
104
105
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 103

def table_alias(arel_table)
  Helpers::ArelBuilder.safe_name "#{arel_table.name}_#{cursor_columns.join('_')}"
end

#with_cte(relation, select, cursor) ⇒ Object



94
95
96
97
98
99
100
101
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 94

def with_cte(relation, select, cursor)
  return select if cursor.nil?

  cte = cursor.cte_for_relation(relation)
  return select if cte.nil?

  Arel::Nodes::SqlLiteral.new([cte.to_sql, select].join(' '))
end

#with_cte_grouped(relation, select, cursor) ⇒ Object



89
90
91
92
# File 'lib/params_ready/pagination/keyset_pagination.rb', line 89

def with_cte_grouped(relation, select, cursor)
  with_cte = with_cte(relation, select, cursor)
  Arel::Nodes::Grouping.new(with_cte)
end