Class: Supplejack::Search

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

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.



18
19
20
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
# File 'lib/supplejack/search.rb', line 18

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



326
327
328
329
330
# File 'lib/supplejack/search.rb', line 326

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.



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

def and
  @and
end

#api_paramsObject

Returns the value of attribute api_params.



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

def api_params
  @api_params
end

#directionObject

Returns the value of attribute direction.



15
16
17
# File 'lib/supplejack/search.rb', line 15

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.



63
64
65
# File 'lib/supplejack/search.rb', line 63

def filters
  @filters
end

#orObject

Returns the value of attribute or.



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

def or
  @or
end

#pageObject

Returns the value of attribute page.



15
16
17
# File 'lib/supplejack/search.rb', line 15

def page
  @page
end

#pagination_limitObject

Returns the value of attribute pagination_limit.



15
16
17
# File 'lib/supplejack/search.rb', line 15

def pagination_limit
  @pagination_limit
end

#paramsObject

Returns the value of attribute params.



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

def params
  @params
end

#per_pageObject

Returns the value of attribute per_page.



15
16
17
# File 'lib/supplejack/search.rb', line 15

def per_page
  @per_page
end

#record_klassObject

Returns the value of attribute record_klass.



15
16
17
# File 'lib/supplejack/search.rb', line 15

def record_klass
  @record_klass
end

#record_typeObject

Returns the value of attribute record_type.



15
16
17
# File 'lib/supplejack/search.rb', line 15

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



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

def results
  @results
end

#sortObject

Returns the value of attribute sort.



15
16
17
# File 'lib/supplejack/search.rb', line 15

def sort
  @sort
end

#textObject

Returns the value of attribute text.



15
16
17
# File 'lib/supplejack/search.rb', line 15

def text
  @text
end

#url_formatObject

Returns the value of attribute url_format.



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

def url_format
  @url_format
end

#withoutObject

Returns the value of attribute without.



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

def without
  @without
end

Instance Method Details

#cacheable?Boolean

Returns:

  • (Boolean)


307
308
309
310
# File 'lib/supplejack/search.rb', line 307

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

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

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

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



161
162
163
164
165
166
167
168
169
170
# File 'lib/supplejack/search.rb', line 161

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



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/supplejack/search.rb', line 192

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



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/supplejack/search.rb', line 288

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



107
108
109
# File 'lib/supplejack/search.rb', line 107

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

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



273
274
275
276
277
278
279
280
281
282
# File 'lib/supplejack/search.rb', line 273

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



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/supplejack/search.rb', line 254

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

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, and responds to name and values



97
98
99
100
101
102
103
104
105
# File 'lib/supplejack/search.rb', line 97

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



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/supplejack/search.rb', line 172

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



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/supplejack/search.rb', line 229

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)


320
321
322
323
324
# File 'lib/supplejack/search.rb', line 320

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.



336
337
338
339
340
341
342
343
# File 'lib/supplejack/search.rb', line 336

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



83
84
85
# File 'lib/supplejack/search.rb', line 83

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

#record?Boolean

Returns:

  • (Boolean)


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

def record?
  self.record_type == 0
end

#request_pathObject



284
285
286
# File 'lib/supplejack/search.rb', line 284

def request_path
  '/records'
end

#totalObject

Returns the total amount of records for the current search filters



141
142
143
144
145
# File 'lib/supplejack/search.rb', line 141

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

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

Gets the type facet unrestricted by the current type filter

Returns:

  • (Hash{String => Integer})

    A hash of type names and counts



220
221
222
223
# File 'lib/supplejack/search.rb', line 220

def types(options={})
  return @types if @types
  @types = facet_values('type', options)
end