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.
- checksums.yaml +7 -0
- data/.gitignore +27 -0
- data/.rspec +2 -0
- data/Gemfile +16 -0
- data/Guardfile +24 -0
- data/LICENSE.txt +674 -0
- data/README.md +153 -0
- data/Rakefile +9 -0
- data/lib/generators/locales/en.yml +11 -0
- data/lib/generators/supplejack/install_generator.rb +28 -0
- data/lib/generators/templates/README +19 -0
- data/lib/generators/templates/supplejack_client.rb +120 -0
- data/lib/supplejack/config.rb +116 -0
- data/lib/supplejack/controllers/helpers.rb +172 -0
- data/lib/supplejack/engine.rb +20 -0
- data/lib/supplejack/exceptions.rb +17 -0
- data/lib/supplejack/facet.rb +33 -0
- data/lib/supplejack/item.rb +94 -0
- data/lib/supplejack/item_relation.rb +73 -0
- data/lib/supplejack/log_subscriber.rb +58 -0
- data/lib/supplejack/paginated_collection.rb +61 -0
- data/lib/supplejack/record.rb +147 -0
- data/lib/supplejack/request.rb +95 -0
- data/lib/supplejack/search.rb +346 -0
- data/lib/supplejack/url_formats/item_hash.rb +208 -0
- data/lib/supplejack/user.rb +132 -0
- data/lib/supplejack/user_set.rb +349 -0
- data/lib/supplejack/user_set_relation.rb +143 -0
- data/lib/supplejack/util.rb +120 -0
- data/lib/supplejack/version.rb +10 -0
- data/lib/supplejack_client.rb +29 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/supplejack/controllers/helpers_spec.rb +277 -0
- data/spec/supplejack/facet_spec.rb +44 -0
- data/spec/supplejack/item_relation_spec.rb +111 -0
- data/spec/supplejack/item_spec.rb +115 -0
- data/spec/supplejack/log_subscriber_spec.rb +40 -0
- data/spec/supplejack/paginated_collection_spec.rb +43 -0
- data/spec/supplejack/record_spec.rb +255 -0
- data/spec/supplejack/request_spec.rb +195 -0
- data/spec/supplejack/search_spec.rb +727 -0
- data/spec/supplejack/url_formats/item_hash_spec.rb +341 -0
- data/spec/supplejack/user_set_relation_spec.rb +149 -0
- data/spec/supplejack/user_set_spec.rb +465 -0
- data/spec/supplejack/user_spec.rb +159 -0
- data/supplejack_client.gemspec +30 -0
- 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
|