blacklight 5.1.1 → 5.2.0

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/VERSION +1 -1
  4. data/app/assets/javascripts/blacklight/search_context.js +38 -24
  5. data/app/helpers/blacklight/blacklight_helper_behavior.rb +49 -70
  6. data/app/helpers/blacklight/catalog_helper_behavior.rb +1 -8
  7. data/app/helpers/blacklight/configuration_helper_behavior.rb +10 -2
  8. data/app/helpers/blacklight/facets_helper_behavior.rb +4 -1
  9. data/app/helpers/blacklight/search_history_constraints_helper_behavior.rb +2 -2
  10. data/app/helpers/blacklight/url_helper_behavior.rb +41 -7
  11. data/app/views/catalog/_facet_layout.html.erb +2 -2
  12. data/app/views/catalog/_per_page_widget.html.erb +4 -4
  13. data/app/views/catalog/_previous_next_doc.html.erb +1 -1
  14. data/app/views/catalog/_show_tools.html.erb +1 -1
  15. data/app/views/catalog/show.html.erb +1 -1
  16. data/app/views/layouts/blacklight.html.erb +1 -1
  17. data/blacklight.gemspec +1 -1
  18. data/config/jetty.yml +3 -0
  19. data/config/locales/blacklight.es.yml +220 -0
  20. data/gemfiles/rails4.1.gemfile +10 -0
  21. data/lib/blacklight/base.rb +1 -1
  22. data/lib/blacklight/catalog/search_context.rb +15 -15
  23. data/lib/blacklight/catalog.rb +19 -6
  24. data/lib/blacklight/configuration.rb +126 -31
  25. data/lib/blacklight/document_presenter.rb +168 -0
  26. data/lib/blacklight/request_builders.rb +288 -0
  27. data/lib/blacklight/routes.rb +6 -2
  28. data/lib/blacklight/solr/request.rb +1 -1
  29. data/lib/blacklight/solr_helper.rb +50 -323
  30. data/lib/blacklight/solr_response/facets.rb +7 -3
  31. data/lib/blacklight/utils.rb +39 -7
  32. data/lib/blacklight.rb +5 -3
  33. data/lib/generators/blacklight/install_generator.rb +17 -5
  34. data/lib/generators/blacklight/models_generator.rb +0 -1
  35. data/lib/generators/blacklight/templates/catalog_controller.rb +6 -0
  36. data/lib/generators/blacklight/templates/config/jetty.yml +8 -4
  37. data/lib/generators/blacklight/templates/config/solr.yml +2 -0
  38. data/spec/controllers/catalog_controller_spec.rb +41 -22
  39. data/spec/features/alternate_controller_spec.rb +1 -1
  40. data/spec/features/search_filters_spec.rb +24 -24
  41. data/spec/features/search_results_spec.rb +9 -4
  42. data/spec/features/search_sort_spec.rb +1 -1
  43. data/spec/helpers/blacklight_helper_spec.rb +87 -0
  44. data/spec/helpers/catalog_helper_spec.rb +5 -10
  45. data/spec/helpers/configuration_helper_spec.rb +22 -1
  46. data/spec/helpers/facets_helper_spec.rb +6 -0
  47. data/spec/helpers/render_constraints_helper_spec.rb +1 -2
  48. data/spec/helpers/url_helper_spec.rb +45 -2
  49. data/spec/lib/blacklight/routes_spec.rb +4 -4
  50. data/spec/lib/blacklight/solr_helper_spec.rb +364 -253
  51. data/spec/lib/blacklight/solr_response/facets_spec.rb +82 -0
  52. data/spec/lib/blacklight/solr_response_spec.rb +3 -1
  53. data/spec/lib/document_presenter_spec.rb +216 -0
  54. data/spec/lib/utils_spec.rb +8 -0
  55. data/spec/test_app_templates/lib/generators/test_app_generator.rb +1 -1
  56. data/spec/views/catalog/index.html.erb_spec.rb +29 -21
  57. data/spec/views/catalog/show.html.erb_spec.rb +11 -7
  58. data/template.demo.rb +20 -0
  59. metadata +12 -4
  60. data/lib/generators/blacklight/jetty_generator.rb +0 -70
@@ -0,0 +1,168 @@
1
+ module Blacklight
2
+ class DocumentPresenter
3
+ include ActionView::Helpers::OutputSafetyHelper
4
+ include ActionView::Helpers::TagHelper
5
+
6
+ # @param [SolrDocument] document
7
+ # @param [ActionController::Base] controller scope for linking and generating urls
8
+ # @param [Blacklight::Configuration] configuration
9
+ def initialize(document, controller, configuration = controller.blacklight_config)
10
+ @document = document
11
+ @configuration = configuration
12
+ @controller = controller
13
+ end
14
+
15
+ ##
16
+ # Get the value of the document's "title" field, or a placeholder
17
+ # value (if empty)
18
+ #
19
+ # @param [SolrDocument] document
20
+ # @return [String]
21
+ def document_heading
22
+ render_field_value(@document[@configuration.view_config(:show).title_field] || @document.id)
23
+ end
24
+
25
+ ##
26
+ # Get the document's "title" to display in the <title> element.
27
+ # (by default, use the #document_heading)
28
+ #
29
+ # @see #document_heading
30
+ # @return [String]
31
+ def document_show_html_title
32
+ if @configuration.view_config(:show).html_title_field
33
+ render_field_value(@document[@configuration.view_config(:show).html_title_field])
34
+ else
35
+ document_heading
36
+ end
37
+ end
38
+
39
+ ##
40
+ # Render a value (or array of values) from a field
41
+ #
42
+ # @param [String] value or list of values to display
43
+ # @param [Blacklight::Solr::Configuration::SolrField] solr field configuration
44
+ # @return [String]
45
+ def render_field_value value=nil, field_config=nil
46
+ safe_values = Array(value).collect { |x| x.respond_to?(:force_encoding) ? x.force_encoding("UTF-8") : x }
47
+
48
+ if field_config and field_config.itemprop
49
+ safe_values = safe_values.map { |x| content_tag :span, x, :itemprop => field_config.itemprop }
50
+ end
51
+
52
+ safe_join(safe_values, (field_config.separator if field_config) || field_value_separator)
53
+ end
54
+
55
+ ##
56
+ # Render the document index heading
57
+ #
58
+ # @param [Hash] opts
59
+ # @option opts [Symbol] :label Render the given field from the document
60
+ # @option opts [Proc] :label Evaluate the given proc
61
+ # @option opts [String] :label Render the given string
62
+ def render_document_index_label opts = {}
63
+ label = nil
64
+ label ||= @document.get(opts[:label], :sep => nil) if opts[:label].instance_of? Symbol
65
+ label ||= opts[:label].call(@document, opts) if opts[:label].instance_of? Proc
66
+ label ||= opts[:label] if opts[:label].is_a? String
67
+ label ||= @document.id
68
+ render_field_value label
69
+ end
70
+
71
+ ##
72
+ # Render the index field label for a document
73
+ #
74
+ # Allow an extention point where information in the document
75
+ # may drive the value of the field
76
+ # @param [String] field
77
+ # @param [Hash] opts
78
+ # @options opts [String] :value
79
+ def render_index_field_value field, options = {}
80
+ field_config = @configuration.index_fields[field]
81
+ value = options[:value] || get_field_values(field, field_config, options)
82
+
83
+ render_field_value value, field_config
84
+ end
85
+
86
+ ##
87
+ # Render the show field value for a document
88
+ #
89
+ # Allow an extention point where information in the document
90
+ # may drive the value of the field
91
+ # @param [String] field
92
+ # @param [Hash] options
93
+ # @options opts [String] :value
94
+ def render_document_show_field_value field, options={}
95
+ field_config = @configuration.show_fields[field]
96
+ value = options[:value] || get_field_values(field, field_config, options)
97
+
98
+ render_field_value value, field_config
99
+ end
100
+
101
+ ##
102
+ # Get the value for a document's field, and prepare to render it.
103
+ # - highlight_field
104
+ # - accessor
105
+ # - solr field
106
+ #
107
+ # Rendering:
108
+ # - helper_method
109
+ # - link_to_search
110
+ # TODO : maybe this should be merged with render_field_value, and the ugly signature
111
+ # simplified by pushing some of this logic into the "model"
112
+ # @param [SolrDocument] document
113
+ # @param [String] field name
114
+ # @param [Blacklight::Solr::Configuration::SolrField] solr field configuration
115
+ # @param [Hash] options additional options to pass to the rendering helpers
116
+ def get_field_values field, field_config, options = {}
117
+ # retrieving values
118
+ value = case
119
+ when (field_config and field_config.highlight)
120
+ # retrieve the document value from the highlighting response
121
+ @document.highlight_field(field_config.field).map { |x| x.html_safe } if @document.has_highlight_field? field_config.field
122
+ when (field_config and field_config.accessor)
123
+ # implicit method call
124
+ if field_config.accessor === true
125
+ @document.send(field)
126
+ # arity-1 method call (include the field name in the call)
127
+ elsif !field_config.accessor.is_a?(Array) && @document.method(field_config.accessor).arity != 0
128
+ @document.send(field_config.accessor, field)
129
+ # chained method calls
130
+ else
131
+ Array(field_config.accessor).inject(@document) do |result, method|
132
+ result.send(method)
133
+ end
134
+ end
135
+ else
136
+ # regular solr
137
+ @document.get(field, :sep => nil) if field
138
+ end
139
+
140
+ # rendering values
141
+ case
142
+ when (field_config and field_config.helper_method)
143
+ @controller.send(field_config.helper_method, options.merge(:document => @document, :field => field, :value => value))
144
+ when (field_config and field_config.link_to_search)
145
+ link_field = if field_config.link_to_search === true
146
+ field_config.field
147
+ else
148
+ field_config.link_to_search
149
+ end
150
+
151
+ Array(value).map do |v|
152
+ @controller.link_to render_field_value(v, field_config), @controller.search_action_path(@controller.add_facet_params(link_field, v, {}))
153
+ end if field
154
+ else
155
+ value
156
+ end
157
+ end
158
+
159
+ ##
160
+ # Default separator to use in #render_field_value
161
+ #
162
+ # @return [String]
163
+ def field_value_separator
164
+ ', '
165
+ end
166
+
167
+ end
168
+ end
@@ -0,0 +1,288 @@
1
+ module Blacklight
2
+ ##
3
+ # This module contains methods that are specified by SolrHelper.solr_search_params_logic
4
+ # They transform user parameters into parameters that are sent as a request to Solr when
5
+ # RequestBuilders#solr_search_params is called.
6
+ #
7
+ module RequestBuilders
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ # We want to install a class-level place to keep
12
+ # solr_search_params_logic method names. Compare to before_filter,
13
+ # similar design. Since we're a module, we have to add it in here.
14
+ # There are too many different semantic choices in ruby 'class variables',
15
+ # we choose this one for now, supplied by Rails.
16
+ class_attribute :solr_search_params_logic
17
+
18
+ # Set defaults. Each symbol identifies a _method_ that must be in
19
+ # this class, taking two parameters (solr_parameters, user_parameters)
20
+ # Can be changed in local apps or by plugins, eg:
21
+ # CatalogController.include ModuleDefiningNewMethod
22
+ # CatalogController.solr_search_params_logic += [:new_method]
23
+ # CatalogController.solr_search_params_logic.delete(:we_dont_want)
24
+ self.solr_search_params_logic = [:default_solr_parameters , :add_query_to_solr, :add_facet_fq_to_solr, :add_facetting_to_solr, :add_solr_fields_to_query, :add_paging_to_solr, :add_sorting_to_solr, :add_group_config_to_solr ]
25
+ end
26
+
27
+ # @returns a params hash for searching solr.
28
+ # The CatalogController #index action uses this.
29
+ # Solr parameters can come from a number of places. From lowest
30
+ # precedence to highest:
31
+ # 1. General defaults in blacklight config (are trumped by)
32
+ # 2. defaults for the particular search field identified by params[:search_field] (are trumped by)
33
+ # 3. certain parameters directly on input HTTP query params
34
+ # * not just any parameter is grabbed willy nilly, only certain ones are allowed by HTTP input)
35
+ # * for legacy reasons, qt in http query does not over-ride qt in search field definition default.
36
+ # 4. extra parameters passed in as argument.
37
+ #
38
+ # spellcheck.q will be supplied with the [:q] value unless specifically
39
+ # specified otherwise.
40
+ #
41
+ # Incoming parameter :f is mapped to :fq solr parameter.
42
+ def solr_search_params(user_params = params || {})
43
+ Blacklight::Solr::Request.new.tap do |solr_parameters|
44
+ solr_search_params_logic.each do |method_name|
45
+ send(method_name, solr_parameters, user_params)
46
+ end
47
+ end
48
+ end
49
+
50
+ ####
51
+ # Start with general defaults from BL config. Need to use custom
52
+ # merge to dup values, to avoid later mutating the original by mistake.
53
+ def default_solr_parameters(solr_parameters, user_params)
54
+ blacklight_config.default_solr_params.each do |key, value|
55
+ solr_parameters[key] = value.dup rescue value
56
+ end
57
+ end
58
+
59
+ ##
60
+ # Take the user-entered query, and put it in the solr params,
61
+ # including config's "search field" params for current search field.
62
+ # also include setting spellcheck.q.
63
+ def add_query_to_solr(solr_parameters, user_parameters)
64
+ ###
65
+ # Merge in search field configured values, if present, over-writing general
66
+ # defaults
67
+ ###
68
+ # legacy behavior of user param :qt is passed through, but over-ridden
69
+ # by actual search field config if present. We might want to remove
70
+ # this legacy behavior at some point. It does not seem to be currently
71
+ # rspec'd.
72
+ solr_parameters[:qt] = user_parameters[:qt] if user_parameters[:qt]
73
+
74
+ search_field_def = search_field_def_for_key(user_parameters[:search_field])
75
+ if (search_field_def)
76
+ solr_parameters[:qt] = search_field_def.qt
77
+ solr_parameters.merge!( search_field_def.solr_parameters) if search_field_def.solr_parameters
78
+ end
79
+
80
+ ##
81
+ # Create Solr 'q' including the user-entered q, prefixed by any
82
+ # solr LocalParams in config, using solr LocalParams syntax.
83
+ # http://wiki.apache.org/solr/LocalParams
84
+ ##
85
+ if (search_field_def && hash = search_field_def.solr_local_parameters)
86
+ local_params = hash.collect do |key, val|
87
+ key.to_s + "=" + solr_param_quote(val, :quote => "'")
88
+ end.join(" ")
89
+ solr_parameters[:q] = "{!#{local_params}}#{user_parameters[:q]}"
90
+ else
91
+ solr_parameters[:q] = user_parameters[:q] if user_parameters[:q]
92
+ end
93
+
94
+
95
+ ##
96
+ # Set Solr spellcheck.q to be original user-entered query, without
97
+ # our local params, otherwise it'll try and spellcheck the local
98
+ # params! Unless spellcheck.q has already been set by someone,
99
+ # respect that.
100
+ #
101
+ # TODO: Change calling code to expect this as a symbol instead of
102
+ # a string, for consistency? :'spellcheck.q' is a symbol. Right now
103
+ # rspec tests for a string, and can't tell if other code may
104
+ # insist on a string.
105
+ solr_parameters["spellcheck.q"] = user_parameters[:q] unless solr_parameters["spellcheck.q"]
106
+ end
107
+
108
+ ##
109
+ # Add any existing facet limits, stored in app-level HTTP query
110
+ # as :f, to solr as appropriate :fq query.
111
+ def add_facet_fq_to_solr(solr_parameters, user_params)
112
+
113
+ # convert a String value into an Array
114
+ if solr_parameters[:fq].is_a? String
115
+ solr_parameters[:fq] = [solr_parameters[:fq]]
116
+ end
117
+
118
+ # :fq, map from :f.
119
+ if ( user_params[:f])
120
+ f_request_params = user_params[:f]
121
+
122
+ f_request_params.each_pair do |facet_field, value_list|
123
+ Array(value_list).each do |value|
124
+ solr_parameters.append_filter_query facet_value_to_fq_string(facet_field, value)
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ ##
131
+ # Add appropriate Solr facetting directives in, including
132
+ # taking account of our facet paging/'more'. This is not
133
+ # about solr 'fq', this is about solr facet.* params.
134
+ def add_facetting_to_solr(solr_parameters, user_params)
135
+ # While not used by BL core behavior, legacy behavior seemed to be
136
+ # to accept incoming params as "facet.field" or "facets", and add them
137
+ # on to any existing facet.field sent to Solr. Legacy behavior seemed
138
+ # to be accepting these incoming params as arrays (in Rails URL with []
139
+ # on end), or single values. At least one of these is used by
140
+ # Stanford for "faux hieararchial facets".
141
+ if user_params.has_key?("facet.field") || user_params.has_key?("facets")
142
+ solr_parameters[:"facet.field"].concat( [user_params["facet.field"], user_params["facets"]].flatten.compact ).uniq!
143
+ end
144
+
145
+ blacklight_config.facet_fields.select { |field_name,facet|
146
+ facet.include_in_request || (facet.include_in_request.nil? && blacklight_config.add_facet_fields_to_solr_request)
147
+ }.each do |field_name, facet|
148
+ solr_parameters[:facet] ||= true
149
+
150
+ case
151
+ when facet.pivot
152
+ solr_parameters.append_facet_pivot with_ex_local_param(facet.ex, facet.pivot.join(","))
153
+ when facet.query
154
+ solr_parameters.append_facet_query facet.query.map { |k, x| with_ex_local_param(facet.ex, x[:fq]) }
155
+ else
156
+ solr_parameters.append_facet_fields with_ex_local_param(facet.ex, facet.field)
157
+ end
158
+
159
+ if facet.sort
160
+ solr_parameters[:"f.#{facet.field}.facet.sort"] = facet.sort
161
+ end
162
+
163
+ if facet.solr_params
164
+ facet.solr_params.each do |k, v|
165
+ solr_parameters[:"f.#{facet.field}.#{k}"] = v
166
+ end
167
+ end
168
+
169
+ # Support facet paging and 'more'
170
+ # links, by sending a facet.limit one more than what we
171
+ # want to page at, according to configured facet limits.
172
+ solr_parameters[:"f.#{facet.field}.facet.limit"] = (facet_limit_for(field_name) + 1) if facet_limit_for(field_name)
173
+ end
174
+ end
175
+
176
+ def add_solr_fields_to_query solr_parameters, user_parameters
177
+ blacklight_config.show_fields.select(&method(:should_add_to_solr)).each do |field_name, field|
178
+ if field.solr_params
179
+ field.solr_params.each do |k, v|
180
+ solr_parameters[:"f.#{field.field}.#{k}"] = v
181
+ end
182
+ end
183
+ end
184
+
185
+ blacklight_config.index_fields.select(&method(:should_add_to_solr)).each do |field_name, field|
186
+ if field.highlight
187
+ solr_parameters[:hl] = true
188
+ solr_parameters.append_highlight_field field.field
189
+ end
190
+
191
+ if field.solr_params
192
+ field.solr_params.each do |k, v|
193
+ solr_parameters[:"f.#{field.field}.#{k}"] = v
194
+ end
195
+ end
196
+ end
197
+ end
198
+
199
+ ###
200
+ # copy paging params from BL app over to solr, changing
201
+ # app level per_page and page to Solr rows and start.
202
+ def add_paging_to_solr(solr_params, user_params)
203
+
204
+ # Now any over-rides from current URL?
205
+ solr_params[:rows] = user_params[:rows].to_i unless user_params[:rows].blank?
206
+ solr_params[:rows] = user_params[:per_page].to_i unless user_params[:per_page].blank?
207
+
208
+ # Do we need to translate :page to Solr :start?
209
+ unless user_params[:page].blank?
210
+ # already set solr_params["rows"] might not be the one we just set,
211
+ # could have been from app defaults too. But we need one.
212
+ # raising is consistent with prior RSolr magic keys behavior.
213
+ # We could change this to default to 10, or to raise on startup
214
+ # from config instead of at runtime.
215
+ if solr_params[:rows].blank?
216
+ raise Exception.new("To use pagination when no :per_page is supplied in the URL, :rows must be configured in blacklight_config default_solr_params")
217
+ end
218
+ solr_params[:start] = solr_params[:rows].to_i * (user_params[:page].to_i - 1)
219
+ solr_params[:start] = 0 if solr_params[:start].to_i < 0
220
+ end
221
+
222
+ solr_params[:rows] ||= blacklight_config.per_page.first unless blacklight_config.per_page.blank?
223
+
224
+ solr_params[:rows] = blacklight_config.max_per_page if solr_params[:rows].to_i > blacklight_config.max_per_page
225
+ end
226
+
227
+ ###
228
+ # copy sorting params from BL app over to solr
229
+ def add_sorting_to_solr(solr_parameters, user_params)
230
+ if user_params[:sort].blank? and sort_field = blacklight_config.default_sort_field
231
+ # no sort param provided, use default
232
+ solr_parameters[:sort] = sort_field.sort unless sort_field.sort.blank?
233
+ elsif sort_field = blacklight_config.sort_fields[user_params[:sort]]
234
+ # check for sort field key
235
+ solr_parameters[:sort] = sort_field.sort unless sort_field.sort.blank?
236
+ else
237
+ # just pass the key through
238
+ solr_parameters[:sort] = user_params[:sort]
239
+ end
240
+ end
241
+
242
+ # Remove the group parameter if we've faceted on the group field (e.g. for the full results for a group)
243
+ def add_group_config_to_solr solr_parameters, user_parameters
244
+ if user_parameters[:f] and user_parameters[:f][grouped_key_for_results]
245
+ solr_parameters[:group] = false
246
+ end
247
+ end
248
+
249
+ def with_ex_local_param(ex, value)
250
+ if ex
251
+ "{!ex=#{ex}}#{value}"
252
+ else
253
+ value
254
+ end
255
+ end
256
+
257
+ private
258
+
259
+ ##
260
+ # Convert a facet/value pair into a solr fq parameter
261
+ def facet_value_to_fq_string(facet_field, value)
262
+ facet_config = blacklight_config.facet_fields[facet_field]
263
+
264
+ local_params = []
265
+ local_params << "tag=#{facet_config.tag}" if facet_config and facet_config.tag
266
+
267
+ prefix = ""
268
+ prefix = "{!#{local_params.join(" ")}}" unless local_params.empty?
269
+
270
+ fq = case
271
+ when (facet_config and facet_config.query)
272
+ facet_config.query[value][:fq]
273
+ when (facet_config and facet_config.date),
274
+ (value.is_a?(TrueClass) or value.is_a?(FalseClass) or value == 'true' or value == 'false'),
275
+ (value.is_a?(Integer) or (value.to_i.to_s == value if value.respond_to? :to_i)),
276
+ (value.is_a?(Float) or (value.to_f.to_s == value if value.respond_to? :to_f))
277
+ (value.is_a?(DateTime) or value.is_a?(Date) or value.is_a?(Time))
278
+ "#{prefix}#{facet_field}:#{value}"
279
+ when value.is_a?(Range)
280
+ "#{prefix}#{facet_field}:[#{value.first} TO #{value.last}]"
281
+ else
282
+ "{!raw f=#{facet_field}#{(" " + local_params.join(" ")) unless local_params.empty?}}#{value}"
283
+ end
284
+
285
+
286
+ end
287
+ end
288
+ end
@@ -108,10 +108,14 @@ module Blacklight
108
108
  def solr_document(primary_resource)
109
109
  add_routes do |options|
110
110
 
111
- args = {only: [:show, :update]}
111
+ args = {only: [:show]}
112
112
  args[:constraints] = options[:constraints] if options[:constraints]
113
113
 
114
- resources :solr_document, args.merge(path: primary_resource, controller: primary_resource)
114
+ resources :solr_document, args.merge(path: primary_resource, controller: primary_resource) do
115
+ member do
116
+ post "track"
117
+ end
118
+ end
115
119
 
116
120
  # :show and :update are for backwards-compatibility with catalog_url named routes
117
121
  resources primary_resource, args
@@ -39,7 +39,7 @@ class Blacklight::Solr::Request < HashWithIndifferentAccess
39
39
  end
40
40
 
41
41
  def to_hash
42
- reject {|key, value| ARRAY_KEYS.include?(key) && value.empty?}
42
+ reject {|key, value| ARRAY_KEYS.include?(key) && value.blank?}
43
43
  end
44
44
 
45
45
  end