Class: ElasticsearchRecord::Result

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/elasticsearch_record/result.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(response, columns = [], column_types = {}) ⇒ Result

initializes a new result object

Parameters:

  • response (Elasticsearch::API::Response, Object, nil)
  • columns (Array) (defaults to: [])
  • column_types (Hash) (defaults to: {})


25
26
27
28
29
30
31
32
33
34
# File 'lib/elasticsearch_record/result.rb', line 25

def initialize(response, columns = [], column_types = {})
  # contains either the response or creates an empty hash (if nil)
  @response = response.presence || {}

  # used to build computed_results
  @columns = columns

  # used to cast values
  @column_types = column_types
end

Instance Attribute Details

#column_typesObject (readonly)

Returns the value of attribute column_types.



19
20
21
# File 'lib/elasticsearch_record/result.rb', line 19

def column_types
  @column_types
end

#columnsObject (readonly)

Returns the value of attribute columns.



19
20
21
# File 'lib/elasticsearch_record/result.rb', line 19

def columns
  @columns
end

#responseObject (readonly)

Returns the value of attribute response.



19
20
21
# File 'lib/elasticsearch_record/result.rb', line 19

def response
  @response
end

Class Method Details

.empty(async: false) ⇒ ElasticsearchRecord::Result (frozen), ActiveRecord::FutureResult::Complete (frozen)

creates an empty response

Returns:



11
12
13
14
15
16
17
# File 'lib/elasticsearch_record/result.rb', line 11

def self.empty(async: false)
  if async
    EMPTY_ASYNC
  else
    EMPTY
  end
end

Instance Method Details

#[](idx) ⇒ Object



166
167
168
# File 'lib/elasticsearch_record/result.rb', line 166

def [](idx)
  computed_results[idx]
end

#aggregationsActiveSupport::HashWithIndifferentAccess, Hash

returns the response RAW aggregations hash.

Returns:

  • (ActiveSupport::HashWithIndifferentAccess, Hash)


103
104
105
# File 'lib/elasticsearch_record/result.rb', line 103

def aggregations
  response['aggregations']&.with_indifferent_access || {}
end

#bucketsActiveSupport::HashWithIndifferentAccess

returns the (nested) bucket values (and aggregated values) from the response aggregations.

Returns:

  • (ActiveSupport::HashWithIndifferentAccess)


109
110
111
112
113
114
115
116
# File 'lib/elasticsearch_record/result.rb', line 109

def buckets
  # aggregations are already a hash with key => data, but to prevent reference manipulation on the hash
  # we have to create a new one here...
  aggregations.reduce({}) { |buckets, (key, agg)|
    buckets[key] = _resolve_bucket(agg)
    buckets
  }.with_indifferent_access
end

#cancelObject

used by ActiveRecord



176
177
178
# File 'lib/elasticsearch_record/result.rb', line 176

def cancel # :nodoc:
  self
end

#cast_values(type_overrides = {}) ⇒ Object

used by ActiveRecord for "pluck"



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/elasticsearch_record/result.rb', line 181

def cast_values(type_overrides = {})
  # fast escape, if no hits are available
  return [] unless response['hits']

  # HINT: This is separated to avoid allocating a (nested) array per row
  if columns.one?
    # resolve the column key
    key = columns.first

    # resolve type from overrides or +#column_type+ method
    type = type_overrides.is_a?(Array) ? type_overrides.first : column_type(key, type_overrides)

    # EDGE-case for metadata fields
    if ActiveRecord::ConnectionAdapters::ElasticsearchAdapter..include?(key)
      # directly read from doc
      response['hits']['hits'].map { |doc| type.deserialize(doc[key]) }
    else
      results.map do |result|
        type.deserialize(result[key])
      end
    end
  else
    # resolve types from overrides or +#column_type+ method
    types = type_overrides.is_a?(Array) ? type_overrides : columns.map { |name| column_type(name, type_overrides) }

    size = types.size

    # EDGE-case for metadata fields - they have to be resolved from the doc, so we merge them into the +_source+
    rows = if (ActiveRecord::ConnectionAdapters::ElasticsearchAdapter. & columns).any?
             response['hits']['hits'].map { |doc|
               (doc['_source'] || {}).merge(doc.slice(*ActiveRecord::ConnectionAdapters::ElasticsearchAdapter.))
             }
           else
             response['hits']['hits'].map { |doc| doc['_source'] || {} }
           end

    rows.map do |result|
      Array.new(size) { |i|
        types[i].deserialize(result[columns[i]])
      }
    end
  end
end

#each(&block) ⇒ Object

Calls the given block once for each element in row collection, passing row as parameter.

Returns an Enumerator if no block is given.



146
147
148
149
150
151
152
# File 'lib/elasticsearch_record/result.rb', line 146

def each(&block)
  if block_given?
    computed_results.each(&block)
  else
    computed_results.to_enum { @computed_results.size }
  end
end

#empty?Boolean

Returns true if there are no records, otherwise false.

Returns:

  • (Boolean)


155
156
157
# File 'lib/elasticsearch_record/result.rb', line 155

def empty?
  length == 0
end

#hitsActiveSupport::HashWithIndifferentAccess, Hash

returns the response RAW hits hash. PLEASE NOTE: Does not return the nested hits (+response['hits']+) array!

Returns:

  • (ActiveSupport::HashWithIndifferentAccess, Hash)


97
98
99
# File 'lib/elasticsearch_record/result.rb', line 97

def hits
  response['hits']&.with_indifferent_access || {}
end

#includes_column?(name) ⇒ Boolean

Returns true if this result set includes the column named name. used by ActiveRecord

Returns:

  • (Boolean)


120
121
122
# File 'lib/elasticsearch_record/result.rb', line 120

def includes_column?(name)
  @columns&.include?(name)
end

#last(n = nil) ⇒ Object

Returns the last record(s) from the computed_results collection.



171
172
173
# File 'lib/elasticsearch_record/result.rb', line 171

def last(n = nil)
  n ? computed_results.last(n) : computed_results.last
end

#lengthInteger

Returns the number of elements in the response array. Either uses the hits length, the responses length (msearch) or the length of the tabular value rows (SQL / ES|QL).

Returns:

  • (Integer)


128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/elasticsearch_record/result.rb', line 128

def length
  if response.key?('hits')
    response['hits']['hits'].length
  elsif response.key?('responses')
    # used by +msearch+
    response['responses'].length
  elsif _tabular?
    # used by +sql+ & +esql+
    _tabular_values.length
  else
    0
  end
end

#resultString

returns the response result string

Returns:

  • (String)


44
45
46
# File 'lib/elasticsearch_record/result.rb', line 44

def result
  response['result'] || ''
end

#resultsArray

Returns the RAW _source data from each hit. PLEASE NOTE: The array will only contain the RAW data from each _source (meta info like '_id' or '_score' are not included)

Returns:

  • (Array)


59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/elasticsearch_record/result.rb', line 59

def results
  # IMPORTANT: check against missing hits without any '_source' node.
  # This happens if the Elasticsearch query has the  +_source:false+ flag!
  if response['hits']
    response['hits']['hits'].map { |doc| doc['_source'] || {} }
  elsif _tabular?
    # a tabular (+SQL+ / +ES|QL+) response has no '_source' node - the row values are the data
    _results_from_tabular
  else
    []
  end
end

#rowsArray

returns an array of all rows. => All result values, depending on the provided columns. The rows is used by the ActiveRecord ConnectionAdapters and must not be removed!

Returns:

  • (Array)


76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/elasticsearch_record/result.rb', line 76

def rows
  # a tabular (+SQL+ / +ES|QL+) response is ALREADY positional - and it is positional to the
  # response's own columns, not to the (requested) +columns+ of the query.
  return _tabular_values if _tabular?

  # IMPORTANT: without provided +columns+ we cannot build positional rows - mapping over an
  # empty +columns+ array would return an empty array per hit and silently lose all data.
  # In this case we fall back to the raw +_source+ values.
  return results.map(&:values) if columns.blank?

  results.map { |doc|
    columns.map { |column|
      doc[column]
    }
  }
end

#to_aryObject Also known as: to_a

Returns an array of hashes representing each row record.



160
161
162
# File 'lib/elasticsearch_record/result.rb', line 160

def to_ary
  computed_results
end

#tookInteger

returns the response duration time

Returns:

  • (Integer)


38
39
40
# File 'lib/elasticsearch_record/result.rb', line 38

def took
  response['took']
end

#totalInteger

returns the response total value. either chops the total value directly from response, from hits or aggregations.

Returns:

  • (Integer)


51
52
53
54
# File 'lib/elasticsearch_record/result.rb', line 51

def total
  # chop total from response and not from the generated data
  @total ||= _total
end