Class: Trample::Search

Inherits:
Object
  • Object
show all
Includes:
Serializable
Defined in:
lib/trample/search.rb

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Serializable

#read_attribute_for_serialization

Class Attribute Details

._aggsObject

Returns the value of attribute _aggs.



13
14
15
# File 'lib/trample/search.rb', line 13

def _aggs
  @_aggs
end

._conditionsObject

Returns the value of attribute _conditions.



13
14
15
# File 'lib/trample/search.rb', line 13

def _conditions
  @_conditions
end

._modelsObject (readonly)

Returns the value of attribute _models.



14
15
16
# File 'lib/trample/search.rb', line 14

def _models
  @_models
end

Class Method Details

.aggregation(name, attrs = {}) {|| ... } ⇒ Object

Yields:

  • ()


31
32
33
34
35
36
# File 'lib/trample/search.rb', line 31

def self.aggregation(name, attrs = {})
  attrs.merge!(name: name)
  attrs[:order] = @_aggs.keys.length
  @_aggs[name] = Aggregation.new(attrs)
  yield @_aggs[name] if block_given?
end

.condition(name, attrs = {}) ⇒ Object



25
26
27
28
29
# File 'lib/trample/search.rb', line 25

def self.condition(name, attrs = {})
  attrs.merge!(name: name, search_klass: self)

  @_conditions[name] = Condition.new(attrs)
end

.inherited(klass) ⇒ Object



19
20
21
22
23
# File 'lib/trample/search.rb', line 19

def self.inherited(klass)
  super
  klass._conditions = self._conditions.dup
  klass._aggs = self._aggs.dup
end

.model(*klasses) ⇒ Object



38
39
40
# File 'lib/trample/search.rb', line 38

def self.model(*klasses)
  @_models = klasses
end

.paginate(page_params) ⇒ Object



42
43
44
45
# File 'lib/trample/search.rb', line 42

def self.paginate(page_params)
  instance = new
  instance.paginate(page_params)
end

Instance Method Details

#agg(*names_or_payloads) ⇒ Object

todo refactor…



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/trample/search.rb', line 78

def agg(*names_or_payloads)
  names_or_payloads.each do |name_or_payload|
    name = name_or_payload
    selections = []
    if name_or_payload.is_a?(Hash)
      name = name_or_payload.keys.first if name_or_payload.is_a?(Hash)
      selections = Array(name_or_payload.values.first)
    end
    template = self.class._aggs[name.to_sym]
    raise AggregationNotDefinedError.new(self, name) unless template
    agg = self.aggregations.find { |a| a.name.to_sym == name.to_sym }

    if agg.nil?
      # N.B. deep dup so buckets don't mutate
      agg = Aggregation.new(deep_dup(template.attributes).merge(name: name.to_sym))
      agg.bucket_sort = template.bucket_sort
      self.aggregations << agg
    end

    selections.each do |key|
      bucket = agg.find_or_initialize_bucket(key)
      bucket.selected = true
    end
  end

  self
end

#aggregationsObject



121
122
123
124
# File 'lib/trample/search.rb', line 121

def aggregations
  @aggregations.sort! { |a, b| a.order <=> b.order }
  @aggregations
end

#aggregations=(aggregation_array) ⇒ Object

N.B rails may send nil here instead of empty array



107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/trample/search.rb', line 107

def aggregations=(aggregation_array)
  aggregation_array ||= []
  super([])

  aggregation_array.each do |aggregation_hash|
    if aggregation_hash[:buckets] # rails converting [] to nil
      selections = aggregation_hash[:buckets].select { |b| !!b[:selected] }.map { |b| b[:key] }
      agg(aggregation_hash[:name].to_sym => selections)
    else
      agg(aggregation_hash[:name].to_sym => [])
    end
  end
end

#backendObject



133
134
135
# File 'lib/trample/search.rb', line 133

def backend
  @backend ||= Backend::Searchkick.new(, self.class._models)
end

#condition(name) ⇒ Object



69
70
71
# File 'lib/trample/search.rb', line 69

def condition(name)
  ConditionProxy.new(name, self)
end

#conditions=(hash) ⇒ Object



126
127
128
129
130
131
# File 'lib/trample/search.rb', line 126

def conditions=(hash)
  super({})
  hash.each_pair do |name, value|
    condition(name).set(value)
  end
end

#find_in_batches(batch_size: 10_000) ⇒ Object

Uses elasticsearch scroll, as ES will blow up > 10_000 results, even if you are paginating.

  1. Execute a search, telling ES we are scrolling

  2. metadata.scroll_id is set on response

  3. Use the scroll_id to fetch the next resultset

  4. Repeat until results are exhausted



181
182
183
184
185
186
187
188
189
190
# File 'lib/trample/search.rb', line 181

def find_in_batches(batch_size: 10_000)
  .scroll = true
  .pagination.per_page = batch_size
  query!
  while !self.results.empty?
    yield self.results
    query!
  end
  self. = Metadata.new
end

#includes(includes) ⇒ Object



73
74
75
# File 'lib/trample/search.rb', line 73

def includes(includes)
  self..records[:includes] = includes
end

#paginate(page_params) ⇒ Object



47
48
49
50
51
52
# File 'lib/trample/search.rb', line 47

def paginate(page_params)
  page_params ||= {}
  .pagination.current_page = page_params[:number] if page_params[:number]
  .pagination.per_page = page_params[:size] if page_params[:size]
  self
end

#query!(options = { lookup: true }) ⇒ Object



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/trample/search.rb', line 137

def query!(options = { lookup: true })
  @records = nil
  hash = backend.query!(conditions, aggregations)

  load_autocompletes if options[:lookup]

  self..took = hash[:took]
  self..scroll_id = hash[:scroll_id]
  self..pagination.total = hash[:total]
  self.results = hash[:results]
  if !!.records[:load]
    records!
  else
    self.results
  end
end

#recordsObject

Todo only works for single-model search atm N.B. preserves sorting



156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/trample/search.rb', line 156

def records
  @records ||= begin
                 queried = self.class._models.first.where(id: results.map(&:_id))
                 queried = queried.includes(.records[:includes])
                 [].tap do |sorted|
                   results.each do |result|
                     model = queried.find { |m| m.id.to_s == result.id.to_s }
                     sorted << model
                   end
                 end
               end
end

#records!Object



169
170
171
172
# File 'lib/trample/search.rb', line 169

def records!
  @records = nil
  records
end

#sort(*fields) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/trample/search.rb', line 54

def sort(*fields)
  return self if fields.empty?

  sorts = fields.map do |f|
    if f.to_s.starts_with?('-')
      f.sub!('-','')
      {att: f, dir: :desc}
    else
      {att: f, dir: :asc}
    end
  end
  self..sort = sorts
  self
end