blacklight 5.0.3 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
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