bento_search 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +131 -74
- data/app/assets/javascripts/bento_search/ajax_load.js +12 -4
- data/app/assets/stylesheets/bento_search/suggested_styles.css +4 -4
- data/app/helpers/bento_search_helper.rb +114 -27
- data/app/item_decorators/bento_search/decorator_base.rb +53 -0
- data/app/item_decorators/bento_search/ebscohost/conditional_openurl_main_link.rb +36 -0
- data/app/item_decorators/bento_search/no_links.rb +3 -2
- data/app/item_decorators/bento_search/only_premade_openurl.rb +4 -0
- data/app/item_decorators/bento_search/openurl_add_other_link.rb +4 -0
- data/app/item_decorators/bento_search/openurl_main_link.rb +4 -0
- data/app/item_decorators/bento_search/standard_decorator.rb +122 -0
- data/app/models/bento_search/multi_searcher.rb +13 -6
- data/app/models/bento_search/openurl_creator.rb +25 -5
- data/app/models/bento_search/result_item.rb +25 -83
- data/app/models/bento_search/results/pagination.rb +8 -2
- data/app/models/bento_search/search_engine.rb +29 -23
- data/app/search_engines/bento_search/ebsco_host_engine.rb +161 -25
- data/app/search_engines/bento_search/eds_engine.rb +1 -44
- data/app/search_engines/bento_search/google_books_engine.rb +61 -14
- data/app/search_engines/bento_search/google_site_search_engine.rb +3 -1
- data/app/search_engines/bento_search/mock_engine.rb +4 -0
- data/app/search_engines/bento_search/primo_engine.rb +2 -3
- data/app/search_engines/bento_search/scopus_engine.rb +1 -0
- data/app/search_engines/bento_search/summon_engine.rb +5 -1
- data/app/search_engines/bento_search/worldcat_sru_dc_engine.rb +36 -8
- data/app/views/bento_search/_item_title.html.erb +29 -0
- data/app/views/bento_search/_no_results.html.erb +3 -0
- data/app/views/bento_search/_search_error.html.erb +19 -15
- data/app/views/bento_search/_std_item.html.erb +55 -30
- data/app/views/bento_search/search/search.html.erb +7 -0
- data/config/locales/en.yml +22 -0
- data/lib/bento_search/util.rb +63 -1
- data/lib/bento_search/version.rb +1 -1
- data/test/decorator/decorator_base_test.rb +72 -0
- data/test/decorator/standard_decorator_test.rb +55 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/log/development.log +12 -0
- data/test/dummy/log/test.log +119757 -0
- data/test/functional/bento_search/search_controller_test.rb +28 -0
- data/test/helper/bento_search_helper_test.rb +71 -0
- data/test/helper/bento_truncate_helper_test.rb +71 -0
- data/test/unit/ebsco_host_engine_test.rb +110 -3
- data/test/unit/google_books_engine_test.rb +22 -14
- data/test/unit/google_site_search_test.rb +11 -4
- data/test/unit/item_decorators_test.rb +6 -65
- data/test/unit/openurl_creator_test.rb +87 -8
- data/test/unit/result_item_test.rb +1 -11
- data/test/unit/search_engine_base_test.rb +25 -2
- data/test/unit/search_engine_test.rb +16 -0
- data/test/unit/summon_engine_test.rb +3 -0
- data/test/vcr_cassettes/ebscohost/another_dissertation.yml +148 -0
- data/test/vcr_cassettes/ebscohost/dissertation_example.yml +218 -0
- data/test/vcr_cassettes/ebscohost/fulltext_info.yml +1306 -0
- data/test/vcr_cassettes/ebscohost/live_book_example.yml +130 -0
- data/test/vcr_cassettes/ebscohost/live_dissertation.yml +148 -0
- data/test/vcr_cassettes/ebscohost/live_pathological_book_item_example.yml +215 -0
- data/test/vcr_cassettes/google_site/gets_format_string.yml +232 -0
- data/test/vcr_cassettes/max_out_pagination.yml +155 -0
- data/test/vcr_cassettes/worldcat_sru_dc/max_out_pagination.yml +167 -0
- data/test/view/std_item_test.rb +25 -8
- metadata +45 -12
- data/test/unit/result_item_display_test.rb +0 -39
- data/test/unit/worldcat_sru_dc_engine_test.rb +0 -120
data/README.md
CHANGED
@@ -7,16 +7,46 @@ be some breaking api changes before 1.0, but probably not too many, it's
|
|
7
7
|
looking pretty good).
|
8
8
|
|
9
9
|
bento_search provides an abstraction/normalization layer for querying and
|
10
|
-
displaying results
|
10
|
+
displaying results from external search engines, in Ruby on Rails. Requires
|
11
11
|
Rails3 and tested only under ruby 1.9.3.
|
12
12
|
|
13
|
-
|
14
|
-
cases too. Initially, engine adapters are planned to be provided for:
|
15
|
-
Google Books, Scopus, SerialSolutions Summon, Ex Libris Primo,
|
16
|
-
EBSCO Discovery Service, EBSCO traditional 'EIT' api, Google Site Search. Most
|
17
|
-
of these search engines require a vendor license to use.
|
13
|
+
### Goals: To help you
|
18
14
|
|
19
|
-
*
|
15
|
+
* **Get up and running as quickly as possible** with searching and displaying
|
16
|
+
results from a third-party service. Solutions to idiosyncracies and
|
17
|
+
undocumented workarounds are encoded in a shared codebase, which abstracts
|
18
|
+
everything to a good, simple code API giving you building blocks to focus
|
19
|
+
on your needs, not the search service's problems.
|
20
|
+
* Let you switch out one search service for another in an already built
|
21
|
+
application with as little code rewriting as possible. **Avoid vendor lock-in**.
|
22
|
+
* Give you the harness to **write adapters for new search services**, without
|
23
|
+
having to rewrite common general functionality, just focus on the interface
|
24
|
+
with the new API you want to support.
|
25
|
+
|
26
|
+
bento_search is focused on use cases for academic libraries, which is mainly
|
27
|
+
evidenced by the search engine adapters currently included, and by the
|
28
|
+
generalized domain models including fields that matter in our domain (issn,
|
29
|
+
vol/issue/page, etc), and some targetted functionality (OpenURL generation).
|
30
|
+
But it ought to be useful for more general basic use
|
31
|
+
cases too (we include a google site search adapter for instance).
|
32
|
+
|
33
|
+
Adapters currently included in bento_search
|
34
|
+
|
35
|
+
* Google Books (requires free api key)
|
36
|
+
* Scopus (requires license)
|
37
|
+
* Serial Solution Summon (requires license)
|
38
|
+
* Ex Libris Primo (requires license)
|
39
|
+
* EBSCO Discovery Service (requires license)
|
40
|
+
* EBSCOHost 'traditional' API (requires license)
|
41
|
+
* WorldCat Search (requires OCLC membership to get api key)
|
42
|
+
* Google Site Search (requires sign-up for more than 100 searches/day)
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
### Scope of functionality
|
48
|
+
|
49
|
+
bento_search could be considered building blocks for a type of 'federated
|
20
50
|
search' functionality, but it does not and will never support merging results
|
21
51
|
from multiple engines into one result set. It is meant to support displaying the
|
22
52
|
first few results from multiple engines on one page, "bento box" style (as
|
@@ -24,18 +54,18 @@ named by Tito Sierra@NCSU), as well as more expanded single-search-on-a-page
|
|
24
54
|
uses.
|
25
55
|
|
26
56
|
* bento_search provides abstract functionality for pagination, sorting,
|
27
|
-
and single-field-specified queries. Faceting, limiting, and 'advanced'
|
28
|
-
multi-field searches are not yet supported, but
|
29
|
-
|
57
|
+
and single-field-specified queries. Faceting, generalized limiting, and 'advanced'
|
58
|
+
multi-field searches are not yet supported, but possibly will be built
|
59
|
+
out in the future.
|
60
|
+
|
61
|
+
Not all search engine adapters support all features. Some engines offer
|
62
|
+
engine-specific features, such as limiting. Search engine adapters can
|
30
63
|
declare search fields and sort options with 'semantics', so you can for
|
31
64
|
instance search or sort by 'title' across search engines without regard
|
32
65
|
to internal engine-specific field names.
|
33
66
|
|
34
67
|
bento_search is designed to allow code to be written agnostic of the search
|
35
|
-
provider, so you can switch out the search provider
|
36
|
-
code in your app that needs to be rewritten. As well as letting you get
|
37
|
-
started quick without reinventing the wheel and figuring out poorly
|
38
|
-
documented vendor API's yourself.
|
68
|
+
provider, so you can switch out the search provider.
|
39
69
|
|
40
70
|
See code-level api documentation for more details, especially at
|
41
71
|
BentoSearch::SearchEngine. http://rubydoc.info/gems/bento_search/frames/
|
@@ -53,9 +83,11 @@ are a few standard keys (see BentoSearch::SearchEngine), and others that
|
|
53
83
|
may be engine-specific. Some engine-specific keys (such as api auth keys)
|
54
84
|
may be required for certain engines.
|
55
85
|
|
86
|
+
~~~~ruby
|
56
87
|
engine = BentoSearch::GoogleBooksEngine.new(:api_key => "my_gbs_api_key")
|
57
88
|
results = engine.search("a query")
|
58
|
-
|
89
|
+
~~~~
|
90
|
+
|
59
91
|
`results` are a BentoSearch::Results object, which acts like an array of
|
60
92
|
BentoSearch::Item objects, along with some meta-information about the
|
61
93
|
search itself (pagination keys, etc). BentoSearch::Results and Item fields
|
@@ -72,16 +104,20 @@ required for certain functionality (like out-of-the-box AJAX loading).
|
|
72
104
|
|
73
105
|
In an initializer in your app, like say `./config/initializers/bento_search.rb`:
|
74
106
|
|
107
|
+
~~~~ruby
|
75
108
|
BentoSearch.register_engine("gbs") do |conf|
|
76
109
|
conf.engine = "BentoSearch::GoogleBooksEngine"
|
77
110
|
conf.api_key = "my_google_api_key"
|
78
111
|
# any other configuration
|
79
112
|
end
|
113
|
+
~~~~
|
80
114
|
|
81
115
|
Then you can refer to it, for instance in a controller, by the id you registered:
|
82
116
|
|
117
|
+
~~~~ruby
|
83
118
|
@results = BentoSearch.get_engine("gbs").search("my query")
|
84
|
-
|
119
|
+
~~~~
|
120
|
+
|
85
121
|
### Display results
|
86
122
|
|
87
123
|
You can of course write your own code to display a BentoSearch::Results object
|
@@ -89,47 +125,70 @@ however you like. But BentoSearch comes with a helper method for displaying
|
|
89
125
|
a list of BentoSearch::Results in a standard way, using the bento_search
|
90
126
|
helper method.
|
91
127
|
|
92
|
-
|
128
|
+
~~~~ruby
|
129
|
+
<%= bento_search @results %>
|
130
|
+
~~~~
|
131
|
+
|
132
|
+
See also the [Customizing Results Display wiki page](https://github.com/jrochkind/bento_search/wiki/Customizing-Results-Display).
|
93
133
|
|
94
134
|
### Fielded searching.
|
95
135
|
|
96
136
|
You can search by an internal engine-specific field name:
|
97
137
|
|
138
|
+
~~~~ruby
|
98
139
|
google_books_engine.search("smith", :search_field => "inauthor")
|
99
|
-
|
140
|
+
~~~~
|
141
|
+
|
100
142
|
Or, if the engine provides it, you can search by normalized semantic search
|
101
143
|
field type names:
|
102
144
|
|
103
|
-
|
104
|
-
|
105
|
-
|
145
|
+
~~~~ruby
|
146
|
+
google_books_engine.search("smith", :semantic_search_field => :title)
|
147
|
+
~~~~
|
148
|
+
|
106
149
|
You can find out what fields a particular engine supports.
|
107
150
|
|
151
|
+
~~~~ruby
|
108
152
|
google_books_engine.search_keys # => internal keys
|
109
153
|
google_books_engine.semantic_search_keys
|
154
|
+
~~~~
|
155
|
+
|
156
|
+
A helper method for generating an html select of search field options is
|
157
|
+
available in `bento_field_hash_for`, check it out.
|
110
158
|
|
111
159
|
You can also provide all arguments in a single hash when it's convenient
|
112
160
|
to do so:
|
113
161
|
|
162
|
+
~~~~ruby
|
114
163
|
google_books_engine.search(:query => "smith", :search_field => "inauthor")
|
164
|
+
~~~~
|
165
|
+
|
166
|
+
Search fields that are not recognized (semantic or internal) will normally
|
167
|
+
be ignored, but set `:unrecognized_search_field => :raise` in configuration
|
168
|
+
or search arg to get an ArgumentError instead.
|
115
169
|
|
116
170
|
### Sorting
|
117
171
|
|
118
172
|
An engine advertises what sort types it supports:
|
119
173
|
|
174
|
+
~~~~ruby
|
120
175
|
google_books_engine.sort_keys
|
121
|
-
|
176
|
+
~~~~
|
177
|
+
|
122
178
|
An array of sort identifiers, where possible
|
123
|
-
chosen from a standard list of semantics. (See list in config/i18n/en.yml
|
124
|
-
bento_search.sort_keys).
|
179
|
+
chosen from a standard list of semantics. (See list in `./config/i18n/en.yml`,
|
180
|
+
`bento_search.sort_keys`).
|
125
181
|
|
182
|
+
~~~~ruby
|
126
183
|
google_books_engine.search("my query", :sort => "date_desc")
|
127
|
-
|
128
|
-
For help creating your UI, you can use built-in helper method:
|
184
|
+
~~~~
|
129
185
|
|
130
|
-
|
131
|
-
|
132
|
-
|
186
|
+
For help creating your UI, you can use built-in helper method, perhaps with Rails helper
|
187
|
+
options_for_select:
|
188
|
+
|
189
|
+
~~~~ruby
|
190
|
+
<%= options_for_select( bento_sort_hash_for(engine), params[:sort] ) %>
|
191
|
+
~~~~
|
133
192
|
|
134
193
|
|
135
194
|
### Pagination
|
@@ -138,13 +197,17 @@ You can tell the search engine how many items you want per-page, and
|
|
138
197
|
use _either_ `:start` (0-based item offset) or `:page` (1-based page
|
139
198
|
offset) keys to paginate into the results.
|
140
199
|
|
200
|
+
~~~~ruby
|
141
201
|
results = google_books_engine.search("my query", :per_page => 20, :start => 40)
|
142
202
|
results = google_books_engine.search("my query", :per_page => 20, :page => 2) # means same as above
|
143
|
-
|
203
|
+
~~~~
|
204
|
+
|
144
205
|
An engine instance advertises it's maximum per-page values.
|
145
206
|
|
207
|
+
~~~~ruby
|
146
208
|
google_books_engine.max_per_page
|
147
|
-
|
209
|
+
~~~~
|
210
|
+
|
148
211
|
bento_search fixes the default per_page at 10.
|
149
212
|
|
150
213
|
For help creating your UI, you can ask a BentoSearch::Results for
|
@@ -153,8 +216,10 @@ object which should be suitable for passing to [kaminari](https://github.com/ama
|
|
153
216
|
`paginate`, or else have convenient methods for roll your own pagination UI.
|
154
217
|
Kaminari's paginate method:
|
155
218
|
|
219
|
+
~~~~ruby
|
156
220
|
<%= paginate results.pagination %>
|
157
|
-
|
221
|
+
~~~~
|
222
|
+
|
158
223
|
### Concurrent searching
|
159
224
|
|
160
225
|
If you're going to search 2 or more search engines at once, you'll want to execute
|
@@ -172,8 +237,8 @@ to help you do this easily. Say, in a controller:
|
|
172
237
|
# constructor takes id's registered with BentoSearch.register_engine
|
173
238
|
searcher = BentoSearch::MultiSearcher.new(:gbs, :scopus, :summon)
|
174
239
|
|
175
|
-
# Call '
|
176
|
-
searcher.
|
240
|
+
# Call 'search' with any parameters you would give to an_engine.search
|
241
|
+
searcher.search("my query", :semantic_search_field => :author, :sort => "title")
|
177
242
|
|
178
243
|
# At this point, all searches are executing asynchronously in seperate threads.
|
179
244
|
# To get the results, blocking until all complete:
|
@@ -189,8 +254,10 @@ in the main thread (like search a local store of some kind outside of
|
|
189
254
|
bento_search)
|
190
255
|
|
191
256
|
You will need to add the 'celluloid' gem to your app to use this feature,
|
192
|
-
BentoSearch doesn't automatically include the celluloid dependency
|
193
|
-
|
257
|
+
BentoSearch doesn't automatically include the celluloid dependency. Note
|
258
|
+
that Celluloid uses multi-threading in such a way that you might need
|
259
|
+
to turn Rails config.cache_classes=true even in development.
|
260
|
+
|
194
261
|
|
195
262
|
For more info, see BentoSearch::MultiSearcher.
|
196
263
|
|
@@ -199,38 +266,16 @@ For more info, see BentoSearch::MultiSearcher.
|
|
199
266
|
BentoSearch provides some basic support for initially displaying a placeholder
|
200
267
|
progress spinner, and having Javascript call back to get the actual results.
|
201
268
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
to get JS for ajax loading. (or require 'bento_search' to get all bento_search JS)
|
207
|
-
* **Note** that this is not a panacea for a very slow search engine -- if the
|
208
|
-
search results take 20 seconds to come in, when the AJAX call back happens,
|
209
|
-
your Rails process _will_ be blocked from serving any other requests for that 20
|
210
|
-
seconds. In fact, that makes this feature of very limited applicability in general,
|
211
|
-
think carefully about what this will do for you.
|
212
|
-
* **Beware** that there are some authorization considerations if your search
|
213
|
-
engine is not publically configurable, see BentoSearch::SearchController
|
214
|
-
for more details.
|
215
|
-
|
216
|
-
You have have registered a configured engine globally, and given it the special
|
217
|
-
`:allow_routable_results` key.
|
218
|
-
|
219
|
-
BentoSearch.register_engine("gbs") do |conf|
|
220
|
-
conf.api_key = "x"
|
221
|
-
conf.allow_routable_results = true
|
222
|
-
end
|
223
|
-
|
224
|
-
Now you can use the `bento_search` helper method with the registered id
|
225
|
-
and query, instead of with results as before, and with an option for
|
226
|
-
ajax auto-load.
|
227
|
-
|
228
|
-
<%= bento_search("gbs", :query => "my query",
|
229
|
-
:semantic_search_field => :title,
|
230
|
-
:load => :ajax_auto) %>
|
231
|
-
|
269
|
+
It's not a panacea for pathologically slow search results, and can be tricky
|
270
|
+
for results that need access controls. But it can be useful
|
271
|
+
in some situations, both for automatic on-page-load ajax loading, and triggered
|
272
|
+
ajax loading.
|
232
273
|
|
274
|
+
See the [wiki page](https://github.com/jrochkind/bento_search/wiki/AJAX-results-loading)
|
275
|
+
for more info.
|
233
276
|
|
277
|
+
|
278
|
+
|
234
279
|
### Item Decorators, and Links
|
235
280
|
|
236
281
|
You can configure Decorators, in the form of plain old ruby modules, to be
|
@@ -244,15 +289,27 @@ link resolver.
|
|
244
289
|
BentoSearch::Items can have a main link associated with them (generally
|
245
290
|
hyperlinked from title), as well as a list of additional links. Most engines
|
246
291
|
do not provide additional links by default, custom local Decorators would
|
247
|
-
be used to add them.
|
292
|
+
be used to add them. See wiki for more info on decorators, and BentoSearch::Link
|
293
|
+
for fields.
|
294
|
+
|
295
|
+
## OpenURL and metadata
|
248
296
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
297
|
+
Academic library uses often need openurl links from scholarly citations. One of
|
298
|
+
the design goals of bento_search is to produce standardized normalized BentoSearch::ResultItem
|
299
|
+
models, with sufficient semantics for translation to other formats.
|
300
|
+
|
301
|
+
See ResultItem#to_openurl_kev (string URL query encoding of OpenURL), and
|
302
|
+
ResultItem#to_openurl (a [ruby OpenURL gem](https://github.com/openurl/openurl) object).
|
253
303
|
|
254
|
-
|
255
|
-
|
304
|
+
Quality may vary, depending on how well the particular engine adapter captures semantics,
|
305
|
+
especially the format/type of results (See bento_search's internal format/type vocabulary
|
306
|
+
documented at ResultItem#format). As well as how well the #to_openurl routine
|
307
|
+
handles all edge cases (OpenURL can be weird). As edge cases are discovered, they
|
308
|
+
can be solved.
|
309
|
+
|
310
|
+
See `./app/item_decorators/bento_search/openurl_add_other_link.rb` for an example
|
311
|
+
of using item decorators to add a link to your openurl resover to an item when
|
312
|
+
displayed.
|
256
313
|
|
257
314
|
## Planned Features
|
258
315
|
|
@@ -267,7 +324,6 @@ Probably:
|
|
267
324
|
also may be supported by some engines that do not support facetting).
|
268
325
|
* Support for multi-field, multi-entry-box 'advanced search' UI's, in
|
269
326
|
a normalized cross-engine way.
|
270
|
-
* More mindless support for displaying pagination UI with kaminari.
|
271
327
|
|
272
328
|
Other needs or suggestions?
|
273
329
|
|
@@ -304,3 +360,4 @@ welcome. See more info on writing a BentoSearch::SearchEngine in the inline
|
|
304
360
|
docs in that file.
|
305
361
|
|
306
362
|
|
363
|
+
|
@@ -4,7 +4,7 @@ var BentoSearch = BentoSearch || {}
|
|
4
4
|
// Will AJAX load bento search results inside that node.
|
5
5
|
// optional second arg success callback function.
|
6
6
|
BentoSearch.ajax_load = function(node, success_callback) {
|
7
|
-
div = $(node);
|
7
|
+
var div = $(node);
|
8
8
|
|
9
9
|
if (div.length == 0) {
|
10
10
|
//we've got nothing
|
@@ -22,11 +22,19 @@ BentoSearch.ajax_load = function(node, success_callback) {
|
|
22
22
|
// Now load the actual external content from html5 data-bento-ajax-url
|
23
23
|
$.ajax({
|
24
24
|
url: div.data("bentoAjaxUrl"),
|
25
|
-
success: function(response, status, xhr) {
|
25
|
+
success: function(response, status, xhr) {
|
26
|
+
var do_replace = true;
|
26
27
|
if (success_callback) {
|
27
|
-
|
28
|
+
// We need to make the response into a DOM so the callback
|
29
|
+
// can deal with it better. Wrapped in a div, so it makes
|
30
|
+
// jquery happy even if there isn't a single parent element.
|
31
|
+
response = $("<div>" + response + "</div>");
|
32
|
+
|
33
|
+
do_replace = success_callback.apply(div, [response]);
|
28
34
|
}
|
29
|
-
|
35
|
+
if (do_replace != false) {
|
36
|
+
div.replaceWith(response);
|
37
|
+
}
|
30
38
|
},
|
31
39
|
error: function(xhr, status, errorThrown) {
|
32
40
|
var msg = "Sorry but there was an error: ";
|
@@ -3,10 +3,6 @@
|
|
3
3
|
or just use this as documentation for suggestions to implement yourself. */
|
4
4
|
|
5
5
|
|
6
|
-
/* year in bold in citations */
|
7
|
-
.bento_item_row.published_in .year {
|
8
|
-
font-weight: bold;
|
9
|
-
}
|
10
6
|
|
11
7
|
/* highlighted element in title is already in a <b> tag, but
|
12
8
|
title is likely to be bold already. italisize it too. */
|
@@ -29,3 +25,7 @@
|
|
29
25
|
.bento_item_title a .bento_search_highlight {
|
30
26
|
font-style: inherit;
|
31
27
|
}
|
28
|
+
|
29
|
+
.bento_item_body .source_title {
|
30
|
+
font-style: italic;
|
31
|
+
}
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
|
1
5
|
# Rails helper module provided by BentoSearch meant to be included
|
2
6
|
# in host app's helpers.
|
3
7
|
module BentoSearchHelper
|
@@ -25,6 +29,7 @@ module BentoSearchHelper
|
|
25
29
|
# bento_results("google_books", :query => "cancer", :load => :ajax_auto)
|
26
30
|
#
|
27
31
|
def bento_search(search_arg, options = {})
|
32
|
+
|
28
33
|
results = search_arg if search_arg.kind_of? BentoSearch::Results
|
29
34
|
|
30
35
|
load_mode = options.delete(:load)
|
@@ -35,6 +40,7 @@ module BentoSearchHelper
|
|
35
40
|
engine = (if search_arg.kind_of? BentoSearch::SearchEngine
|
36
41
|
search_arg
|
37
42
|
else
|
43
|
+
raise ArgumentError.new("Need Results, engine, or registered engine_id as first argument to #bento_search") unless search_arg
|
38
44
|
BentoSearch.get_engine(search_arg.to_s)
|
39
45
|
end)
|
40
46
|
|
@@ -66,16 +72,32 @@ module BentoSearchHelper
|
|
66
72
|
results = engine.search(options) unless results
|
67
73
|
|
68
74
|
if results.failed?
|
69
|
-
|
70
|
-
|
71
|
-
|
75
|
+
partial = (results.display_configuration.error_partial if results.display_configuration) || "bento_search/search_error"
|
76
|
+
render :partial => partial, :locals => {:results => results}
|
77
|
+
elsif results.length > 0
|
78
|
+
partial = (results.display_configuration.item_partial if results.display_configuration) || "bento_search/std_item"
|
79
|
+
render :partial => partial, :collection => results, :as => :item, :locals => {:results => results}
|
72
80
|
else
|
73
81
|
content_tag(:div, :class=> "bento_search_no_results") do
|
74
|
-
|
82
|
+
partial = (results.display_configuration.no_results_partial if results.display_configuration) || "bento_search/no_results"
|
83
|
+
render :partial => partial, :locals => {:results => results}
|
75
84
|
end
|
76
85
|
end
|
77
86
|
end
|
78
87
|
end
|
88
|
+
|
89
|
+
# Wrap a ResultItem in a decorator! For now hard-coded to
|
90
|
+
# BentoSearch::StandardDecorator
|
91
|
+
def bento_decorate(result_item)
|
92
|
+
# What decorator class? If specified as string in #decorator,
|
93
|
+
# look it up as a class object, else default.
|
94
|
+
decorator_class = result_item.decorator.try {|name| BentoSearch::Util.constantize(name) } || BentoSearch::StandardDecorator
|
95
|
+
|
96
|
+
# in a helper method, 'self' is a view_context already I think?
|
97
|
+
decorated = decorator_class.new(result_item, self)
|
98
|
+
yield(decorated) if block_given?
|
99
|
+
return decorated
|
100
|
+
end
|
79
101
|
|
80
102
|
|
81
103
|
##
|
@@ -89,33 +111,53 @@ module BentoSearchHelper
|
|
89
111
|
#
|
90
112
|
##
|
91
113
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
114
|
+
# Like rails truncate helper, and taking the same options, but html_safe.
|
115
|
+
#
|
116
|
+
# If input string is NOT marked html_safe?, simply passes to rails truncate helper.
|
117
|
+
# If a string IS marked html_safe?, uses nokogiri to parse it, and truncate
|
118
|
+
# actual displayed text to max_length, while keeping html structure valid.
|
119
|
+
#
|
120
|
+
# Default omission marker is unicode elipsis
|
121
|
+
#
|
122
|
+
# :length option will also default to 280, what we think is a good
|
123
|
+
# length for abstract/snippet display
|
124
|
+
def bento_truncate(str, options = {})
|
125
|
+
options.reverse_merge!(:omission => "…", :length => 280, :separator => ' ')
|
126
|
+
|
127
|
+
# works for non-html of course, but for html a quick check
|
128
|
+
# to avoid expensive nokogiri parse if the whole string, even
|
129
|
+
# with tags, is still less than max length.
|
130
|
+
return str if str.length < options[:length]
|
96
131
|
|
97
|
-
|
132
|
+
if str.html_safe?
|
133
|
+
noko = Nokogiri::HTML::DocumentFragment.parse(str)
|
134
|
+
BentoSearch::Util.nokogiri_truncate(noko, options[:length], options[:omission], options[:separator]).inner_html.html_safe
|
135
|
+
else
|
136
|
+
return truncate(str, options)
|
137
|
+
end
|
98
138
|
end
|
139
|
+
|
140
|
+
|
99
141
|
|
100
|
-
#
|
101
|
-
#
|
142
|
+
# Deprecated, made more sense to put this in a partial. Just
|
143
|
+
# call partial directly:
|
144
|
+
# <%= render :partial => "bento_search/item_title", :object => item, :as => 'item' %>
|
102
145
|
def bento_item_title(item)
|
103
|
-
|
104
|
-
safe_join([
|
105
|
-
link_to_unless( item.link.blank?, item.complete_title, item.link ),
|
106
|
-
if item.format.present? || item.format_str.present?
|
107
|
-
content_tag("small", :class => "bento_item_about") do
|
108
|
-
" (" +
|
109
|
-
if item.format_str
|
110
|
-
item.format_str
|
111
|
-
else
|
112
|
-
t(item.format, :scope => [:bento_search, :format], :default => item.format.to_s.titleize)
|
113
|
-
end + ")"
|
114
|
-
end
|
115
|
-
end
|
116
|
-
], '')
|
117
|
-
end
|
146
|
+
render :partial => "bento_search/item_title", :object => item, :as => 'item'
|
118
147
|
end
|
148
|
+
deprecate :bento_item_title
|
149
|
+
|
150
|
+
# pass in 0-based rails current collection counter and a BentoSearch::Results,
|
151
|
+
# calculates a user-displayable result set index label.
|
152
|
+
#
|
153
|
+
# Only non-trivial thing is both inputs are allowed to be nil; if either
|
154
|
+
# is nil, nil is returned.
|
155
|
+
def bento_item_counter(counter, results)
|
156
|
+
return nil if counter.nil? || results.nil? || results.start.nil?
|
157
|
+
|
158
|
+
return counter + results.start + 1
|
159
|
+
end
|
160
|
+
|
119
161
|
|
120
162
|
# first 3 authors, each in a <span>, using item.author_display, seperated by
|
121
163
|
# semi-colons.
|
@@ -125,7 +167,7 @@ module BentoSearchHelper
|
|
125
167
|
first_three = item.authors.slice(0,3)
|
126
168
|
|
127
169
|
first_three.each_with_index do |author, index|
|
128
|
-
parts << content_tag("span", :class => "
|
170
|
+
parts << content_tag("span", :class => "authors") do
|
129
171
|
item.author_display(author)
|
130
172
|
end
|
131
173
|
if (index + 1) < first_three.length
|
@@ -133,6 +175,10 @@ module BentoSearchHelper
|
|
133
175
|
end
|
134
176
|
end
|
135
177
|
|
178
|
+
if item.authors.length > 3
|
179
|
+
parts << I18n.t("bento_search.authors_et_al")
|
180
|
+
end
|
181
|
+
|
136
182
|
return safe_join(parts, "")
|
137
183
|
end
|
138
184
|
|
@@ -150,5 +196,46 @@ module BentoSearchHelper
|
|
150
196
|
]
|
151
197
|
end
|
152
198
|
|
199
|
+
# Returns a hash of label => key suitable for passing to rails
|
200
|
+
# options_for_select. ONLY includes fields with :semantics set at
|
201
|
+
# present. Key will be _semantic_ key name.
|
202
|
+
# For engine-specific fields, you're on you're own, sorry!
|
203
|
+
#
|
204
|
+
# If first arg is an engine instance, will
|
205
|
+
# be search fields supported by that engine. If first arg is nil,
|
206
|
+
# will be any field in our i18n lists for search fields.
|
207
|
+
#
|
208
|
+
# Can pass in options :only or :except to customize list. Values
|
209
|
+
# in :only and :except will match on internal field names OR
|
210
|
+
# semantic field names (hopefully this convenience ambiguity
|
211
|
+
# won't cause problems)
|
212
|
+
#
|
213
|
+
# eg:
|
214
|
+
# <%= select_tag 'search_field', options_for_select(bento_field_hash_for(@engine), params[:search_field]) %>
|
215
|
+
def bento_field_hash_for(engine, options = {})
|
216
|
+
if engine.nil?
|
217
|
+
hash = I18n.t("bento_search.search_fields").invert
|
218
|
+
else
|
219
|
+
hash = Hash[ engine.search_field_definitions.collect do |k, defn|
|
220
|
+
if defn[:semantic] && (label = I18n.t(defn[:semantic], :scope => "bento_search.search_fields", :default => defn[:semantic].to_s.titlecase ))
|
221
|
+
[label, defn[:semantic].to_s]
|
222
|
+
end
|
223
|
+
end]
|
224
|
+
end
|
225
|
+
|
226
|
+
# :only/:except
|
227
|
+
if options[:only]
|
228
|
+
keys = [options[:only]].flatten.collect(&:to_s)
|
229
|
+
hash.delete_if {|key, value| ! keys.include?(value) }
|
230
|
+
end
|
231
|
+
|
232
|
+
if options[:except]
|
233
|
+
keys = [options[:except]].flatten.collect(&:to_s)
|
234
|
+
hash.delete_if {|key, value| keys.include?(value) }
|
235
|
+
end
|
236
|
+
|
237
|
+
return hash
|
238
|
+
end
|
239
|
+
|
153
240
|
|
154
241
|
end
|