class RailsAiAdmin::Queries::List
Bounded, permission-aware scalar index query. No SQL expressions enter it.
Constants
- AREL_PREDICATES
- COUNT_LIMIT
- CountResult
-
The bounded number of records the same search/scope/filters match, capped like the index count. Same grants and validation as call.
- FILTER_VALUE
- MAX_FILTER_CONDITIONS
- PAGE_SIZE
-
Default page size; resources may choose 1..100 through ‘index`.
- Page
-
scope is the applied named scope’s name, or nil for the policy relation. sort/direction describe the applied order (nil sort: primary key only). count is nil unless the resource opts in; count_capped marks
COUNT_LIMIT.
Public Class Methods
Source
# File lib/rails_ai_admin/queries/list.rb, line 74 def self.filter_value(field, operator, raw, time_zone:) raise AccessDenied, "INVALID_QUERY" unless field.filter_operators.include?(operator.to_sym) && FILTER_VALUE.call(raw) value = field.coerce(raw, time_zone: time_zone) raise AccessDenied, "INVALID_QUERY" if value.nil? value rescue ArgumentError raise AccessDenied, "INVALID_QUERY", cause: nil end
Coerces each wire value through the field’s scalar contract. Values are bound by Arel; no operator, column or SQL text comes from the request.
Source
# File lib/rails_ai_admin/queries/list.rb, line 18 def initialize(context, resource) @access = Authorization::Access.new(context, resource) @resource = resource end
Public Instance Methods
Source
# File lib/rails_ai_admin/queries/list.rb, line 28 def call(search: nil, sort: nil, direction: "asc", page: "1", recover_empty_page: false, scope: nil, filters: nil, page_size: nil, offset: nil) @access.authorize!(:index) unless (page.is_a?(String) || page.is_a?(Integer)) && page.to_s.valid_encoding? && page.to_s.ascii_only? && page.to_s.match?(/\A[1-9]\d{0,3}\z/) && page.to_i <= 1000 raise AccessDenied, "INVALID_QUERY" end raise AccessDenied, "INVALID_QUERY" unless %w[asc desc].include?(direction) relation, named = filtered_relation(search: search, scope: scope, filters: filters) settings = @resource.index size = page_size || settings.page_size raise AccessDenied, "INVALID_QUERY" unless size.is_a?(Integer) && size.between?(1, Presentation::IndexSettings::MAX_PAGE_SIZE) raise AccessDenied, "INVALID_QUERY" unless offset.nil? || offset.is_a?(Integer) && offset.between?(0, 99_999) && !recover_empty_page relation, field, direction = sorted(relation, sort, direction) records = relation.offset(offset || (page.to_i - 1) * size).limit(size + 1).to_a number = page.to_i if recover_empty_page && records.empty? && number > 1 count = relation.except(:order).limit(number * size).count number = [ 1, (count - 1).div(size) + 1 ].max records = relation.offset((number - 1) * size).limit(size + 1).to_a end total = relation.except(:order).limit(COUNT_LIMIT + 1).count if settings.count == :exact Page.new(records: records.first(size), number: number, has_next: records.size > size, scope: named&.name&.to_s, sort: field&.name&.to_s, direction: field ? direction : "asc", page_size: size, count: total && [ total, COUNT_LIMIT ].min, count_capped: !!total && total > COUNT_LIMIT) end
scope: nil uses the declared default when it is currently granted; NamedScope::NONE applies no named scope at all. A named scope must be declared and granted; it is never discarded here. filters: { “field” => { “eq” | “gte” | “lte” => “wire string” } }, ANDed. Every field needs a current :filter grant and a declared operator.
Source
# File lib/rails_ai_admin/queries/list.rb, line 65 def count(search: nil, scope: nil, filters: nil) @access.authorize!(:index) relation, named = filtered_relation(search: search, scope: scope, filters: filters) total = relation.except(:order).limit(COUNT_LIMIT + 1).count CountResult.new([ total, COUNT_LIMIT ].min, total > COUNT_LIMIT, named&.name&.to_s) end
Source
# File lib/rails_ai_admin/queries/list.rb, line 55 def relation(search: nil, sort: nil, direction: "asc", scope: nil, filters: nil) @access.authorize!(:index) raise AccessDenied, "INVALID_QUERY" unless %w[asc desc].include?(direction) relation, = filtered_relation(search: search, scope: scope, filters: filters) sorted(relation, sort, direction).first end
The complete ordered relation the index shows across all its pages, with the same grants and validation. Callers bound it themselves.