Module: Plutonium::Resource::Controllers::Typeahead

Extended by:
ActiveSupport::Concern
Included in:
Plutonium::Resource::Controller
Defined in:
lib/plutonium/resource/controllers/typeahead.rb

Overview

Backend dispatch for typeahead/autocomplete queries against resource form inputs and index filter inputs. Auto-mounted on every Plutonium resource via the interactive_resource_actions routing concern (see Plutonium::Routing::MapperExtensions).

The controller resolves what to query directly from the input definition + the model's association reflection — no widget indirection. Two source kinds are supported:

1. Static `choices: [...]` — case-insensitive substring filter.
2. Association — either `association_class:` set on the input,
 or inferred from `resource_class.reflect_on_association(name)`.

Association queries route through the associated resource's policy.relation_scope so users only see records they can read.

Constant Summary collapse

TYPEAHEAD_LIMIT =
50
FALLBACK_SEARCH_COLUMNS =

Priority list tried when the input doesn't tell us which column carries its label. Aligns with what to_label usually wraps. Used only as a last resort.

%w[name title label slug display_name email].freeze
LIKE_ESCAPE_CHAR =

Escapes the SQL LIKE wildcards % and _ (plus the escape char itself) so a user searching for "100%" doesn't match everything. The literal ! is used as the ESCAPE character — unambiguous across sqlite/postgres/mysql, no backslash-quoting surprises.

"!"

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.escape_like(value) ⇒ Object



60
61
62
# File 'lib/plutonium/resource/controllers/typeahead.rb', line 60

def self.escape_like(value)
  value.to_s.gsub(/[!%_]/) { |c| "#{LIKE_ESCAPE_CHAR}#{c}" }
end

.searchable_column_for(klass, label_method: nil) ⇒ Object

Returns the column to LIKE against when no search block is defined. Used by both the server (to build the WHERE clause) and the input component (to decide whether to attach the typeahead URL).

Resolution order:

  1. The input's label_method if it names a real column (so input :user, label_method: :email just works).
  2. The first match from FALLBACK_SEARCH_COLUMNS.
  3. nil — no usable column, server returns unfiltered.

The fallback is fine for moderate tables but uses a leading- wildcard LIKE which can't be served by a b-tree index. For large tables, declare a search block that uses a trigram or full-text index instead.



46
47
48
49
50
51
52
# File 'lib/plutonium/resource/controllers/typeahead.rb', line 46

def self.searchable_column_for(klass, label_method: nil)
  cols = klass.column_names
  if label_method && cols.include?(label_method.to_s)
    return label_method.to_s
  end
  FALLBACK_SEARCH_COLUMNS.find { |c| cols.include?(c) }
end

Instance Method Details

#typeahead_filterObject

GET //typeahead/filter/:name?q=...



87
88
89
90
91
92
93
94
95
# File 'lib/plutonium/resource/controllers/typeahead.rb', line 87

def typeahead_filter
  filter = current_query_object.filter_definitions[params[:name].to_sym]
  return head(:not_found) unless filter

  defn = filter.defined_inputs[:value]
  return head(:not_found) unless defn

  render_typeahead_response(defn, params[:name].to_sym)
end

#typeahead_inputObject

GET //typeahead/input/:name?q=...



73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/plutonium/resource/controllers/typeahead.rb', line 73

def typeahead_input
  field_name = params[:name].to_sym
  defn = current_definition.defined_inputs[field_name]
  # Inputs are often inferred from the model (no explicit
  # `input :foo` in the definition). Accept the request when the
  # field name maps to a real association even without an entry.
  unless defn || resource_class.reflect_on_association(field_name)
    return head(:not_found)
  end

  render_typeahead_response(defn || {}, field_name)
end