supplejack_client 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +27 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +16 -0
  5. data/Guardfile +24 -0
  6. data/LICENSE.txt +674 -0
  7. data/README.md +153 -0
  8. data/Rakefile +9 -0
  9. data/lib/generators/locales/en.yml +11 -0
  10. data/lib/generators/supplejack/install_generator.rb +28 -0
  11. data/lib/generators/templates/README +19 -0
  12. data/lib/generators/templates/supplejack_client.rb +120 -0
  13. data/lib/supplejack/config.rb +116 -0
  14. data/lib/supplejack/controllers/helpers.rb +172 -0
  15. data/lib/supplejack/engine.rb +20 -0
  16. data/lib/supplejack/exceptions.rb +17 -0
  17. data/lib/supplejack/facet.rb +33 -0
  18. data/lib/supplejack/item.rb +94 -0
  19. data/lib/supplejack/item_relation.rb +73 -0
  20. data/lib/supplejack/log_subscriber.rb +58 -0
  21. data/lib/supplejack/paginated_collection.rb +61 -0
  22. data/lib/supplejack/record.rb +147 -0
  23. data/lib/supplejack/request.rb +95 -0
  24. data/lib/supplejack/search.rb +346 -0
  25. data/lib/supplejack/url_formats/item_hash.rb +208 -0
  26. data/lib/supplejack/user.rb +132 -0
  27. data/lib/supplejack/user_set.rb +349 -0
  28. data/lib/supplejack/user_set_relation.rb +143 -0
  29. data/lib/supplejack/util.rb +120 -0
  30. data/lib/supplejack/version.rb +10 -0
  31. data/lib/supplejack_client.rb +29 -0
  32. data/spec/spec_helper.rb +23 -0
  33. data/spec/supplejack/controllers/helpers_spec.rb +277 -0
  34. data/spec/supplejack/facet_spec.rb +44 -0
  35. data/spec/supplejack/item_relation_spec.rb +111 -0
  36. data/spec/supplejack/item_spec.rb +115 -0
  37. data/spec/supplejack/log_subscriber_spec.rb +40 -0
  38. data/spec/supplejack/paginated_collection_spec.rb +43 -0
  39. data/spec/supplejack/record_spec.rb +255 -0
  40. data/spec/supplejack/request_spec.rb +195 -0
  41. data/spec/supplejack/search_spec.rb +727 -0
  42. data/spec/supplejack/url_formats/item_hash_spec.rb +341 -0
  43. data/spec/supplejack/user_set_relation_spec.rb +149 -0
  44. data/spec/supplejack/user_set_spec.rb +465 -0
  45. data/spec/supplejack/user_spec.rb +159 -0
  46. data/supplejack_client.gemspec +30 -0
  47. metadata +159 -0
@@ -0,0 +1,61 @@
1
+ # The Supplejack Client code is Crown copyright (C) 2014, New Zealand Government,
2
+ # and is licensed under the GNU General Public License, version 3.
3
+ # See https://github.com/DigitalNZ/supplejack_client for details.
4
+ #
5
+ # Supplejack was created by DigitalNZ at the National Library of NZ
6
+ # and the Department of Internal Affairs. http://digitalnz.org/supplejack
7
+
8
+ module Supplejack
9
+ class PaginatedCollection
10
+ instance_methods.each { |m| undef_method m unless m =~ /^__|instance_eval|object_id/ }
11
+
12
+ attr_reader :current_page, :per_page
13
+ attr_accessor :total_count
14
+ alias :total_entries :total_count
15
+ alias :total_entries= :total_count=
16
+ alias :limit_value :per_page
17
+
18
+ def initialize(collection, page, per_page, total)
19
+ @collection = collection
20
+ @current_page = page
21
+ @per_page = per_page
22
+ @total_count = total
23
+ end
24
+
25
+ def total_pages
26
+ (total_count.to_f / per_page).ceil
27
+ end
28
+ alias :num_pages :total_pages
29
+
30
+ def first_page?
31
+ current_page == 1
32
+ end
33
+
34
+ def last_page?
35
+ current_page >= total_pages
36
+ end
37
+
38
+ def previous_page
39
+ current_page > 1 ? (current_page - 1) : nil
40
+ end
41
+
42
+ def next_page
43
+ current_page < total_pages ? (current_page + 1) : nil
44
+ end
45
+
46
+ def out_of_bounds?
47
+ current_page > total_pages
48
+ end
49
+
50
+ def offset
51
+ (current_page - 1) * per_page
52
+ end
53
+
54
+ private
55
+
56
+ def method_missing(method, *args, &block)
57
+ @collection.send(method, *args, &block)
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,147 @@
1
+ # The Supplejack Client code is Crown copyright (C) 2014, New Zealand Government,
2
+ # and is licensed under the GNU General Public License, version 3.
3
+ # See https://github.com/DigitalNZ/supplejack_client for details.
4
+ #
5
+ # Supplejack was created by DigitalNZ at the National Library of NZ
6
+ # and the Department of Internal Affairs. http://digitalnz.org/supplejack
7
+
8
+ require 'supplejack/search'
9
+
10
+ module Supplejack
11
+ module Record
12
+ extend ActiveSupport::Concern
13
+
14
+ attr_accessor :attributes
15
+
16
+ included do
17
+ extend Supplejack::Request
18
+ extend ActiveModel::Naming
19
+ include ActiveModel::Conversion
20
+ end
21
+
22
+ def initialize(attributes={})
23
+ if attributes.is_a?(String)
24
+ attributes = JSON.parse(attributes) rescue {}
25
+ end
26
+ @attributes = attributes.symbolize_keys rescue {}
27
+ end
28
+
29
+ def id
30
+ id = @attributes[:id] || @attributes[:record_id]
31
+ id.to_i
32
+ end
33
+
34
+ def to_param
35
+ self.id
36
+ end
37
+
38
+ def title
39
+ @attributes[:title].present? ? @attributes[:title] : "Untitled"
40
+ end
41
+
42
+ # Returns a array of hashes containing all the record attributes and
43
+ # the schema each attribute belongs to. To set what fields belong to
44
+ # each schema there is a config option to set supplejack_fields and admin_fields
45
+ #
46
+ # @example
47
+ # record.metadata => [{:name => "location", :schema => "supplejack", :value => "Wellington" }, ...]
48
+ #
49
+ def metadata
50
+ metadata = []
51
+
52
+ ['supplejack', 'admin'].each do |schema|
53
+ Supplejack.send("#{schema}_fields").each do |field|
54
+ if @attributes.has_key?(field)
55
+ values = @attributes[field]
56
+ values ||= [] unless !!values == values #Testing if boolean
57
+ values = [values] unless values.is_a?(Array)
58
+ field = field.to_s.camelcase(:lower) if schema == "dcterms"
59
+ field = field.to_s.sub(/#{schema}_/, '')
60
+ values.each do |value|
61
+ metadata << {:name => field, :schema => schema, :value => value }
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ metadata
68
+ end
69
+
70
+ def format
71
+ raise NoMethodError, "undefined method 'format' for Supplejack::Record:Module" unless @attributes.has_key?(:format)
72
+ @attributes[:format]
73
+ end
74
+
75
+ # Some of the records in the API return an array of values, but in practice
76
+ # most of them have on only one value. What this does is just convert the array
77
+ # to a string for the methods defined in the configuration.
78
+ Supplejack.single_value_methods.each do |method|
79
+ define_method("#{method}") do
80
+ values = @attributes[method]
81
+ values.is_a?(Array) ? values.first : values
82
+ end
83
+ end
84
+
85
+ [:next_page, :previous_page, :next_record, :previous_record].each do |pagination_field|
86
+ define_method(pagination_field) do
87
+ @attributes[pagination_field]
88
+ end
89
+ end
90
+
91
+ def persisted?
92
+ true
93
+ end
94
+
95
+ def method_missing(symbol, *args, &block)
96
+ raise NoMethodError, "undefined method '#{symbol.to_s}' for Supplejack::Record:Module" unless @attributes.has_key?(symbol)
97
+ @attributes[symbol]
98
+ end
99
+
100
+ module ClassMethods
101
+
102
+ # Finds a record or array of records from the Supplejack API
103
+ #
104
+ # @params [ Integer, Array ] id A integer or array of integers representing the ID of records
105
+ # @params [ Hash ] options Search options used to perform a search in order to get the next/previous
106
+ # records within the search results.
107
+ #
108
+ # @return [ Supplejack::Record ] A record or array of records initialized with the class of where the Supplejack::Record module was included
109
+ #
110
+ def find(id_or_array, options={})
111
+ if id_or_array.is_a?(Array)
112
+ options = {:record_ids => id_or_array, :fields => Supplejack.fields.join(',') }
113
+ response = get("/records/multiple", options)
114
+ response["records"].map {|attributes| new(attributes) }
115
+ else
116
+ begin
117
+ # handle malformed id's before requesting anything.
118
+ id = id_or_array.to_i
119
+ raise(Supplejack::MalformedRequest, "'#{id_or_array}' is not a valid record id") if id <= 0
120
+
121
+ # Do not send any parameters in the :search key when the user didn't specify any options
122
+ # And also always send the :fields parameter
123
+ #
124
+ search_klass = Supplejack::Search
125
+ search_klass = Supplejack.search_klass.classify.constantize if Supplejack.search_klass.present?
126
+
127
+ any_options = options.try(:any?)
128
+
129
+ search = search_klass.new(options)
130
+ search_options = search.api_params
131
+ search_options = search.merge_extra_filters(search_options)
132
+
133
+ options = {:search => search_options}
134
+ options[:fields] = options[:search].delete(:fields)
135
+ options.delete(:search) unless any_options
136
+
137
+ response = get("/records/#{id}", options)
138
+ new(response['record'])
139
+ rescue RestClient::ResourceNotFound => e
140
+ raise Supplejack::RecordNotFound, "Record with ID #{id_or_array} was not found"
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ end
147
+ end
@@ -0,0 +1,95 @@
1
+ # The Supplejack Client code is Crown copyright (C) 2014, New Zealand Government,
2
+ # and is licensed under the GNU General Public License, version 3.
3
+ # See https://github.com/DigitalNZ/supplejack_client for details.
4
+ #
5
+ # Supplejack was created by DigitalNZ at the National Library of NZ
6
+ # and the Department of Internal Affairs. http://digitalnz.org/supplejack
7
+
8
+ require 'rest-client'
9
+
10
+ module Supplejack
11
+ module Request
12
+ extend ActiveSupport::Concern
13
+
14
+ def get(path, params={}, options={})
15
+ params ||= {}
16
+ url = full_url(path, options[:format], params)
17
+
18
+ started = Time.now
19
+ payload = {:path => path, :params => params, :options => options}
20
+
21
+ begin
22
+ result = RestClient::Request.execute(:url => url, :method => :get, :timeout => timeout(options))
23
+ result = JSON.parse(result) if result
24
+ rescue StandardError => e
25
+ payload[:exception] = [e.class.name, e.message]
26
+ raise e
27
+ ensure
28
+ duration = (Time.now - started)*1000 # Convert to miliseconds
29
+ solr_request_params = result["search"]['solr_request_params'] if result && result['search']
30
+ @subscriber = Supplejack::LogSubscriber.new
31
+ @subscriber.log_request(duration, payload, solr_request_params)
32
+ end
33
+
34
+ result
35
+ end
36
+
37
+ def post(path, params={}, payload={}, options={})
38
+ payload ||= {}
39
+ log_request(:post, path, params, payload) do
40
+ response = RestClient::Request.execute(:url => full_url(path, nil, params), :method => :post, :payload => payload.to_json, :timeout => timeout(options), :headers => {:content_type => :json, :accept => :json})
41
+ JSON.parse(response) rescue {}.to_json
42
+ end
43
+ end
44
+
45
+ def delete(path, params={}, options={})
46
+ log_request(:delete, path, params, {}) do
47
+ RestClient::Request.execute(:url => full_url(path, nil, params), :method => :delete, :timeout => timeout(options))
48
+ end
49
+ end
50
+
51
+ def put(path, params={}, payload={}, options={})
52
+ payload ||= {}
53
+ log_request(:put, path, params, payload) do
54
+ response = RestClient::Request.execute(:url => full_url(path, nil, params), :method => :put, :payload => payload.to_json, :timeout => timeout(options), :headers => {:content_type => :json, :accept => :json})
55
+ JSON.parse(response) rescue {}.to_json
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def full_url(path, format=nil, params={})
62
+ params ||= {}
63
+ format = format ? format : 'json'
64
+ params[:api_key] ||= Supplejack.api_key
65
+ params[:debug] = true if Supplejack.enable_debugging
66
+
67
+ Supplejack.api_url + path + ".#{format.to_s}" + '?' + params.to_query
68
+ end
69
+
70
+ def timeout(options={})
71
+ timeout = Supplejack.timeout.to_i == 0 ? 30 : Supplejack.timeout.to_i
72
+ options[:timeout] || timeout
73
+ end
74
+
75
+ def log_request(method, path, params={}, payload={})
76
+ information = {path: path}
77
+ information[:params] = params
78
+ information[:payload] = payload
79
+ information[:method] = method
80
+
81
+ begin
82
+ started = Time.now
83
+ yield
84
+ rescue StandardError => e
85
+ information[:exception] = [e.class.name, e.message]
86
+ raise e
87
+ ensure
88
+ duration = (Time.now - started)*1000 # Convert to miliseconds
89
+ @subscriber = Supplejack::LogSubscriber.new
90
+ @subscriber.log_request(duration, information, {})
91
+ end
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,346 @@
1
+ # The Supplejack Client code is Crown copyright (C) 2014, New Zealand Government,
2
+ # and is licensed under the GNU General Public License, version 3.
3
+ # See https://github.com/DigitalNZ/supplejack_client for details.
4
+ #
5
+ # Supplejack was created by DigitalNZ at the National Library of NZ
6
+ # and the Department of Internal Affairs. http://digitalnz.org/supplejack
7
+
8
+ require 'supplejack/request'
9
+ require 'digest/md5'
10
+
11
+ module Supplejack
12
+ class Search
13
+ include Supplejack::Request
14
+
15
+ attr_accessor :results, :text, :page, :per_page, :pagination_limit, :direction, :sort, :filters, :record_type, :record_klass
16
+ attr_accessor :url_format, :without, :and, :or, :params, :api_params
17
+
18
+ def initialize(params={})
19
+ @params = params.clone rescue {}
20
+ @params[:facets] ||= Supplejack.facets.join(',')
21
+ @params[:facets_per_page] ||= Supplejack.facets_per_page
22
+ [:action, :controller].each {|p| @params.delete(p) }
23
+
24
+ @text = @params[:text]
25
+ @geo_bbox = @params[:geo_bbox]
26
+ @record_type = @params[:record_type]
27
+ @record_type = @record_type.to_i unless @record_type == "all"
28
+ @page = (@params[:page] || 1).to_i
29
+ @per_page = (@params[:per_page] || Supplejack.per_page).to_i
30
+ @pagination_limit = @params[:pagination_limit] || Supplejack.pagination_limit
31
+ @sort = @params[:sort]
32
+ @direction = @params[:direction]
33
+ @url_format = Supplejack.url_format_klass.new(@params, self)
34
+ @filters = @url_format.filters
35
+ @api_params = @url_format.to_api_hash
36
+ @record_klass = @params[:record_klass] || Supplejack.record_klass
37
+
38
+ # Do not execute the actual search right away, it should be lazy loaded
39
+ # when the user needs one of the following values.
40
+ @total = nil
41
+ @results = nil
42
+ @facets = nil
43
+
44
+ Supplejack.search_attributes.each do |attribute|
45
+ # We have to define the attribute accessors for the filters at initialization of the search instance
46
+ # otherwise because the rails initializer is run after the gem was loaded, only the default
47
+ # Supplejack.search_attributes set in the Gem would be defined.
48
+
49
+ self.class.send(:attr_accessor, attribute)
50
+ self.send("#{attribute}=", @filters[attribute]) unless @filters[attribute] == 'all'
51
+ end
52
+ end
53
+
54
+ # Returns by default a array of two element arrays with all the active filters
55
+ # in the search object and their values
56
+ #
57
+ # @example Return a array of filters
58
+ # search = Search.new(:i => {:content_partner => "Matapihi", :category => ["Images", "Videos"]})
59
+ # search.filters => [[:content_partner, "Matapihi"], [:category, "Images"], [:category, "Videos"]]
60
+ #
61
+ # @return [ Array<Array> ] Array with two element arrays, each with the filter and its value.
62
+ #
63
+ def filters(options={})
64
+ options.reverse_merge!(:format => :array, :except => [])
65
+ return @filters if options[:format] == :hash
66
+
67
+ filters = []
68
+ @filters.each do |key, value|
69
+ unless options[:except].include?(key)
70
+ if value.is_a?(Array)
71
+ value.each do |v|
72
+ filters << [key, v]
73
+ end
74
+ else
75
+ filters << [key, value]
76
+ end
77
+ end
78
+ end
79
+
80
+ return filters
81
+ end
82
+
83
+ def options(filter_options={})
84
+ @url_format.options(filter_options)
85
+ end
86
+
87
+ # Returns an array of facets for the current search criteria sorted by
88
+ # the order specified in +Supplejack.facets+
89
+ #
90
+ # @example facets return format
91
+ # search.facets => [Supplejack::Facet]
92
+ #
93
+ # @param [ Hash ] options Supported options: :drill_dates
94
+ #
95
+ # @return [ Array<Supplejack::Facet> ] Every element in the array is a Supplejack::Facet object, and responds to name and values
96
+ #
97
+ def facets(options={})
98
+ return @facets if @facets
99
+ self.execute_request
100
+
101
+ facets = @response['search']['facets'] || {}
102
+
103
+ facet_array = facets.sort_by {|facet, rows| Supplejack.facets.find_index(facet.to_sym) || 100 }
104
+ @facets = facet_array.map {|name, values| Supplejack::Facet.new(name, values) }
105
+ end
106
+
107
+ def facet(value)
108
+ self.facets.find { |facet| facet.name == value }
109
+ end
110
+
111
+ # Returns a array of +Supplejack::Record+ objects wrapped in a Paginated Collection
112
+ # which provides methods for will_paginate and kaminari to work properly
113
+ #
114
+ # It will initialize the +Supplejack::Record+ objects with the class stored in
115
+ # +Supplejack.record_klass+, so that you can override any method provided by the +Supplejack::Record+
116
+ # module or create new methods. You can also provide a +:record_klass+ option
117
+ # when initialing a +Supplejack::Search+ object to override the record_klass on a per request basis.
118
+ #
119
+ # @return [ Array ] Array of +Supplejack::Record+ objects
120
+ #
121
+ def results
122
+ return @results if @results
123
+ self.execute_request
124
+
125
+ if @response['search']['results'].respond_to?(:map)
126
+ records = @response['search']['results'].map do |attributes|
127
+ @record_klass.classify.constantize.new(attributes)
128
+ end
129
+ else
130
+ records = []
131
+ end
132
+
133
+ last_page = [pagination_limit || total, total].min
134
+ @results = Supplejack::PaginatedCollection.new(records, page, per_page, last_page)
135
+ end
136
+
137
+ # Returns the total amount of records for the current search filters
138
+ #
139
+ # @returns [ Integer ] Number of records that match the current search criteria
140
+ #
141
+ def total
142
+ return @total if @total
143
+ self.execute_request
144
+ @total = @response['search']['result_count'].to_i
145
+ end
146
+
147
+ def record?
148
+ self.record_type == 0
149
+ end
150
+
151
+ # Calculates counts for specific queries using solr's facet.query
152
+ #
153
+ # @example Request images with a large_thumbnail_url and of record_type = 1:
154
+ # search.counts({"photos" => {:large_thumbnail_url => "all", :record_type => 1}})
155
+ # @example Returns the following hash:
156
+ # {"photos" => 100}
157
+ #
158
+ # @param [Hash{String => Hash{String => String}}] a hash with query names as keys and a hash with filters as values.
159
+ # @return [Hash{String => Integer}] A hash with the query names as keys and the result count for every query as values
160
+ #
161
+ def counts(query_parameters={})
162
+ if Supplejack.enable_caching
163
+ cache_key = Digest::MD5.hexdigest(counts_params(query_parameters).to_query)
164
+ Rails.cache.fetch(cache_key, :expires_in => 1.day) do
165
+ fetch_counts(query_parameters)
166
+ end
167
+ else
168
+ fetch_counts(query_parameters)
169
+ end
170
+ end
171
+
172
+ def fetch_counts(query_parameters={})
173
+ begin
174
+ response = get(request_path, counts_params(query_parameters))
175
+ counts_hash = response['search']['facets']['counts']
176
+ rescue StandardError => e
177
+ counts_hash = {}
178
+ end
179
+
180
+ # When the search doesn't match any facets for the specified filters, Sunspot doesn't return any facets
181
+ # at all. Here we add those keys with a value of 0.
182
+ #
183
+ query_parameters.each_pair do |count_name, count_filters|
184
+ counts_hash[count_name.to_s] = 0 unless counts_hash[count_name.to_s]
185
+ end
186
+
187
+ counts_hash
188
+ end
189
+
190
+ # Returns a hash with all the parameters required by the counts method
191
+ #
192
+ def counts_params(query_parameters={})
193
+ query_with_filters = {}
194
+ query_parameters.each_pair do |count_name, count_filters|
195
+ count_filters = count_filters.symbolize_keys
196
+ query_record_type = count_filters[:record_type].to_i
197
+ type = query_record_type == 0 ? :items : :headings
198
+ filters = self.url_format.and_filters(type).dup
199
+
200
+ without_filters = self.url_format.without_filters(type).dup
201
+ without_filters = Hash[without_filters.map {|key, value| ["-#{key}".to_sym, value]}]
202
+
203
+ filters.merge!(without_filters)
204
+ query_with_filters.merge!({count_name.to_sym => Supplejack::Util.deep_merge(filters, count_filters) })
205
+ end
206
+
207
+ params = {:facet_query => query_with_filters, :record_type => "all"}
208
+ params[:text] = self.url_format.text
209
+ params[:text] = self.text if self.text.present?
210
+ # params[:geo_bbox] = self.geo_bbox if self.geo_bbox.present?
211
+ params[:query_fields] = self.url_format.query_fields
212
+ params = merge_extra_filters(params)
213
+ params
214
+ end
215
+
216
+ # Gets the type facet unrestricted by the current type filter
217
+ #
218
+ # @return [Hash{String => Integer}] A hash of type names and counts
219
+ #
220
+ def categories(options={})
221
+ return @categories if @categories
222
+ @categories = facet_values('category', options)
223
+ end
224
+
225
+ # Gets the facet values unrestricted by the current filter
226
+ #
227
+ # @return [Hash{String => Integer}] A hash of facet names and counts
228
+ #
229
+ def fetch_facet_values(facet_name, options={})
230
+ options.reverse_merge!(:all => true, :sort => nil)
231
+ memoized_values = instance_variable_get("@#{facet_name}_values")
232
+ return memoized_values if memoized_values
233
+
234
+ begin
235
+ response = get(request_path, facet_values_params(facet_name, options))
236
+ @facet_values = response["search"]["facets"]["#{facet_name}"]
237
+ rescue StandardError => e
238
+ response = {"search" => {"result_count" => 0}}
239
+ @facet_values = {}
240
+ end
241
+
242
+ @facet_values["All"] = response["search"]["result_count"] if options[:all]
243
+
244
+ facet = Supplejack::Facet.new(facet_name, @facet_values)
245
+ @facet_values = facet.values(options[:sort])
246
+
247
+ instance_variable_set("@#{facet_name}_values", @facet_values)
248
+ @facet_values
249
+ end
250
+
251
+ # Returns a hash with all the parameters required by the facet_values
252
+ # method
253
+ #
254
+ def facet_values_params(facet_name, options={})
255
+ memoized_values = instance_variable_get("@#{facet_name}_params")
256
+ return memoized_values if memoized_values
257
+
258
+ filters = self.url_format.and_filters
259
+ filters.delete(facet_name.to_sym)
260
+
261
+ facet_params = self.api_params
262
+ facet_params[:and] = filters
263
+ facet_params[:facets] = "#{facet_name}"
264
+ facet_params[:per_page] = 0
265
+ facet_params[:facets_per_page] = options[:facets_per_page] if options[:facets_per_page]
266
+
267
+ facet_params = merge_extra_filters(facet_params)
268
+
269
+ instance_variable_set("@#{facet_name}_params", facet_params)
270
+ facet_params
271
+ end
272
+
273
+ def facet_values(facet_name, options={})
274
+ if Supplejack.enable_caching
275
+ cache_key = Digest::MD5.hexdigest(facet_values_params(facet_name).to_query)
276
+ Rails.cache.fetch(cache_key, :expires_in => 1.day) do
277
+ fetch_facet_values(facet_name, options)
278
+ end
279
+ else
280
+ fetch_facet_values(facet_name, options)
281
+ end
282
+ end
283
+
284
+ def request_path
285
+ '/records'
286
+ end
287
+
288
+ def execute_request
289
+ return @response if @response
290
+
291
+ @api_params = merge_extra_filters(@api_params)
292
+
293
+ begin
294
+ if Supplejack.enable_caching && self.cacheable?
295
+ cache_key = Digest::MD5.hexdigest("#{request_path}?#{@api_params.to_query}")
296
+ @response = Rails.cache.fetch(cache_key, expires_in: 1.hour) do
297
+ get(request_path, @api_params)
298
+ end
299
+ else
300
+ @response = get(request_path, @api_params)
301
+ end
302
+ rescue StandardError => e
303
+ @response = {'search' => {}}
304
+ end
305
+ end
306
+
307
+ def cacheable?
308
+ return false if text.present? || page > 1
309
+ return true
310
+ end
311
+
312
+ # Convienence method to find out if the search object has any specific filter
313
+ # applied to it. It works for both single and multiple value filters.
314
+ # This methods are actually defined on method_missing.
315
+ #
316
+ # @exampe Return true when the search has a category filter set to images
317
+ # search = Search.new(:i => {:category => ["Images"]})
318
+ # search.has_category?("Images") => true
319
+
320
+ def has_filter_and_value?(filter, value)
321
+ actual_value = *self.send(filter)
322
+ return false unless actual_value
323
+ actual_value.include?(value)
324
+ end
325
+
326
+ def method_missing(symbol, *args, &block)
327
+ if symbol.to_s.match(/has_(.+)\?/) && Supplejack.search_attributes.include?($1.to_sym)
328
+ return has_filter_and_value?($1, args.first)
329
+ end
330
+ end
331
+
332
+ # Adds any filters defined in the :or, :and or :without attr_accessors
333
+ # By setting them directly it allows to nest any conditions that is not
334
+ # normally possible though the item_hash URL format.
335
+ #
336
+ def merge_extra_filters(existing_filters)
337
+ and_filters = self.and.try(:any?) ? {:and => self.and} : {}
338
+ or_filters = self.or.try(:any?) ? {:or => self.or} : {}
339
+ without_filters = self.without.try(:any?) ? {:without => self.without} : {}
340
+ extra_filters = and_filters.merge(or_filters).merge(without_filters)
341
+
342
+ Util.deep_merge(existing_filters, extra_filters)
343
+ end
344
+
345
+ end
346
+ end