Module: Layered::Ui::ComboboxOptions
- Defined in:
- app/controllers/concerns/layered/ui/combobox_options.rb
Overview
Builds the JSON a combobox rendered with url: expects, so a remote
option list is a couple of lines in the host app's controller rather than
a hand-rolled endpoint:
class UsersController < ApplicationController
include Layered::Ui::ComboboxOptions
def
render json: (policy_scope(User), label: :name, search: [:name, :email])
end
end
The endpoint is deliberately plain: it reads term and page from the
query string and answers with
{ "options": [{ "label": "Ada Lovelace", "value": "7" }], "page": 1, "pages": 3, "count": 42 }
Ransack and Pagy are used when they are available - Ransack to build the
search predicate (so association attributes such as :author_name work),
Pagy to take the page - and there is a plain Active Record fallback for
when they are not, so neither gem is required.
Endpoints stay the host app's own actions on purpose: there is no generic "query any model" route to authorise, so the scope passed in is whatever the current user is allowed to see.
Constant Summary collapse
- DEFAULT_LIMIT =
20- LIKE_PREDICATES =
The predicates the Ransack-less fallback can build for itself, mapping a quoted column onto its condition. Ransack has many more, so the rest are an error rather than a query quietly matching on something else.
i_contlowers both sides, as Ransack's own does, rather than reaching for an ILIKE only PostgreSQL has. { cont: ->(column) { "#{column} LIKE :term" }, i_cont: ->(column) { "LOWER(#{column}) LIKE :term" } }.freeze
Instance Method Summary collapse
-
#l_ui_combobox_options(scope, label:, value: :id, search: nil, predicate: :cont, combinator: :or, term: nil, page: nil, limit: DEFAULT_LIMIT) ⇒ Object
Options: label: (Symbol/Proc) Attribute, or callable taking the record, used for the option's label.
Instance Method Details
#l_ui_combobox_options(scope, label:, value: :id, search: nil, predicate: :cont, combinator: :or, term: nil, page: nil, limit: DEFAULT_LIMIT) ⇒ Object
Options:
label: (Symbol/Proc) Attribute, or callable taking the record, used for the option's label.
value: (Symbol/Proc) Attribute, or callable, used for the option's value. Defaults to +:id+.
search: (Array) Attributes the term is matched against. Defaults to +label+, so it is
required when +label+ is a callable.
predicate: (Symbol) Ransack predicate for the match (default +:cont+; +:i_cont+ ignores case).
Without Ransack only +:cont+ and +:i_cont+ can be built; any other raises.
combinator: (Symbol) How the search attributes combine, +:or+ (default) or +:and+.
term: (String) The term to search for. Defaults to +params[:term]+.
page: (Integer) The page to return. Defaults to +params[:page]+.
limit: (Integer) Options per page (default 20).
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'app/controllers/concerns/layered/ui/combobox_options.rb', line 52 def (scope, label:, value: :id, search: nil, predicate: :cont, combinator: :or, term: nil, page: nil, limit: DEFAULT_LIMIT) # Both come off the query string, where a parameter can arrive as any # shape Rack can parse - +?page[]=1+ hands over an Array - so each is # read through to_s. A page that makes no sense is the first page, as a # missing one is, rather than an exception from the coercion. term = (term.nil? ? l_ui_combobox_request_param(:term) : term).to_s.strip page = (page.nil? ? l_ui_combobox_request_param(:page) : page).to_s.to_i page = 1 if page < 1 matches = term.empty? ? scope.all : l_ui_combobox_matches(scope, search || [label], term, predicate, combinator) matches = l_ui_combobox_ordered(matches, label) records, count, pages = l_ui_combobox_page(matches, page, limit) { options: records.map do |record| { label: l_ui_combobox_attribute(record, label).to_s, value: l_ui_combobox_attribute(record, value).to_s } end, page: page, pages: pages, count: count } end |