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.

Direct Known Subclasses

Otu::Autoselect, TaxonName::Autoselect

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



37
38
39
40
41
42
43
44
# File 'lib/autoselect/base.rb', line 37

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

#level_paramsHash (readonly)

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

Returns:

  • (Hash)

    extra level-specific params passed through from the controller



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

def level_params
  @level_params
end

#project_idInteger? (readonly)

Returns:

  • (Integer, nil)


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

def project_id
  @project_id
end

#raw_termString? (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_levelString? (readonly)

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

Returns:

  • (String, nil)

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



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

def requested_level
  @requested_level
end

#user_idInteger? (readonly)

Returns:

  • (Integer, nil)


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

def user_id
  @user_id
end

Instance Method Details

#build_configObject (private)



108
109
110
111
112
113
114
115
116
# File 'lib/autoselect/base.rb', line 108

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

#config_responseObject (private)



76
77
78
79
80
81
82
83
84
85
# File 'lib/autoselect/base.rb', line 76

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)



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/autoselect/base.rb', line 133

def execute_level(level_instance, effective_term, operator)
  # 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)



122
123
124
# File 'lib/autoselect/base.rb', line 122

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)

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>)


164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/autoselect/base.rb', line 164

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_keysObject (private)



118
119
120
# File 'lib/autoselect/base.rb', line 118

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

#levelsArray<Autoselect::Level>

Subclasses must override with their own level stack.

Returns:

Raises:

  • (NotImplementedError)


57
58
59
# File 'lib/autoselect/base.rb', line 57

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

#next_level_key(current_key) ⇒ Object (private)



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

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

#record_list_operator?(operator) ⇒ Boolean (private)

Returns:

  • (Boolean)


154
155
156
# File 'lib/autoselect/base.rb', line 154

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

#resource_pathString

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

Returns:

  • (String)

    the base URL path for this autoselect endpoint

Raises:

  • (NotImplementedError)


70
71
72
# File 'lib/autoselect/base.rb', line 70

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

#responseHash

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:



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

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)


64
65
66
# File 'lib/autoselect/base.rb', line 64

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

#term_responseObject (private)



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/autoselect/base.rb', line 87

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)

  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