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 /<resource>/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 /<resource>/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