Class: Supplejack::Search

Inherits:
Object
  • Object
show all
Includes:
Request
Defined in:
lib/supplejack/search.rb

Overview

rubocop:disable Metrics/ClassLength FIXME: make me smaller!

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Request

#delete, #get, #post, #put

Constructor Details

#initialize(params = {}) ⇒ Search

Returns a new instance of Search.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/supplejack/search.rb', line 21

def initialize(params={})
  @params = params.clone rescue {}
  @params[:facets] ||= Supplejack.facets.join(',')
  @params[:facets_per_page] ||= Supplejack.facets_per_page
  [:action, :controller].each {|p| @params.delete(p) }

  @text             = @params[:text]
  @geo_bbox         = @params[:geo_bbox]
  @record_type      = @params[:record_type]
  @record_type      = @record_type.to_i unless @record_type == "all"
  @page             = (@params[:page] || 1).to_i
  @per_page         = (@params[:per_page] || Supplejack.per_page).to_i
  @pagination_limit = @params[:pagination_limit] || Supplejack.pagination_limit
  @sort             = @params[:sort]
  @direction        = @params[:direction]
  @url_format       = Supplejack.url_format_klass.new(@params, self)
  @filters          = @url_format.filters

  @api_params       = @url_format.to_api_hash
  @record_klass     = @params[:record_klass] || Supplejack.record_klass

  # Do not execute the actual search right away, it should be lazy loaded
  # when the user needs one of the following values.
  @total    = nil
  @results  = nil
  @facets   = nil

  Supplejack.search_attributes.each do |attribute|
    # We have to define the attribute accessors for the filters at initialization of the search instance
    # otherwise because the rails initializer is run after the gem was loaded, only the default
    # Supplejack.search_attributes set in the Gem would be defined.

    self.class.send(:attr_accessor, attribute)
    self.send("#{attribute}=", @filters[attribute]) unless @filters[attribute] == 'all'
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(symbol, *args, &block) ⇒ Object



341
342
343
344
345
# File 'lib/supplejack/search.rb', line 341

def method_missing(symbol, *args, &block)
  if symbol.to_s.match(/has_(.+)\?/) && Supplejack.search_attributes.include?($1.to_sym)
    return has_filter_and_value?($1, args.first)
  end
end

Instance Attribute Details

#andObject

Returns the value of attribute and.



19
20
21
# File 'lib/supplejack/search.rb', line 19

def and
  @and
end

#api_paramsObject

Returns the value of attribute api_params.



19
20
21
# File 'lib/supplejack/search.rb', line 19

def api_params
  @api_params
end

#directionObject

Returns the value of attribute direction.



17
18
19
# File 'lib/supplejack/search.rb', line 17

def direction
  @direction
end

#filters(options = {}) ⇒ Array<Array>

Returns by default a array of two element arrays with all the active filters in the search object and their values

Examples:

Return a array of filters

search = Search.new(:i => {:content_partner => "Matapihi", :category => ["Images", "Videos"]})
search.filters => [[:content_partner, "Matapihi"], [:category, "Images"], [:category, "Videos"]]

Returns:

  • (Array<Array>)

    Array with two element arrays, each with the filter and its value.



67
68
69
# File 'lib/supplejack/search.rb', line 67

def filters
  @filters
end

#geo_bboxObject

Returns the value of attribute geo_bbox.



18
19
20
# File 'lib/supplejack/search.rb', line 18

def geo_bbox
  @geo_bbox
end

#orObject

Returns the value of attribute or.



19
20
21
# File 'lib/supplejack/search.rb', line 19

def or
  @or
end

#pageObject

Returns the value of attribute page.



17
18
19
# File 'lib/supplejack/search.rb', line 17

def page
  @page
end

#pagination_limitObject

Returns the value of attribute pagination_limit.



17
18
19
# File 'lib/supplejack/search.rb', line 17

def pagination_limit
  @pagination_limit
end

#paramsObject

Returns the value of attribute params.



19
20
21
# File 'lib/supplejack/search.rb', line 19

def params
  @params
end

#per_pageObject

Returns the value of attribute per_page.



17
18
19
# File 'lib/supplejack/search.rb', line 17

def per_page
  @per_page
end

#record_klassObject

Returns the value of attribute record_klass.



18
19
20
# File 'lib/supplejack/search.rb', line 18

def record_klass
  @record_klass
end

#record_typeObject

Returns the value of attribute record_type.



18
19
20
# File 'lib/supplejack/search.rb', line 18

def record_type
  @record_type
end

#resultsArray

Returns a array of Supplejack::Record objects wrapped in a Paginated Collection which provides methods for will_paginate and kaminari to work properly

It will initialize the Supplejack::Record objects with the class stored in Supplejack.record_klass, so that you can override any method provided by the Supplejack::Record module or create new methods. You can also provide a :record_klass option when initialing a Supplejack::Search object to override the record_klass on a per request basis.

Returns:

  • (Array)

    Array of Supplejack::Record objects



126
127
128
# File 'lib/supplejack/search.rb', line 126

def results
  @results
end

#sortObject

Returns the value of attribute sort.



18
19
20
# File 'lib/supplejack/search.rb', line 18

def sort
  @sort
end

#textObject

Returns the value of attribute text.



17
18
19
# File 'lib/supplejack/search.rb', line 17

def text
  @text
end

#url_formatObject

Returns the value of attribute url_format.



19
20
21
# File 'lib/supplejack/search.rb', line 19

def url_format
  @url_format
end

#withoutObject

Returns the value of attribute without.



19
20
21
# File 'lib/supplejack/search.rb', line 19

def without
  @without
end

Instance Method Details

#cacheable?Boolean

Returns:

  • (Boolean)


313
314
315
316
# File 'lib/supplejack/search.rb', line 313

def cacheable?
  return false if text.present? || page > 1
  return true
end

#categories(options = {}) ⇒ Hash{String => Integer}

Gets the category facet unrestricted by the current category filter

Returns:

  • (Hash{String => Integer})

    A hash of category names and counts



226
227
228
229
# File 'lib/supplejack/search.rb', line 226

def categories(options={})
  return @categories if @categories
  @categories = facet_values('category', options)
end

#counts(query_parameters = {}) ⇒ Hash{String => Integer}

Calculates counts for specific queries using solr’s facet.query

rubocop:disable Metrics/LineLength rubocop:enable Metrics/LineLength

Examples:

Request images with a large_thumbnail_url and of record_type = 1:

search.counts({"photos" => {:large_thumbnail_url => "all", :record_type => 1}})

Returns the following hash:

{"photos" => 100}

Parameters:

  • a (Hash{String => Hash{String => String}})

    hash with query names as keys and a hash with filters as values.

Returns:

  • (Hash{String => Integer})

    A hash with the query names as keys and the result count for every query as values



167
168
169
170
171
172
173
174
175
176
# File 'lib/supplejack/search.rb', line 167

def counts(query_parameters={})
  if Supplejack.enable_caching
    cache_key = Digest::MD5.hexdigest(counts_params(query_parameters).to_query)
    Rails.cache.fetch(cache_key, :expires_in => 1.day) do
      fetch_counts(query_parameters)
    end
  else
    fetch_counts(query_parameters)
  end
end

#counts_params(query_parameters = {}) ⇒ Object

Returns a hash with all the parameters required by the counts method



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/supplejack/search.rb', line 198

def counts_params(query_parameters={})
  query_with_filters = {}
  query_parameters.each_pair do |count_name, count_filters|
    count_filters = count_filters.symbolize_keys
    query_record_type = count_filters[:record_type].to_i
    type = query_record_type == 0 ? :items : :headings
    filters = self.url_format.and_filters(type).dup

    without_filters = self.url_format.without_filters(type).dup
    without_filters = Hash[without_filters.map {|key, value| ["-#{key}".to_sym, value]}]

    filters.merge!(without_filters)
    query_with_filters.merge!({count_name.to_sym => Supplejack::Util.deep_merge(filters, count_filters) })
  end

  params = {:facet_query => query_with_filters, :record_type => "all"}
  params[:text] = self.url_format.text
  params[:text] = self.text if self.text.present?
  params[:geo_bbox] = self.geo_bbox if self.geo_bbox.present?
  params[:query_fields] = self.url_format.query_fields
  params = merge_extra_filters(params)
  params
end

#execute_requestObject



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/supplejack/search.rb', line 294

def execute_request
  return @response if @response

  @api_params = merge_extra_filters(@api_params)

  begin
    if Supplejack.enable_caching && self.cacheable?
      cache_key = Digest::MD5.hexdigest("#{request_path}?#{@api_params.to_query}")
      @response = Rails.cache.fetch(cache_key, expires_in: 1.hour) do
        get(request_path, @api_params)
      end
    else
      @response = get(request_path, @api_params)
    end
  rescue StandardError => e
    @response = {'search' => {}}
  end
end

#facet(value) ⇒ Object



112
113
114
# File 'lib/supplejack/search.rb', line 112

def facet(value)
  self.facets.find { |facet| facet.name == value }
end

#facet_values(facet_name, options = {}) ⇒ Object



279
280
281
282
283
284
285
286
287
288
# File 'lib/supplejack/search.rb', line 279

def facet_values(facet_name, options={})
  if Supplejack.enable_caching
    cache_key = Digest::MD5.hexdigest(facet_values_params(facet_name).to_query)
    Rails.cache.fetch(cache_key, :expires_in => 1.day) do
      fetch_facet_values(facet_name, options)
    end
  else
    fetch_facet_values(facet_name, options)
  end
end

#facet_values_params(facet_name, options = {}) ⇒ Object

Returns a hash with all the parameters required by the facet_values method



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/supplejack/search.rb', line 260

def facet_values_params(facet_name, options={})
  memoized_values = instance_variable_get("@#{facet_name}_params")
  return memoized_values if memoized_values

  filters = self.url_format.and_filters
  filters.delete(facet_name.to_sym)

  facet_params = self.api_params
  facet_params[:and] = filters
  facet_params[:facets] = "#{facet_name}"
  facet_params[:per_page] = 0
  facet_params[:facets_per_page] = options[:facets_per_page] if options[:facets_per_page]

  facet_params = merge_extra_filters(facet_params)

  instance_variable_set("@#{facet_name}_params", facet_params)
  facet_params
end

#facets(options = {}) ⇒ Array<Supplejack::Facet>

Returns an array of facets for the current search criteria sorted by the order specified in Supplejack.facets

and responds to name and values

Examples:

facets return format

search.facets => [Supplejack::Facet]

Parameters:

  • options (Hash) (defaults to: {})

    Supported options: :drill_dates

Returns:

  • (Array<Supplejack::Facet>)

    Every element in the array is a Supplejack::Facet object,



102
103
104
105
106
107
108
109
110
# File 'lib/supplejack/search.rb', line 102

def facets(options={})
  return @facets if @facets
  self.execute_request

  facets = @response['search']['facets'] || {}

  facet_array = facets.sort_by {|facet, rows| Supplejack.facets.find_index(facet.to_sym) || 100 }
  @facets = facet_array.map {|name, values| Supplejack::Facet.new(name, values) }
end

#fetch_counts(query_parameters = {}) ⇒ Object



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/supplejack/search.rb', line 178

def fetch_counts(query_parameters={})
  begin
    response = get(request_path, counts_params(query_parameters))
    counts_hash = response['search']['facets']['counts']
  rescue StandardError => e
    counts_hash = {}
  end

  # When the search doesn't match any facets for the specified filters, Sunspot doesn't return any facets
  # at all. Here we add those keys with a value of 0.
  #
  query_parameters.each_pair do |count_name, count_filters|
    counts_hash[count_name.to_s] = 0 unless counts_hash[count_name.to_s]
  end

  counts_hash
end

#fetch_facet_values(facet_name, options = {}) ⇒ Hash{String => Integer}

Gets the facet values unrestricted by the current filter

Returns:

  • (Hash{String => Integer})

    A hash of facet names and counts



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/supplejack/search.rb', line 235

def fetch_facet_values(facet_name, options={})
  options.reverse_merge!(:all => true, :sort => nil)
  memoized_values = instance_variable_get("@#{facet_name}_values")
  return memoized_values if memoized_values

  begin
    response = get(request_path, facet_values_params(facet_name, options))
    @facet_values = response["search"]["facets"]["#{facet_name}"]
  rescue StandardError => e
    response = {"search" => {"result_count" => 0}}
    @facet_values = {}
  end

  @facet_values["All"] = response["search"]["result_count"] if options[:all]

  facet = Supplejack::Facet.new(facet_name, @facet_values)
  @facet_values = facet.values(options[:sort])

  instance_variable_set("@#{facet_name}_values", @facet_values)
  @facet_values
end

#has_filter_and_value?(filter, value) ⇒ Boolean

Convienence method to find out if the search object has any specific filter applied to it. It works for both single and multiple value filters. This methods are actually defined on method_missing.

Returns:

  • (Boolean)


335
336
337
338
339
# File 'lib/supplejack/search.rb', line 335

def has_filter_and_value?(filter, value)
  actual_value = *self.send(filter)
  return false unless actual_value
  actual_value.include?(value)
end

#merge_extra_filters(existing_filters) ⇒ Object

Adds any filters defined in the :or, :and or :without attr_accessors By setting them directly it allows to nest any conditions that is not normally possible though the item_hash URL format.



351
352
353
354
355
356
357
358
# File 'lib/supplejack/search.rb', line 351

def merge_extra_filters(existing_filters)
  and_filters = self.and.try(:any?) ? {:and => self.and} : {}
  or_filters = self.or.try(:any?) ? {:or => self.or} : {}
  without_filters = self.without.try(:any?) ? {:without => self.without} : {}
  extra_filters = and_filters.merge(or_filters).merge(without_filters)

  Util.deep_merge(existing_filters, extra_filters)
end

#options(filter_options = {}) ⇒ Object



87
88
89
# File 'lib/supplejack/search.rb', line 87

def options(filter_options={})
  @url_format.options(filter_options)
end

#record?Boolean

Returns:

  • (Boolean)


152
153
154
# File 'lib/supplejack/search.rb', line 152

def record?
  self.record_type == 0
end

#request_pathObject



290
291
292
# File 'lib/supplejack/search.rb', line 290

def request_path
  '/records'
end

#totalObject

Returns the total amount of records for the current search filters



146
147
148
149
150
# File 'lib/supplejack/search.rb', line 146

def total
  return @total if @total
  self.execute_request
  @total = @response['search']['result_count'].to_i
end