Class: Autoselect::Base

Inherits:
Object
  • Object
show all
Includes:
Operators
Defined in:
lib/autoselect/base.rb

Overview

lib/autoselect/base.rb

Base class for all model-specific autoselect implementations. Mirrors the pattern of lib/queries/ where each model subclasses a shared base. Generated via rails generate taxon_works:autoselect.

Usage:

class Autoselect::TaxonName::Autoselect < Autoselect::Base
...
end

Claude wrote > 50% of this class.

Constant Summary

Constants included from Operators

Operators::OPERATORS

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Operators

included, #parse_operators

Constructor Details

#initialize(term: nil, level: nil, project_id: nil, user_id: nil, show_info: true, **kwargs) ⇒ Base

Returns a new instance of Base.

Parameters:

  • term (String, nil) (defaults to: nil) —

    raw input term (may contain operator prefix)

  • level (String, nil) (defaults to: nil) —

    the level key to query

  • project_id (Integer, nil) (defaults to: nil)
  • user_id (Integer, nil) (defaults to: nil)
  • show_info (Boolean, String) (defaults to: true) —

    when false/'false', record_info_html is skipped

  • kwargs (Hash) —

    any level-specific filter params



43
44
45
46
47
48
49
50
51
52
53
# File 'lib/autoselect/base.rb', line 43

def initialize(
  term: nil, level: nil, project_id: nil, user_id: nil, show_info: true,
  **kwargs
)
  @raw_term = term.presence
  @requested_level = level.presence
  @project_id = project_id
  @user_id = user_id
  @show_info = show_info.to_s != 'false'
  @level_params = kwargs
end

Instance Attribute Details

#effective_term ⇒ String? (readonly)

Returns raw search term with operators removed (in theory).

Returns:

  • (String, nil) —

    raw search term with operators removed (in theory)



20
21
22
# File 'lib/autoselect/base.rb', line 20

def effective_term
  @effective_term
end

#level_params ⇒ Hash (readonly)

Returns extra level-specific params passed through from the controller.

Returns:

  • (Hash) —

    extra level-specific params passed through from the controller



35
36
37
# File 'lib/autoselect/base.rb', line 35

def level_params
  @level_params
end

#operator ⇒ String? (readonly)

Returns the current operator raw search term with operators removed (in theory).

Returns:

  • (String, nil) —

    the current operator raw search term with operators removed (in theory)



23
24
25
# File 'lib/autoselect/base.rb', line 23

def operator
  @operator
end

#project_id ⇒ Integer? (readonly)

Returns:

  • (Integer, nil)


29
30
31
# File 'lib/autoselect/base.rb', line 29

def project_id
  @project_id
end

#raw_term ⇒ String? (readonly)

Returns the raw search term including any operator prefix.

Returns:

  • (String, nil) —

    the raw search term including any operator prefix



17
18
19
# File 'lib/autoselect/base.rb', line 17

def raw_term
  @raw_term
end

#requested_level ⇒ String? (readonly)

Returns the level key being queried (e.g. 'fast', 'smart').

Returns:

  • (String, nil) —

    the level key being queried (e.g. 'fast', 'smart')



26
27
28
# File 'lib/autoselect/base.rb', line 26

def requested_level
  @requested_level
end

#user_id ⇒ Integer? (readonly)

Returns:

  • (Integer, nil)


32
33
34
# File 'lib/autoselect/base.rb', line 32

def user_id
  @user_id
end

Instance Method Details

#build_config ⇒ Object (private)



119
120
121
122
123
124
125
126
127
# File 'lib/autoselect/base.rb', line 119

def build_config
  {
    resource: resource_path,
    levels: levels.map(&:metadata),
    operators: self.class.operator_definitions,
    map: level_map_keys,
    user_preferences: {}
  }
end

#config_response ⇒ Object (private)



85
86
87
88
89
90
91
92
93
94
# File 'lib/autoselect/base.rb', line 85

def config_response
  Autoselect::Response.new(
    config: build_config,
    request: nil,
    level: nil,
    results: nil,
    next_level: nil,
    level_map: level_map_keys
  ).as_json
end

#execute_level(level_instance, effective_term, operator) ⇒ Object (private)



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/autoselect/base.rb', line 144

def execute_level(level_instance, effective_term, operator)
  return [] if nothing_to_search?

  # Record-list operators are disabled on external levels (e.g. CoL)
  return [] if level_instance.external? && record_list_operator?(operator)

  if record_list_operator?(operator)
    # Always route to the Smart level regardless of the client's active level,
    # so !b/!! work even when the user is on the Fast level (the default).
    smart = levels.find { |l| l.is_a?(::Autoselect::Levels::Smart) }
    target = smart || level_instance
    return target.call(term: effective_term, operator:, project_id:, user_id:, **level_params)
  end

  level_instance.call(
    term: effective_term,
    operator:,
    project_id:,
    user_id:,
    **level_params
  )
end

#find_level(key) ⇒ Object (private)



133
134
135
# File 'lib/autoselect/base.rb', line 133

def find_level(key)
  levels.find { |l| l.key.to_s == key.to_s } || levels.first
end

#format_results(records, level_instance) ⇒ Array<Hash> (private)

!! This is often overrode !! Build response item hashes from an array of records. Rendering is delegated to level_instance so external levels can supply their own. Subclasses may override to handle extension data (e.g. CoL pseudo-records).

Parameters:

Returns:

  • (Array<Hash>)


186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/autoselect/base.rb', line 186

def format_results(records, level_instance)
  records.map do |record|
    {
      id: record.id,
      global_id: record.respond_to?(:to_global_id) ? record.to_global_id.to_s : nil,
      label: level_instance.record_label(record),
      label_html: level_instance.record_label_html(record),
      info_html: @show_info ? level_instance.record_info_html(record) : '',
      response_values: response_values(record),
      extension: {}
    }
  end
end

#level_map_keys ⇒ Object (private)



129
130
131
# File 'lib/autoselect/base.rb', line 129

def level_map_keys
  levels.map { |l| l.key.to_s }
end

#levels ⇒ Array<Autoselect::Level>

Subclasses must override with their own level stack.

Returns:

Raises:

  • (NotImplementedError)


66
67
68
# File 'lib/autoselect/base.rb', line 66

def levels
  raise NotImplementedError, "#{self.class} must implement #levels"
end

#next_level_key(current_key) ⇒ Object (private)



137
138
139
140
141
142
# File 'lib/autoselect/base.rb', line 137

def next_level_key(current_key)
  map = level_map_keys
  idx = map.index(current_key.to_s)
  return nil if idx.nil? || idx >= map.length - 1
  map[idx + 1]
end

#nothing_to_search? ⇒ Boolean (private)

Returns true when the term is only an operator that needs a term after it (e.g. "!2", "!n" sent before the client strips it), so no level is searched; record-list operators (!r, !b, ...) need no term.

Returns:

  • (Boolean) —

    true when the term is only an operator that needs a term after it (e.g. "!2", "!n" sent before the client strips it), so no level is searched; record-list operators (!r, !b, ...) need no term



171
172
173
# File 'lib/autoselect/base.rb', line 171

def nothing_to_search?
  effective_term.blank? && !record_list_operator?(operator)
end

#record_list_operator?(operator) ⇒ Boolean (private)

Returns:

  • (Boolean)


175
176
177
# File 'lib/autoselect/base.rb', line 175

def record_list_operator?(operator)
  %i[recent recent_mine pinboard pinboard_top].include?(operator)
end

#resource_path ⇒ String

Override in subclasses, e.g. '/taxon_names/autoselect'

Returns:

  • (String) —

    the base URL path for this autoselect endpoint

Raises:

  • (NotImplementedError)


79
80
81
# File 'lib/autoselect/base.rb', line 79

def resource_path
  raise NotImplementedError, "#{self.class} must implement #resource_path"
end

#response ⇒ Hash

Returns the full response (config or term response), ready for render json:.

Returns:

  • (Hash) —

    the full response (config or term response), ready for render json:



56
57
58
59
60
61
62
# File 'lib/autoselect/base.rb', line 56

def response
  if raw_term.blank?
    config_response
  else
    term_response
  end
end

#response_values(record) ⇒ Hash

Subclasses override to specify which attribute keys to inject into the parent form.

Parameters:

Returns:

  • (Hash) —

    model-specific response_values for a given record.

Raises:

  • (NotImplementedError)


73
74
75
# File 'lib/autoselect/base.rb', line 73

def response_values(record)
  raise NotImplementedError, "#{self.class} must implement #response_values(record)"
end

#term_response ⇒ Object (private)



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/autoselect/base.rb', line 96

def term_response
  parsed = parse_operators(raw_term)

  @operator = parsed[:operator]
  @effective_term = parsed[:effective_term]

  level_instance = find_level(requested_level)
  results = execute_level(level_instance, effective_term, operator)
  formatted = format_results(results, level_instance)

  suppress_escalation =
    %i[pinboard pinboard_top].include?(operator) || nothing_to_search?

  Autoselect::Response.new(
    config: nil,
    request: { term: raw_term, level: requested_level, project_id:, operator: operator&.to_s },
    level: requested_level,
    results: formatted,
    next_level: (!suppress_escalation && formatted.empty?) ? next_level_key(requested_level) : nil,
    level_map: nil
  ).as_json
end