blacklight 5.0.3 → 5.1.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -2
  3. data/Gemfile +9 -9
  4. data/Rakefile +0 -1
  5. data/VERSION +1 -1
  6. data/app/assets/stylesheets/blacklight/_facets.css.scss +2 -1
  7. data/app/controllers/bookmarks_controller.rb +0 -1
  8. data/app/helpers/blacklight/blacklight_helper_behavior.rb +3 -3
  9. data/app/helpers/blacklight/catalog_helper_behavior.rb +5 -1
  10. data/app/helpers/blacklight/configuration_helper_behavior.rb +60 -0
  11. data/app/helpers/blacklight/facets_helper_behavior.rb +3 -5
  12. data/app/helpers/blacklight/render_constraints_helper_behavior.rb +1 -1
  13. data/app/helpers/blacklight/url_helper_behavior.rb +24 -19
  14. data/app/views/catalog/_facet_layout.html.erb +1 -1
  15. data/app/views/catalog/_facet_limit.html.erb +2 -4
  16. data/app/views/catalog/_facet_pagination.html.erb +0 -1
  17. data/app/views/catalog/facet.html.erb +2 -13
  18. data/blacklight.gemspec +3 -3
  19. data/gemfiles/rails3.gemfile +19 -5
  20. data/gemfiles/rails4.gemfile +17 -5
  21. data/lib/blacklight/catalog.rb +55 -10
  22. data/lib/blacklight/configuration.rb +6 -1
  23. data/lib/blacklight/configuration/fields.rb +38 -4
  24. data/lib/blacklight/controller.rb +10 -1
  25. data/lib/blacklight/facet.rb +8 -0
  26. data/lib/blacklight/solr_helper.rb +53 -25
  27. data/lib/blacklight/solr_response/facets.rb +22 -4
  28. data/spec/controllers/catalog_controller_spec.rb +59 -2
  29. data/spec/features/alternate_controller_spec.rb +3 -3
  30. data/spec/features/record_view_spec.rb +1 -1
  31. data/spec/helpers/blacklight_helper_spec.rb +5 -5
  32. data/spec/helpers/catalog_helper_spec.rb +20 -7
  33. data/spec/helpers/configuration_helper_spec.rb +42 -0
  34. data/spec/helpers/facets_helper_spec.rb +5 -12
  35. data/spec/helpers/render_constraints_helper_spec.rb +4 -1
  36. data/spec/helpers/url_helper_spec.rb +37 -49
  37. data/spec/lib/blacklight/configuration_spec.rb +53 -1
  38. data/spec/lib/blacklight/solr_helper_spec.rb +37 -4
  39. data/spec/test_app_templates/Gemfile.extra +21 -0
  40. data/spec/test_app_templates/lib/generators/test_app_generator.rb +5 -0
  41. data/spec/test_app_templates/lib/tasks/blacklight_test_app.rake +14 -0
  42. data/spec/views/catalog/_constraints.html.erb_spec.rb +2 -2
  43. data/spec/views/catalog/_facet_layout.html.erb_spec.rb +3 -1
  44. data/spec/views/catalog/_facets.html.erb_spec.rb +42 -40
  45. data/spec/views/catalog/facet.html.erb_spec.rb +30 -0
  46. metadata +23 -33
  47. data/spec/views/catalog/opensearch.xml.builder_spec.rb +0 -10
@@ -1,10 +1,22 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
- file = File.expand_path("../../Gemfile", __FILE__)
3
+ # Please see blacklight.gemspec for dependency information.
4
+ gemspec :path=>"../"
4
5
 
5
- if File.exists?(file)
6
- puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`
7
- instance_eval File.read(file)
6
+ gem 'rails', '~> 4.0.0'
7
+
8
+ gem 'activerecord-jdbcsqlite3-adapter', :platform => :jruby
9
+
10
+ group :test do
11
+ gem 'simplecov', require: false
12
+ gem 'coveralls', require: false
13
+ gem 'devise'
14
+ gem 'devise-guests'
15
+ gem "bootstrap-sass"
16
+ gem 'turbolinks'
8
17
  end
9
18
 
10
- gem 'rails', '4.0.3'
19
+ f = File.expand_path(File.dirname(__FILE__) + '/../spec/test_app_templates/Gemfile.extra')
20
+ if File.exists?(f)
21
+ eval File.read(f), nil, f
22
+ end
@@ -27,11 +27,11 @@ module Blacklight::Catalog
27
27
  format.html { }
28
28
  format.rss { render :layout => false }
29
29
  format.atom { render :layout => false }
30
-
31
-
32
30
  format.json do
33
31
  render json: render_search_results_as_json
34
32
  end
33
+
34
+ additional_response_formats(format)
35
35
  end
36
36
  end
37
37
 
@@ -63,7 +63,13 @@ module Blacklight::Catalog
63
63
 
64
64
  # displays values and pagination links for a single facet field
65
65
  def facet
66
- @pagination = get_facet_pagination(params[:id], params)
66
+ @facet = blacklight_config.facet_fields[params[:id]]
67
+ @response = get_facet_field_response(@facet.field, params)
68
+ @display_facet = @response.facets.first
69
+
70
+ # @pagination was deprecated in Blacklight 5.1
71
+ @pagination = facet_paginator(@facet, @display_facet)
72
+
67
73
 
68
74
  respond_to do |format|
69
75
  # Draw the facet selector for users who have javascript disabled:
@@ -149,6 +155,25 @@ module Blacklight::Catalog
149
155
  # non-routable methods ->
150
156
  #
151
157
 
158
+ def additional_response_formats format
159
+ blacklight_config.index.respond_to.each do |key, config|
160
+ format.send key do
161
+ case config
162
+ when false
163
+ raise ActionController::RoutingError.new('Not Found')
164
+ when Hash
165
+ render config
166
+ when Proc
167
+ instance_exec &config
168
+ when Symbol, String
169
+ send config
170
+ else
171
+ # no-op, just render the page
172
+ end
173
+ end
174
+ end
175
+ end
176
+
152
177
  # override this method to change the JSON response from #index
153
178
  def render_search_results_as_json
154
179
  {response: {docs: @document_list, facets: search_facets_as_json, pages: pagination_info(@response)}}
@@ -156,6 +181,7 @@ module Blacklight::Catalog
156
181
 
157
182
  def search_facets_as_json
158
183
  facets_from_request.as_json.each do |f|
184
+ f.delete "options"
159
185
  f["label"] = facet_configuration_for_field(f["name"]).label
160
186
  f["items"] = f["items"].as_json.each do |i|
161
187
  i['label'] ||= i['value']
@@ -172,7 +198,7 @@ module Blacklight::Catalog
172
198
  # By default, any search action from a Blacklight::Catalog controller
173
199
  # should use the current controller when constructing the route.
174
200
  def search_action_url options = {}
175
- url_for(options.merge(:action => 'index', :only_path => true))
201
+ url_for(options.merge(:action => 'index'))
176
202
  end
177
203
 
178
204
  # extract the pagination info from the response object
@@ -225,12 +251,31 @@ module Blacklight::Catalog
225
251
  flash[:error].blank?
226
252
  end
227
253
 
228
- # when a request for /catalog/BAD_SOLR_ID is made, this method is executed...
229
- def invalid_solr_id_error
230
- flash[:notice] = I18n.t('blacklight.search.errors.invalid_solr_id')
231
- params.delete(:id)
232
- index
233
- render "index", :status => 404
254
+ # when a request for /catalog/BAD_SOLR_ID is made, this method is executed.
255
+ # Just returns a 404 response, but you can override locally in your own
256
+ # CatalogController to do something else -- older BL displayed a Catalog#inde
257
+ # page with a flash message and a 404 status.
258
+ def invalid_solr_id_error(exception)
259
+ error_info = {
260
+ "status" => "404",
261
+ "error" => "#{exception.class}: #{exception.message}"
262
+ }
263
+
264
+ respond_to do |format|
265
+ format.xml { render :xml => error_info, :status => 404 }
266
+ format.json { render :json => error_info, :status => 404 }
267
+
268
+ # default to HTML response, even for other non-HTML formats we don't
269
+ # neccesarily know about, seems to be consistent with what Rails4 does
270
+ # by default with uncaught ActiveRecord::RecordNotFound in production
271
+ format.any do
272
+ # use standard, possibly locally overridden, 404.html file. Even for
273
+ # possibly non-html formats, this is consistent with what Rails does
274
+ # on raising an ActiveRecord::RecordNotFound. Rails.root IS needed
275
+ # for it to work under testing, without worrying about CWD.
276
+ render :file => "#{Rails.root}/public/404.html", :status => 404, :layout => false, :content_type => 'text/html'
277
+ end
278
+ end
234
279
  end
235
280
 
236
281
  def start_new_search_session?
@@ -20,7 +20,12 @@ module Blacklight
20
20
  :document_solr_request_handler => nil,
21
21
  :default_document_solr_params => {},
22
22
  :show => ViewConfig::Show.new(:partials => [:show_header, :show]),
23
- :index => ViewConfig::Index.new(:partials => [:index_header, :thumbnail, :index], :title_field => unique_key, :display_type_field => 'format', :group => false),
23
+ :index => ViewConfig::Index.new(:partials => [:index_header, :thumbnail, :index],
24
+ :title_field => unique_key,
25
+ :display_type_field => 'format',
26
+ :group => false,
27
+ :respond_to => OpenStructWithHashAccess.new()
28
+ ),
24
29
  :view => NestedOpenStructWithHashAccess.new(ViewConfig, 'list'),
25
30
  :spell_max => 5,
26
31
  :max_per_page => 100,
@@ -74,12 +74,30 @@ module Blacklight
74
74
  args[0] = args[0].to_s
75
75
  field_config_from_key_and_hash(config_key, *args)
76
76
  when Array
77
- field_config_from_array(config_key, *args)
77
+ field_config_from_array(config_key, *args, &block)
78
+ return # we've iterated over the array above.
78
79
  else
79
80
  field_config_from_field_or_hash(config_key, *args)
80
81
  end
81
82
 
82
- return if field_config.is_a? Array
83
+ # look up any dynamic fields
84
+ if field_config.field.to_s =~ /\*/ and luke_fields
85
+ salient_fields = luke_fields.select do |k,v|
86
+ k =~ Regexp.new("^" + field_config.field.to_s.gsub('*', '.+') + "$")
87
+ end
88
+
89
+ salient_fields.each do |field, luke_config|
90
+ config = field_config.dup
91
+ config.field = field
92
+ if self[config_key.pluralize][ config.field ]
93
+ self[config_key.pluralize][ config.field ] = config.merge(self[config_key.pluralize][ config.field ])
94
+ else
95
+ add_solr_field(config_key, config, &block)
96
+ end
97
+ end
98
+
99
+ return
100
+ end
83
101
 
84
102
  if block_given?
85
103
  yield field_config
@@ -94,6 +112,22 @@ module Blacklight
94
112
  end
95
113
 
96
114
  protected
115
+ def luke_fields
116
+ if @table[:luke_fields] === false
117
+ return nil
118
+ end
119
+
120
+ @table[:luke_fields] ||= begin
121
+ if has_key? :blacklight_solr
122
+ blacklight_solr.get('admin/luke', params: { fl: '*', 'json.nl' => 'map' })['fields']
123
+ end
124
+ rescue
125
+ false
126
+ end
127
+
128
+ @table[:luke_fields] || nil
129
+ end
130
+
97
131
  # Add a solr field by a solr field name and hash
98
132
  def field_config_from_key_and_hash config_key, solr_field, field_or_hash = {}
99
133
  field_config = field_config_from_field_or_hash(config_key, field_or_hash)
@@ -103,9 +137,9 @@ module Blacklight
103
137
  end
104
138
 
105
139
  # Add multiple solr fields using a hash or Field instance
106
- def field_config_from_array config_key, array_of_fields_or_hashes
140
+ def field_config_from_array config_key, array_of_fields_or_hashes, &block
107
141
  array_of_fields_or_hashes.map do |field_or_hash|
108
- add_solr_field(config_key, field_or_hash)
142
+ add_solr_field(config_key, field_or_hash, &block)
109
143
  end
110
144
  end
111
145
 
@@ -23,7 +23,7 @@ module Blacklight::Controller
23
23
  # extra head content
24
24
  helper_method :has_user_authentication_provider?
25
25
  helper_method :blacklight_config
26
- helper_method :search_action_url
26
+ helper_method :search_action_url, :search_action_path
27
27
 
28
28
 
29
29
  # This callback runs when a user first logs in
@@ -50,6 +50,15 @@ module Blacklight::Controller
50
50
  catalog_index_url *args
51
51
  end
52
52
 
53
+ def search_action_path *args
54
+
55
+ if args.first.is_a? Hash
56
+ args.first[:only_path] = true
57
+ end
58
+
59
+ search_action_url *args
60
+ end
61
+
53
62
  # Returns a list of Searches from the ids in the user's history.
54
63
  def searches_from_history
55
64
  session[:history].blank? ? Search.none : Search.where(:id => session[:history]).order("updated_at desc")
@@ -3,6 +3,14 @@
3
3
  #
4
4
  module Blacklight
5
5
  module Facet
6
+
7
+ def facet_paginator field_config, display_facet
8
+ Blacklight::Solr::FacetPaginator.new(display_facet.items,
9
+ sort: display_facet.sort,
10
+ offset: display_facet.offset,
11
+ limit: facet_limit_for(field_config.field))
12
+ end
13
+
6
14
  def facets_from_request(fields = facet_field_names)
7
15
  fields.map { |solr_field| facet_by_field_name(solr_field) }.compact
8
16
  end
@@ -322,6 +322,13 @@ module Blacklight::SolrHelper
322
322
  if facet.sort
323
323
  solr_parameters[:"f.#{facet.field}.facet.sort"] = facet.sort
324
324
  end
325
+
326
+ if facet.solr_params
327
+ facet.solr_params.each do |k, v|
328
+ solr_parameters[:"f.#{facet.field}.#{k}"] = v
329
+ end
330
+ end
331
+
325
332
  end
326
333
 
327
334
  # Support facet paging and 'more'
@@ -341,11 +348,26 @@ module Blacklight::SolrHelper
341
348
 
342
349
  def add_solr_fields_to_query solr_parameters, user_parameters
343
350
  return unless blacklight_config.add_field_configuration_to_solr_request
351
+
352
+ blacklight_config.show_fields.each do |field_name, field|
353
+ if field.solr_params
354
+ field.solr_params.each do |k, v|
355
+ solr_parameters[:"f.#{field.field}.#{k}"] = v
356
+ end
357
+ end
358
+ end
359
+
344
360
  blacklight_config.index_fields.each do |field_name, field|
345
361
  if field.highlight
346
362
  solr_parameters[:hl] = true
347
363
  solr_parameters.append_highlight_field field.field
348
364
  end
365
+
366
+ if field.solr_params
367
+ field.solr_params.each do |k, v|
368
+ solr_parameters[:"f.#{field.field}.#{k}"] = v
369
+ end
370
+ end
349
371
  end
350
372
  end
351
373
 
@@ -465,13 +487,14 @@ module Blacklight::SolrHelper
465
487
  # method facet_list_limit, otherwise 20.
466
488
  def solr_facet_params(facet_field, user_params=params || {}, extra_controller_params={})
467
489
  input = user_params.deep_merge(extra_controller_params)
490
+ facet_config = blacklight_config.facet_fields[facet_field]
468
491
 
469
492
  # First start with a standard solr search params calculations,
470
493
  # for any search context in our request params.
471
494
  solr_params = solr_search_params(user_params).merge(extra_controller_params)
472
495
 
473
496
  # Now override with our specific things for fetching facet values
474
- solr_params[:"facet.field"] = facet_field
497
+ solr_params[:"facet.field"] = with_ex_local_param((facet_config.ex if facet_config.respond_to?(:ex)), facet_field)
475
498
 
476
499
 
477
500
  limit =
@@ -493,26 +516,31 @@ module Blacklight::SolrHelper
493
516
  return solr_params
494
517
  end
495
518
 
519
+ ##
520
+ # Get the solr response when retrieving only a single facet field
521
+ def get_facet_field_response(facet_field, user_params = params || {}, extra_controller_params = {})
522
+ solr_params = solr_facet_params(facet_field, user_params, extra_controller_params)
523
+ # Make the solr call
524
+ find(blacklight_config.qt, solr_params)
525
+ end
526
+
496
527
  # a solr query method
497
528
  # used to paginate through a single facet field's values
498
529
  # /catalog/facet/language_facet
499
530
  def get_facet_pagination(facet_field, user_params=params || {}, extra_controller_params={})
500
-
501
- solr_params = solr_facet_params(facet_field, user_params, extra_controller_params)
502
-
503
531
  # Make the solr call
504
- response =find(blacklight_config.qt, solr_params)
532
+ response = get_facet_field_response(facet_field, user_params, extra_controller_params)
533
+
534
+ limit = response.params[:"f.#{facet_field}.facet.limit"].to_s.to_i - 1
505
535
 
506
- limit = solr_params[:"f.#{facet_field}.facet.limit"] -1
507
-
508
536
  # Actually create the paginator!
509
537
  # NOTE: The sniffing of the proper sort from the solr response is not
510
538
  # currently tested for, tricky to figure out how to test, since the
511
539
  # default setup we test against doesn't use this feature.
512
540
  return Blacklight::Solr::FacetPaginator.new(response.facets.first.items,
513
- :offset => solr_params[:"f.#{facet_field}.facet.offset"],
541
+ :offset => response.params[:"f.#{facet_field}.facet.offset"],
514
542
  :limit => limit,
515
- :sort => response["responseHeader"]["params"][:"f.#{facet_field}.facet.sort"] || response["responseHeader"]["params"]["facet.sort"]
543
+ :sort => response.params[:"f.#{facet_field}.facet.sort"] || response.params["facet.sort"]
516
544
  )
517
545
  end
518
546
 
@@ -590,23 +618,23 @@ module Blacklight::SolrHelper
590
618
  # a facet paginator with the right limit.
591
619
  def facet_limit_for(facet_field)
592
620
  facet = blacklight_config.facet_fields[facet_field]
593
- return nil if facet.blank?
594
-
595
- limit = facet.limit
596
-
597
- if ( limit == true && @response &&
598
- @response["responseHeader"] &&
599
- @response["responseHeader"]["params"])
600
- limit =
601
- @response["responseHeader"]["params"]["f.#{facet_field}.facet.limit"] ||
602
- @response["responseHeader"]["params"]["facet.limit"]
603
- limit = (limit.to_i() -1) if limit
604
- limit = nil if limit == -2 # -1-1==-2, unlimited.
605
- elsif limit == true
606
- limit = nil
607
- end
608
621
 
609
- return limit
622
+ return if facet.blank?
623
+
624
+ if facet.limit and @response
625
+ limit = @response.params["f.#{facet_field}.facet.limit"] ||
626
+ @response.params["facet.limit"]
627
+
628
+ if limit.blank? # we didn't get or a set a limit, so infer one.
629
+ facet.limit if facet.limit != true
630
+ elsif limit == -1 # limit -1 is solr-speak for unlimited
631
+ nil
632
+ else
633
+ limit.to_i - 1 # we added 1 to find out if we needed to paginate
634
+ end
635
+ elsif (facet.limit and facet.limit != true)
636
+ facet.limit
637
+ end
610
638
  end
611
639
 
612
640
  ##
@@ -29,8 +29,21 @@ module Blacklight::SolrResponse::Facets
29
29
  # represents a facet; which is a field and its values
30
30
  class FacetField
31
31
  attr_reader :name, :items
32
- def initialize name, items
32
+ def initialize name, items, options = {}
33
33
  @name, @items = name, items
34
+ @options = options
35
+ end
36
+
37
+ def limit
38
+ @options[:limit]
39
+ end
40
+
41
+ def sort
42
+ @options[:sort] || 'index'
43
+ end
44
+
45
+ def offset
46
+ @options[:offset] || 0
34
47
  end
35
48
  end
36
49
 
@@ -40,15 +53,20 @@ module Blacklight::SolrResponse::Facets
40
53
  # end
41
54
  # "caches" the result in the @facets instance var
42
55
  def facets
43
- @facets ||= (
56
+ @facets ||= begin
44
57
  facet_fields.map do |(facet_field_name,values_and_hits)|
45
58
  items = []
59
+ options = {}
46
60
  values_and_hits.each_slice(2) do |k,v|
47
61
  items << FacetItem.new(:value => k, :hits => v)
48
62
  end
49
- FacetField.new(facet_field_name, items)
63
+
64
+ options[:sort] = params[:"f.#{facet_field_name}.facet.sort"] || params['facet.sort']
65
+ options[:offset] = params[:"f.#{facet_field_name}.facet.offset"].to_i
66
+
67
+ FacetField.new(facet_field_name, items, options)
50
68
  end
51
- )
69
+ end
52
70
  end
53
71
 
54
72
  # pass in a facet field name and get back a Facet instance
@@ -155,6 +155,49 @@ describe CatalogController do
155
155
  end
156
156
  end
157
157
 
158
+ describe "with additional formats from configuration" do
159
+ let(:blacklight_config) { Blacklight::Configuration.new }
160
+
161
+ before :each do
162
+ @controller.stub blacklight_config: blacklight_config
163
+ @controller.stub get_search_results: [double, double]
164
+ end
165
+
166
+ it "should not render when the config is false" do
167
+ blacklight_config.index.respond_to.yaml = false
168
+ expect { get :index, format: 'yaml' }.to raise_error ActionController::RoutingError
169
+ end
170
+
171
+ it "should render the default when the config is true" do
172
+ # TODO: this should really stub a template and see if it gets rendered,
173
+ # but how to do that is non-obvious..
174
+ blacklight_config.index.respond_to.yaml = true
175
+ expect { get :index, format: 'yaml' }.to raise_error ActionView::MissingTemplate
176
+ end
177
+
178
+ it "should pass a hash to the render call" do
179
+ blacklight_config.index.respond_to.yaml = { nothing: true, layout: false }
180
+ get :index, format: 'yaml'
181
+ expect(response.body).to be_blank
182
+ end
183
+
184
+ it "should evaluate a proc" do
185
+ blacklight_config.index.respond_to.yaml = lambda { render text: "" }
186
+ get :index, format: 'yaml'
187
+ expect(response.body).to be_empty
188
+ end
189
+
190
+ it "with a symbol, it should call a controller method" do
191
+ subject.should_receive(:render_some_yaml) do
192
+ subject.render nothing: true, layout: false
193
+ end
194
+
195
+ blacklight_config.index.respond_to.yaml = :render_some_yaml
196
+ get :index, format: 'yaml'
197
+ expect(response.body).to be_blank
198
+ end
199
+ end
200
+
158
201
  end # describe index action
159
202
 
160
203
  describe "update action" do
@@ -421,10 +464,21 @@ describe CatalogController do
421
464
  controller.stub(:find => @mock_response,
422
465
  :get_single_doc_via_search => @mock_document)
423
466
  get :show, :id=>"987654321"
424
- expect(request.flash[:notice]).to eq "Sorry, you have requested a record that doesn't exist."
425
- expect(response).to render_template('index')
426
467
  expect(response.status).to eq 404
468
+ expect(response.content_type).to eq Mime::HTML
427
469
  end
470
+ it "should return status 404 for a record that doesn't exist even for non-html format" do
471
+ @mock_response = double()
472
+ @mock_response.stub(:docs => [])
473
+ @mock_document = double()
474
+ controller.stub(:find => @mock_response,
475
+ :get_single_doc_via_search => @mock_document)
476
+
477
+ get :show, :id=>"987654321", :format => "xml"
478
+ expect(response.status).to eq 404
479
+ expect(response.content_type).to eq Mime::XML
480
+ end
481
+
428
482
  it "should redirect the user to the root url for a bad search" do
429
483
  req = {}
430
484
  res = {}
@@ -483,6 +537,9 @@ describe CatalogController do
483
537
  it "should be successful" do
484
538
  get :facet, id: 'format'
485
539
  expect(response).to be_successful
540
+ expect(assigns[:response]).to be_kind_of Blacklight::SolrResponse
541
+ expect(assigns[:facet]).to be_kind_of Blacklight::Configuration::FacetField
542
+ expect(assigns[:display_facet]).to be_kind_of Blacklight::SolrResponse::Facets::FacetField
486
543
  expect(assigns[:pagination]).to be_kind_of Blacklight::Solr::FacetPaginator
487
544
  end
488
545
  end