bento_search 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.md +299 -0
- data/Rakefile +40 -0
- data/app/assets/images/bento_search/large_loader.gif +0 -0
- data/app/assets/javascripts/bento_search.js +3 -0
- data/app/assets/javascripts/bento_search/ajax_load.js +22 -0
- data/app/assets/stylesheets/bento_search/bento.css +4 -0
- data/app/controllers/bento_search/bento_search_controller.rb +7 -0
- data/app/controllers/bento_search/search_controller.rb +72 -0
- data/app/helpers/bento_search_helper.rb +138 -0
- data/app/item_decorators/bento_search/only_premade_openurl.rb +16 -0
- data/app/item_decorators/bento_search/openurl_add_other_link.rb +35 -0
- data/app/item_decorators/bento_search/openurl_main_link.rb +30 -0
- data/app/models/bento_search/author.rb +25 -0
- data/app/models/bento_search/link.rb +30 -0
- data/app/models/bento_search/multi_searcher.rb +109 -0
- data/app/models/bento_search/openurl_creator.rb +128 -0
- data/app/models/bento_search/registrar.rb +70 -0
- data/app/models/bento_search/result_item.rb +203 -0
- data/app/models/bento_search/results.rb +54 -0
- data/app/models/bento_search/results/pagination.rb +67 -0
- data/app/models/bento_search/search_engine.rb +219 -0
- data/app/models/bento_search/search_engine/capabilities.rb +65 -0
- data/app/search_engines/bento_search/#Untitled-1# +11 -0
- data/app/search_engines/bento_search/ebsco_host_engine.rb +356 -0
- data/app/search_engines/bento_search/eds_engine.rb +557 -0
- data/app/search_engines/bento_search/google_books_engine.rb +184 -0
- data/app/search_engines/bento_search/primo_engine.rb +231 -0
- data/app/search_engines/bento_search/scopus_engine.rb +295 -0
- data/app/search_engines/bento_search/summon_engine.rb +398 -0
- data/app/search_engines/bento_search/xerxes_engine.rb +168 -0
- data/app/views/bento_search/_link.html.erb +4 -0
- data/app/views/bento_search/_search_error.html.erb +22 -0
- data/app/views/bento_search/_std_item.html.erb +39 -0
- data/app/views/bento_search/search/search.html.erb +1 -0
- data/config/locales/en.yml +25 -0
- data/lib/bento_search.rb +29 -0
- data/lib/bento_search/engine.rb +5 -0
- data/lib/bento_search/routes.rb +45 -0
- data/lib/bento_search/version.rb +3 -0
- data/lib/generators/bento_search/pull_ebsco_dbs_generator.rb +24 -0
- data/lib/generators/bento_search/templates/ebsco_global_var.erb +6 -0
- data/lib/http_client_patch/include_client.rb +86 -0
- data/lib/tasks/bento_search_tasks.rake +4 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +56 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +6 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/test.log +3100 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/functional/bento_search/search_controller_test.rb +81 -0
- data/test/helper/bento_search_helper_test.rb +125 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/support/mock_engine.rb +23 -0
- data/test/support/test_with_cassette.rb +38 -0
- data/test/test_helper.rb +52 -0
- data/test/unit/#vcr_test.rb# +68 -0
- data/test/unit/ebsco_host_engine_test.rb +134 -0
- data/test/unit/eds_engine_test.rb +105 -0
- data/test/unit/google_books_engine_test.rb +93 -0
- data/test/unit/item_decorators_test.rb +66 -0
- data/test/unit/multi_searcher_test.rb +49 -0
- data/test/unit/openurl_creator_test.rb +111 -0
- data/test/unit/pagination_test.rb +59 -0
- data/test/unit/primo_engine_test.rb +37 -0
- data/test/unit/register_engine_test.rb +50 -0
- data/test/unit/result_item_display_test.rb +39 -0
- data/test/unit/result_item_test.rb +36 -0
- data/test/unit/scopus_engine_test.rb +130 -0
- data/test/unit/search_engine_base_test.rb +178 -0
- data/test/unit/search_engine_test.rb +95 -0
- data/test/unit/summon_engine_test.rb +161 -0
- data/test/unit/xerxes_engine_test.rb +70 -0
- data/test/vcr_cassettes/ebscohost/error_bad_db.yml +45 -0
- data/test/vcr_cassettes/ebscohost/error_bad_password.yml +45 -0
- data/test/vcr_cassettes/ebscohost/get_info.yml +3626 -0
- data/test/vcr_cassettes/ebscohost/live_search.yml +45 -0
- data/test/vcr_cassettes/ebscohost/live_search_smoke_test.yml +1311 -0
- data/test/vcr_cassettes/eds/basic_search_smoke_test.yml +1811 -0
- data/test/vcr_cassettes/eds/get_auth_token.yml +75 -0
- data/test/vcr_cassettes/eds/get_auth_token_failure.yml +39 -0
- data/test/vcr_cassettes/eds/get_with_auth.yml +243 -0
- data/test/vcr_cassettes/eds/get_with_auth_recovers_from_bad_auth.yml +368 -0
- data/test/vcr_cassettes/gbs/error_condition.yml +40 -0
- data/test/vcr_cassettes/gbs/pagination.yml +702 -0
- data/test/vcr_cassettes/gbs/search.yml +340 -0
- data/test/vcr_cassettes/primo/search_smoke_test.yml +1112 -0
- data/test/vcr_cassettes/scopus/bad_api_key_should_return_error_response.yml +60 -0
- data/test/vcr_cassettes/scopus/escaped_chars.yml +187 -0
- data/test/vcr_cassettes/scopus/fielded_search.yml +176 -0
- data/test/vcr_cassettes/scopus/simple_search.yml +227 -0
- data/test/vcr_cassettes/scopus/zero_results_search.yml +67 -0
- data/test/vcr_cassettes/summon/bad_auth.yml +54 -0
- data/test/vcr_cassettes/summon/proper_tags_for_snippets.yml +216 -0
- data/test/vcr_cassettes/summon/search.yml +242 -0
- data/test/vcr_cassettes/xerxes/live_search.yml +2580 -0
- data/test/view/std_item_test.rb +98 -0
- metadata +421 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
# Rails helper module provided by BentoSearch meant to be included
|
2
|
+
# in host app's helpers.
|
3
|
+
module BentoSearchHelper
|
4
|
+
|
5
|
+
# Renders bento search results on page, or an AJAX loader, etc, as appropriate.
|
6
|
+
# Pass in:
|
7
|
+
# * BentoSearch::SearchResults => will render
|
8
|
+
# * an instantiated BentoSearch::SearchEngine => Will do search and render
|
9
|
+
# * an id that a search engine was registered under with
|
10
|
+
# BentoSearch.register_engine => will do search and render.
|
11
|
+
#
|
12
|
+
# Second arg options hash includes options for bento_search helper,
|
13
|
+
# as well as other options pased on to BentoSearch::Engine.search(options)
|
14
|
+
#
|
15
|
+
# == Options
|
16
|
+
#
|
17
|
+
# load: :ajax_auto, :immediate. :ajax_auto will put a spinner up there,
|
18
|
+
# and load actual results via AJAX request. :immediate preloads
|
19
|
+
# results.
|
20
|
+
#
|
21
|
+
# == Examples
|
22
|
+
#
|
23
|
+
# bento_results( results_obj )
|
24
|
+
# bento_results( engine_obj, :query => "cancer")
|
25
|
+
# bento_results("google_books", :query => "cancer", :load => :ajax_auto)
|
26
|
+
#
|
27
|
+
def bento_search(search_arg, options = {})
|
28
|
+
results = search_arg if search_arg.kind_of? BentoSearch::Results
|
29
|
+
|
30
|
+
load_mode = options.delete(:load)
|
31
|
+
|
32
|
+
engine = nil
|
33
|
+
unless results
|
34
|
+
# need to load an engine and do a search, or ajax, etc.
|
35
|
+
engine = (if search_arg.kind_of? BentoSearch::SearchEngine
|
36
|
+
search_arg
|
37
|
+
else
|
38
|
+
BentoSearch.get_engine(search_arg.to_s)
|
39
|
+
end)
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
if (!results && load_mode == :ajax_auto)
|
44
|
+
raise ArgumentError.new("`:load => :ajax` requires a registered engine with an id") unless engine.configuration.id
|
45
|
+
content_tag(:div, :class => "bento_search_ajax_wait",
|
46
|
+
:"data-bento-ajax-url" => to_bento_search_url( {:engine_id => engine.configuration.id}.merge(options) )) do
|
47
|
+
image_tag("bento_search/large_loader.gif", :alt => I18n.translate("bento_search.ajax_loading")) +
|
48
|
+
content_tag("noscript") do
|
49
|
+
"Can not load results without javascript"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
else
|
53
|
+
results = engine.search(options) unless results
|
54
|
+
|
55
|
+
if results.failed?
|
56
|
+
render :partial => "bento_search/search_error", :locals => {:results => results}
|
57
|
+
elsif results.length > 0
|
58
|
+
render :partial => "bento_search/std_item", :collection => results
|
59
|
+
else
|
60
|
+
content_tag(:div, :class=> "bento_search_no_results") do
|
61
|
+
I18n.translate("bento_search.no_results")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
##
|
69
|
+
# More methods used by bento standard views, namespaced with bento_, sorry
|
70
|
+
# no great way to take logic out of views into helper methods without
|
71
|
+
# namespacey hack.
|
72
|
+
#
|
73
|
+
# You can use these methods in your own custom views, you also should be
|
74
|
+
# able to over-ride them (including calling super) in local helpers to
|
75
|
+
# change behavior in standard views.
|
76
|
+
#
|
77
|
+
##
|
78
|
+
|
79
|
+
def bento_abstract_truncate(str)
|
80
|
+
# if it's html safe, we can't truncate it, we don't have an HTML-aware
|
81
|
+
# truncation routine right now, that avoids leaving tags open etc.
|
82
|
+
return str if str.html_safe?
|
83
|
+
|
84
|
+
truncate(str, :length => 280, :separator => " ")
|
85
|
+
end
|
86
|
+
|
87
|
+
# Prepare a title in an H4, with formats in parens in a <small> (for
|
88
|
+
# bootstrap), linked, etc.
|
89
|
+
def bento_item_title(item)
|
90
|
+
content_tag("h4", :class => "bento_item_title") do
|
91
|
+
safe_join([
|
92
|
+
link_to_unless( item.link.blank?, item.complete_title, item.link ),
|
93
|
+
if item.format.present? || item.format_str.present?
|
94
|
+
content_tag("small", :class => "bento_item_about") do
|
95
|
+
" (" +
|
96
|
+
if item.format_str
|
97
|
+
item.format_str
|
98
|
+
else
|
99
|
+
t(item.format, :scope => [:bento_search, :format], :default => item.format.to_s.titleize)
|
100
|
+
end + ")"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
], '')
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# first 3 authors, each in a <span>, using item.author_display, seperated by
|
108
|
+
# semi-colons.
|
109
|
+
def bento_item_authors(item)
|
110
|
+
parts = []
|
111
|
+
|
112
|
+
first_three = item.authors.slice(0,3)
|
113
|
+
|
114
|
+
first_three.each_with_index do |author, index|
|
115
|
+
parts << content_tag("span", :class => "bento_item_author") do
|
116
|
+
item.author_display(author)
|
117
|
+
end
|
118
|
+
if (index + 1) < first_three.length
|
119
|
+
parts << "; "
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
return safe_join(parts, "")
|
124
|
+
end
|
125
|
+
|
126
|
+
# returns a hash of label => key suitable for passing to rails
|
127
|
+
# options_for_select. (Yes, it works backwards from how you'd expect).
|
128
|
+
# Label is looked up using I18n, at bento_search.sort_keys.*
|
129
|
+
def bento_sort_hash_for(engine)
|
130
|
+
Hash[
|
131
|
+
engine.sort_definitions.keys.collect do |key|
|
132
|
+
[I18n.translate(key, :scope => "bento_search.sort_keys"), key]
|
133
|
+
end
|
134
|
+
]
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'openurl'
|
2
|
+
|
3
|
+
# A Decorator that will make #to_openurl refuse to construct
|
4
|
+
# an openurl from individual elements, it'll use the #openurl_kev_co
|
5
|
+
# or nothing.
|
6
|
+
module BentoSearch::OnlyPremadeOpenurl
|
7
|
+
|
8
|
+
def to_openurl
|
9
|
+
if self.openurl_kev_co
|
10
|
+
return OpenURL::ContextObject.new_from_kev( self.openurl_kev_co )
|
11
|
+
else
|
12
|
+
return nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Example of an Item Decorator that ADDs an 'other link' with an openurl.
|
2
|
+
#
|
3
|
+
# This example uses crazy metaprogramming to dynamically create
|
4
|
+
# a module configured with your base url etc. You don't need to use
|
5
|
+
# crazy method like that; just define your own local decorator doing
|
6
|
+
# exactly what you need, it's meant to be simple.
|
7
|
+
#
|
8
|
+
# config.item_decorators = [ BentoSearch::OpenurlAddOtherLink[:base_url => "http://resolve.somewhere.edu/foo", :extra_query => "&foo=bar"] ]
|
9
|
+
#
|
10
|
+
module BentoSearch::OpenurlAddOtherLink
|
11
|
+
def self.[](options)
|
12
|
+
base_url = options[:base_url]
|
13
|
+
extra_query = options[:extra_query] || ""
|
14
|
+
link_name = options[:link_name] || "Find It"
|
15
|
+
# overwrite: if true, overwrite previously existing other_links, if
|
16
|
+
# false add on to previously existing.
|
17
|
+
overwrite = options.has_key?(:overwrite) ? options[:overwrite] : false
|
18
|
+
|
19
|
+
Module.new do
|
20
|
+
|
21
|
+
define_method :other_links do
|
22
|
+
start = overwrite ? [] : super()
|
23
|
+
if (ou = to_openurl)
|
24
|
+
start + [BentoSearch::Link.new(:url => "#{base_url}?#{ou.kev}#{extra_query}", :label => link_name)]
|
25
|
+
else
|
26
|
+
start
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Example of an Item Decorator that replaces the main 'link'
|
2
|
+
# with an openurl.
|
3
|
+
#
|
4
|
+
# This example uses crazy metaprogramming to dynamically create
|
5
|
+
# a module configured with your base url etc. You don't need to use
|
6
|
+
# crazy method like that; just define your own local decorator doing
|
7
|
+
# exactly what you need, it's meant to be simple.
|
8
|
+
#
|
9
|
+
# config.item_decorators = [ BentoSearch::OpenurlMainLink[:base_url => "http://resolve.somewhere.edu/foo", :extra_query => "&foo=bar"] ]
|
10
|
+
#
|
11
|
+
module BentoSearch::OpenurlMainLink
|
12
|
+
def self.[](options)
|
13
|
+
base_url = options[:base_url]
|
14
|
+
extra_query = options[:extra_query] || ""
|
15
|
+
Module.new do
|
16
|
+
|
17
|
+
define_method :link do
|
18
|
+
if (ou = to_openurl)
|
19
|
+
"#{base_url}?#{ou.kev}#{extra_query}"
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module BentoSearch
|
2
|
+
class Author
|
3
|
+
def initialize(args ={})
|
4
|
+
args.each_pair do |key, value|
|
5
|
+
send("#{key}=", value)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# Can be first name or initial, whatever source provides
|
10
|
+
attr_accessor :first
|
11
|
+
# last name/surname/family name as provided by source
|
12
|
+
attr_accessor :last
|
13
|
+
# middle name or initial, as and if provided by source
|
14
|
+
attr_accessor :middle
|
15
|
+
|
16
|
+
# if source doens't provide seperate first/last,
|
17
|
+
# source may only be able to provide one big string, author_display
|
18
|
+
attr_accessor :display
|
19
|
+
|
20
|
+
def empty?
|
21
|
+
first.blank? && last.blank? && middle.blank? && display.blank?
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module BentoSearch
|
2
|
+
# Represents an 'additional link' held in BentoSearch::ResultItem#other_links
|
3
|
+
#
|
4
|
+
# label, url, other metadata about the link.
|
5
|
+
class Link
|
6
|
+
# url is normally a string, but can also be a Hash passed
|
7
|
+
# to url_for in the local app.
|
8
|
+
attr_accessor :label, :url
|
9
|
+
|
10
|
+
# Used both for HTML links and possibly later Atom.
|
11
|
+
# Must be a string, EITHER a complete URL (representing
|
12
|
+
# vocab term), OR a legal short name from
|
13
|
+
# http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#linkTypes
|
14
|
+
attr_accessor :rel
|
15
|
+
|
16
|
+
# Array of strings, used for CSS classes on this link, possibly
|
17
|
+
# for custom styles/images etc. May be used in non-html link
|
18
|
+
# contexts too.
|
19
|
+
attr_accessor :style_classes
|
20
|
+
|
21
|
+
def initialize(hash = {})
|
22
|
+
self.style_classes = []
|
23
|
+
|
24
|
+
hash.each_pair do |key, value|
|
25
|
+
send("#{key}=", value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
|
3
|
+
# Based on Celluloid, concurrently runs multiple searches in
|
4
|
+
# seperate threads. You must include 'celluloid' gem dependency
|
5
|
+
# into your local app to use this class.
|
6
|
+
#
|
7
|
+
# I am not an expert at use of Celluloid, it's possible there's a better
|
8
|
+
# way to do this all, but seems to work.
|
9
|
+
#
|
10
|
+
# ## Usage
|
11
|
+
#
|
12
|
+
# initialize with id's of registered engines:
|
13
|
+
# searcher = BentoBox::MultiSearcher.new(:gbs, :scopus)
|
14
|
+
#
|
15
|
+
# start the concurrent searches, params same as engine.search
|
16
|
+
# searcher.start( query_params )
|
17
|
+
#
|
18
|
+
# retrieve results, blocking until each is completed:
|
19
|
+
# searcher.results
|
20
|
+
#
|
21
|
+
# returns a Hash keyed by engine id, values BentoSearch::Results objects.
|
22
|
+
#
|
23
|
+
# Can only call #results once per #start, after that it'll return empty hash.
|
24
|
+
# (should we make it actually raise instead?). .
|
25
|
+
#
|
26
|
+
# important to call results at some point after calling start, in order
|
27
|
+
# to make sure Celluloid::Actors are properly terminated to avoid
|
28
|
+
# resource leakage.
|
29
|
+
#
|
30
|
+
# TODO: have a method that returns Futures instead of only supplying the blocking
|
31
|
+
# results method? Several tricks, including making sure to properly terminate actors.
|
32
|
+
class BentoSearch::MultiSearcher
|
33
|
+
|
34
|
+
def initialize(*engine_ids)
|
35
|
+
@engines = []
|
36
|
+
@actors = []
|
37
|
+
engine_ids.each do |id|
|
38
|
+
add_engine( BentoSearch.get_engine id )
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Adds an instantiated engine directly, rather than by id from global
|
43
|
+
# registry.
|
44
|
+
def add_engine(engine)
|
45
|
+
@engines << engine
|
46
|
+
end
|
47
|
+
|
48
|
+
# Starts all searches, returns self so you can chain method calls if you like.
|
49
|
+
def start(*search_args)
|
50
|
+
@engines.each do |engine|
|
51
|
+
a = Actor.new(engine)
|
52
|
+
@actors << a
|
53
|
+
a.start! *search_args
|
54
|
+
end
|
55
|
+
return self
|
56
|
+
end
|
57
|
+
|
58
|
+
# Call after #start. Blocks until each included engine is finished
|
59
|
+
# then returns a Hash keyed by engine registered id, value is a
|
60
|
+
# BentoSearch::Results object.
|
61
|
+
#
|
62
|
+
# Can only call _once_ per invocation of #start, after that it'll return
|
63
|
+
# an empty hash.
|
64
|
+
def results
|
65
|
+
results = {}
|
66
|
+
|
67
|
+
# we use #delete_if to get an iterator that deletes
|
68
|
+
# each item after iteration.
|
69
|
+
@actors.delete_if do |actor|
|
70
|
+
result_key = (actor.engine.configuration.id || actor.engine.class.name)
|
71
|
+
results[result_key] = actor.results
|
72
|
+
actor.terminate
|
73
|
+
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
return results
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
class Actor
|
82
|
+
include Celluloid
|
83
|
+
|
84
|
+
attr_accessor :engine
|
85
|
+
|
86
|
+
def initialize(a_engine)
|
87
|
+
self.engine = a_engine
|
88
|
+
end
|
89
|
+
|
90
|
+
# call as start! with bang, to invoke async.
|
91
|
+
def start(*search_args)
|
92
|
+
begin
|
93
|
+
@results = self.engine.search(*search_args)
|
94
|
+
rescue Exception => e
|
95
|
+
warn e
|
96
|
+
# Make a fake results with caught exception.
|
97
|
+
@results = BentoSearch::Results.new
|
98
|
+
@results.error ||= {}
|
99
|
+
@results.error["exception"] = e
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def results
|
104
|
+
@results
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'openurl'
|
2
|
+
|
3
|
+
module BentoSearch
|
4
|
+
|
5
|
+
# Helper class used to take a ResultItem, and construct
|
6
|
+
# a ruby OpenURL::ContextObject out of it. That represents
|
7
|
+
# a NISO Z39.88 OpenURL context object, useful for using
|
8
|
+
# with linking software that expects such. http://en.wikipedia.org/wiki/OpenURL
|
9
|
+
#
|
10
|
+
# co = OpenurlCreator.new( result_item ).to_open_url
|
11
|
+
# # => ruby OpenURL::ContextObject object.
|
12
|
+
#
|
13
|
+
# co.kev
|
14
|
+
# # => context object serialized to KEV format (URL query string)
|
15
|
+
#
|
16
|
+
# In some cases nil can be returned, if no reasonable OpenURL can
|
17
|
+
# be created from the ResultItem.
|
18
|
+
class OpenurlCreator
|
19
|
+
attr_accessor :result_item
|
20
|
+
|
21
|
+
def initialize(ri)
|
22
|
+
self.result_item = ri
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_openurl
|
26
|
+
# If we have a pre-constructed KEV, just use it.
|
27
|
+
if result_item.openurl_kev_co
|
28
|
+
return OpenURL::ContextObject.new_from_kev( result_item.openurl_kev_co )
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
context_object = OpenURL::ContextObject.new
|
33
|
+
|
34
|
+
r = context_object.referent
|
35
|
+
|
36
|
+
r.set_format( self.format )
|
37
|
+
|
38
|
+
if result_item.doi
|
39
|
+
r.add_identifier("info:doi:#{result_item.doi}")
|
40
|
+
end
|
41
|
+
|
42
|
+
r.set_metadata("genre", self.genre)
|
43
|
+
|
44
|
+
if result_item.authors.length > 0
|
45
|
+
r.set_metadata("aufirst", result_item.authors.first.first)
|
46
|
+
r.set_metadata("aulast", result_item.authors.first.last)
|
47
|
+
r.set_metadata("au", result_item.author_display(result_item.authors.first))
|
48
|
+
end
|
49
|
+
|
50
|
+
r.set_metadata("date", result_item.year.to_s)
|
51
|
+
r.set_metadata("volume", result_item.volume.to_s)
|
52
|
+
r.set_metadata("issue", result_item.issue.to_s)
|
53
|
+
r.set_metadata("spage", result_item.start_page.to_s)
|
54
|
+
r.set_metadata("epage", result_item.end_page.to_s)
|
55
|
+
r.set_metadata("jtitle", result_item.journal_title)
|
56
|
+
r.set_metadata("issn", result_item.issn)
|
57
|
+
r.set_metadata("isbn", result_item.isbn)
|
58
|
+
r.set_metadata("pub", result_item.publisher)
|
59
|
+
|
60
|
+
case result_item.format
|
61
|
+
when "Book"
|
62
|
+
r.set_metadata("btitle", result_item.complete_title)
|
63
|
+
when "Article", :conference_paper
|
64
|
+
r.set_metadata("atitle", result_item.complete_title)
|
65
|
+
else
|
66
|
+
r.set_metadata("title", result_item.complete_title)
|
67
|
+
end
|
68
|
+
|
69
|
+
return context_object
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
# rft.genre value. Yeah, the legal ones differ depending on openurl
|
74
|
+
# 'format', but we've given up trying to do things strictly legal,
|
75
|
+
# OpenURL is a bear, we do things as generally used and good enough.
|
76
|
+
#
|
77
|
+
# can be nil.
|
78
|
+
def genre
|
79
|
+
case result_item.format
|
80
|
+
when "Book"
|
81
|
+
"book"
|
82
|
+
when :book_item
|
83
|
+
"bookitem"
|
84
|
+
when :conference_paper
|
85
|
+
"proceeding"
|
86
|
+
when :conference_proceedings
|
87
|
+
"conference"
|
88
|
+
when :report
|
89
|
+
"report"
|
90
|
+
when :serial
|
91
|
+
"journal"
|
92
|
+
when "Article"
|
93
|
+
"article"
|
94
|
+
else
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
# We need to map from our formats to which OpenURL 'format'
|
101
|
+
# we're going to create.
|
102
|
+
#
|
103
|
+
# We only pick from a limited set of standard scholarly citation formats,
|
104
|
+
# that's all any actual widespread software recognizes.
|
105
|
+
#
|
106
|
+
# Returns the last component of a valid format from:
|
107
|
+
# http://alcme.oclc.org/openurl/servlet/OAIHandler?verb=ListRecords&metadataPrefix=oai_dc&set=Core:Metadata+Formats
|
108
|
+
#
|
109
|
+
# Eg, "book", "journal", "dissertation".
|
110
|
+
#
|
111
|
+
# In fact, we only pick from one of those three -- by default, if we
|
112
|
+
# can't figure out exactly what it is or can't map it to a specific
|
113
|
+
# format, we'll return 'journal', never nil. 'journal' serves, in practice,
|
114
|
+
# with much actual software, as a neutral default.
|
115
|
+
def format
|
116
|
+
case result_item.format
|
117
|
+
when "Book"
|
118
|
+
"book"
|
119
|
+
when :dissertation
|
120
|
+
"dissertation"
|
121
|
+
else
|
122
|
+
"journal"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|