oai 0.2.1 → 0.3.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.
- data/README.md +28 -23
- data/Rakefile +14 -40
- data/examples/providers/dublin_core.rb +63 -63
- data/lib/oai/client.rb +131 -97
- data/lib/oai/client/list_identifiers.rb +1 -0
- data/lib/oai/client/list_records.rb +6 -5
- data/lib/oai/client/list_sets.rb +6 -5
- data/lib/oai/client/record.rb +6 -7
- data/lib/oai/client/response.rb +7 -4
- data/lib/oai/client/resumable.rb +42 -0
- data/lib/oai/harvester/shell.rb +40 -41
- data/lib/oai/provider.rb +85 -67
- data/lib/oai/provider/metadata_format/oai_dc.rb +5 -6
- data/lib/oai/provider/model/activerecord_caching_wrapper.rb +23 -25
- data/lib/oai/provider/model/activerecord_wrapper.rb +99 -51
- data/lib/oai/provider/response.rb +33 -31
- data/lib/oai/provider/response/get_record.rb +7 -7
- data/lib/oai/provider/response/list_records.rb +5 -4
- data/lib/oai/provider/response/record_response.rb +14 -14
- data/test/activerecord_provider/config/connection.rb +8 -4
- data/test/activerecord_provider/database/{ar_migration.rb → 0001_oaipmh_tables.rb} +17 -12
- data/test/activerecord_provider/helpers/providers.rb +2 -3
- data/test/activerecord_provider/helpers/set_provider.rb +10 -22
- data/test/activerecord_provider/helpers/transactional_test_case.rb +34 -0
- data/test/activerecord_provider/models/dc_field.rb +4 -4
- data/test/activerecord_provider/models/dc_set.rb +3 -2
- data/test/activerecord_provider/models/exclusive_set_dc_field.rb +11 -0
- data/test/activerecord_provider/tc_ar_provider.rb +67 -28
- data/test/activerecord_provider/tc_ar_sets_provider.rb +104 -18
- data/test/activerecord_provider/tc_caching_paging_provider.rb +6 -10
- data/test/activerecord_provider/tc_simple_paging_provider.rb +7 -11
- data/test/activerecord_provider/test_helper.rb +10 -0
- data/test/client/helpers/provider.rb +44 -47
- data/test/client/helpers/test_wrapper.rb +4 -16
- data/test/client/tc_http_client.rb +90 -2
- data/test/client/tc_list_identifiers.rb +22 -3
- data/test/client/tc_list_records.rb +17 -4
- data/test/client/tc_list_sets.rb +17 -2
- data/test/provider/models.rb +32 -30
- data/test/provider/tc_exceptions.rb +30 -20
- data/test/provider/tc_functional_tokens.rb +11 -6
- data/test/provider/tc_provider.rb +58 -24
- data/test/provider/tc_resumption_tokens.rb +6 -6
- data/test/provider/tc_simple_provider.rb +51 -26
- data/test/provider/test_helper.rb +7 -0
- metadata +67 -128
- data/test/activerecord_provider/config/database.yml +0 -6
- data/test/activerecord_provider/database/oaipmhtest +0 -0
data/lib/oai/client/response.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
module OAI
|
2
2
|
|
3
|
-
# An OAI::Response contains entries and a resumption token. If a resumption token is present,
|
3
|
+
# An OAI::Response contains entries and a resumption token. If a resumption token is present,
|
4
4
|
# then you must use it to fetch the rest of the entries for your query. For example:
|
5
|
+
#
|
6
|
+
# ```ruby
|
5
7
|
# # List all records in a given set
|
6
8
|
# client = OAI::Client.new 'http://my-oai-provider.example.com/oai'
|
7
9
|
# response = client.list_records :set => 'my_set_name'
|
@@ -13,14 +15,15 @@ module OAI
|
|
13
15
|
# # Note: You do not need to pass the options hash again, just the verb and the resumption token
|
14
16
|
# response = client.list_records :resumption_token => token if token
|
15
17
|
# end
|
16
|
-
|
18
|
+
# ```
|
17
19
|
class Response
|
18
20
|
include OAI::XPath
|
19
|
-
attr_reader :doc, :resumption_token
|
21
|
+
attr_reader :doc, :resumption_token, :resumption_block
|
20
22
|
|
21
|
-
def initialize(doc)
|
23
|
+
def initialize(doc, &resumption_block)
|
22
24
|
@doc = doc
|
23
25
|
@resumption_token = xpath(doc, './/resumptionToken')
|
26
|
+
@resumption_block = resumption_block
|
24
27
|
|
25
28
|
# throw an exception if there was an error
|
26
29
|
error = xpath_first(doc, './/error')
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module OAI
|
2
|
+
module Resumable
|
3
|
+
|
4
|
+
class ResumptionWrapper
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(response)
|
8
|
+
@response = response
|
9
|
+
@resumption_block = response.resumption_block
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
yield_from_response &block
|
14
|
+
while resumable?
|
15
|
+
@response = @resumption_block.call @response
|
16
|
+
yield_from_response &block
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def yield_from_response(&block)
|
23
|
+
@response.each do |obj|
|
24
|
+
block.call(obj)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def resumable?
|
29
|
+
@response.resumption_token and not @response.resumption_token.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def full
|
35
|
+
if @resumption_block.nil?
|
36
|
+
raise NotImplementedError.new("Resumption block not provided")
|
37
|
+
end
|
38
|
+
ResumptionWrapper.new(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
data/lib/oai/harvester/shell.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
module OAI
|
2
2
|
module Harvester
|
3
|
-
# = OAI::Harvester::Shell
|
4
|
-
#
|
5
3
|
# A OAI-PMH client shell allowing OAI Harvesting to be configured in
|
6
|
-
# an interactive manner. Typing
|
4
|
+
# an interactive manner. Typing `oai` on the command line starts the
|
7
5
|
# shell. The first time the shell is run it will prompt for the following
|
8
6
|
# configuration details:
|
9
|
-
#
|
10
|
-
#
|
7
|
+
#
|
8
|
+
# 1. A storage directory for all harvested records. Harvests will be
|
9
|
+
# stored under this directory in a directory structure based on the
|
11
10
|
# date of the harvest.
|
12
11
|
# 2. A log file directory.
|
13
12
|
# 3. Email address(es) for sending daily harvesting activity reports.
|
@@ -21,15 +20,15 @@ module OAI
|
|
21
20
|
#
|
22
21
|
# The shell automatically pulls down the list of sets in the repository, and
|
23
22
|
# the supported metadata prefixes. Making it very simple to setup harvests.
|
24
|
-
#
|
23
|
+
#
|
25
24
|
class Shell
|
26
25
|
include Readline
|
27
|
-
|
26
|
+
|
28
27
|
def initialize(config)
|
29
28
|
@conf = config
|
30
29
|
@conf.sites ||= {} # Initialize sites hash there isn't one
|
31
30
|
end
|
32
|
-
|
31
|
+
|
33
32
|
def start
|
34
33
|
unless @conf.storage
|
35
34
|
banner "Entering first-time setup"
|
@@ -54,9 +53,9 @@ module OAI
|
|
54
53
|
end
|
55
54
|
end
|
56
55
|
end
|
57
|
-
|
56
|
+
|
58
57
|
private
|
59
|
-
|
58
|
+
|
60
59
|
def help
|
61
60
|
banner "Commands:"
|
62
61
|
puts "\tharvest site [date] - Harvest site(s) manually"
|
@@ -68,7 +67,7 @@ module OAI
|
|
68
67
|
puts "\tedit [site] - Change settings for a provider site"
|
69
68
|
puts "\texit - Exit the harvester shell.\n\n"
|
70
69
|
end
|
71
|
-
|
70
|
+
|
72
71
|
def harvest(options)
|
73
72
|
site, *date = options.split(/\s/)
|
74
73
|
if @conf.sites.keys.include?(site)
|
@@ -80,7 +79,7 @@ module OAI
|
|
80
79
|
puts "Couldn't parse the date supplied"
|
81
80
|
return
|
82
81
|
end
|
83
|
-
else
|
82
|
+
else
|
84
83
|
date = nil
|
85
84
|
end
|
86
85
|
harvester = Harvest.new(@conf, @conf.storage, date)
|
@@ -91,7 +90,7 @@ module OAI
|
|
91
90
|
end
|
92
91
|
puts # blank line
|
93
92
|
end
|
94
|
-
|
93
|
+
|
95
94
|
def list(args = nil)
|
96
95
|
if 'config' == args
|
97
96
|
banner "Current Configuration"
|
@@ -104,7 +103,7 @@ module OAI
|
|
104
103
|
end
|
105
104
|
puts # blank line
|
106
105
|
end
|
107
|
-
|
106
|
+
|
108
107
|
def info(args)
|
109
108
|
banner "Provider Site Information"
|
110
109
|
sites = args.split(/[,\s|\s|,]/)
|
@@ -115,21 +114,21 @@ module OAI
|
|
115
114
|
rescue
|
116
115
|
puts args + " doesn't appear to be configured, use list to see configured repositories."
|
117
116
|
end
|
118
|
-
|
117
|
+
|
119
118
|
def new
|
120
119
|
banner "Define New Harvesting Site"
|
121
120
|
name, site = form
|
122
121
|
@conf.sites[name] = site
|
123
122
|
@conf.save
|
124
123
|
end
|
125
|
-
|
124
|
+
|
126
125
|
def edit(name)
|
127
126
|
banner "Edit Harvesting Site"
|
128
127
|
name, site = form(name)
|
129
128
|
@conf.sites[name] = site
|
130
129
|
@conf.save
|
131
130
|
end
|
132
|
-
|
131
|
+
|
133
132
|
def remove(site)
|
134
133
|
if 'Y' == readline("Remove #{site}? (Y/N): ").upcase
|
135
134
|
@conf.sites.delete(site)
|
@@ -149,14 +148,14 @@ module OAI
|
|
149
148
|
end
|
150
149
|
end
|
151
150
|
site = @conf.sites[name] || {}
|
152
|
-
|
151
|
+
|
153
152
|
# URL
|
154
153
|
url = prompt("url", site['url'])
|
155
154
|
while(not (site['url'] = verify(url)))
|
156
155
|
puts "Trouble contacting provider, bad url?"
|
157
156
|
url = prompt("url", site['url'])
|
158
157
|
end
|
159
|
-
|
158
|
+
|
160
159
|
# Metadata formats
|
161
160
|
formats = metadata(site['url'])
|
162
161
|
report "Repository supports [#{formats.join(', ')}] metadata formats."
|
@@ -187,15 +186,15 @@ module OAI
|
|
187
186
|
puts "Must be daily, weekly, or monthly"
|
188
187
|
period = expand_period(prompt("period", "daily"))
|
189
188
|
end
|
190
|
-
|
189
|
+
|
191
190
|
site['period'] = period
|
192
|
-
|
191
|
+
|
193
192
|
return [name, site]
|
194
|
-
rescue
|
193
|
+
rescue
|
195
194
|
puts "Problem adding/updating provider, aborting. (#{$!})"
|
196
195
|
end
|
197
196
|
end
|
198
|
-
|
197
|
+
|
199
198
|
def config
|
200
199
|
begin
|
201
200
|
directory = prompt("storage directory", @conf.storage)
|
@@ -205,7 +204,7 @@ module OAI
|
|
205
204
|
|
206
205
|
email = @conf.email.join(', ') rescue nil
|
207
206
|
@conf.email = parse_emails(prompt("email", email))
|
208
|
-
|
207
|
+
|
209
208
|
@conf.mail_server = prompt("mail server", @conf.mail_server)
|
210
209
|
|
211
210
|
logfile = prompt("log file(s) directory", @conf.logfile)
|
@@ -215,22 +214,22 @@ module OAI
|
|
215
214
|
@conf.storage = directory
|
216
215
|
@conf.logfile = logfile
|
217
216
|
@conf.save
|
218
|
-
rescue
|
217
|
+
rescue
|
219
218
|
nil
|
220
219
|
end
|
221
220
|
end
|
222
|
-
|
221
|
+
|
223
222
|
def display(key, value, split = 40)
|
224
223
|
(split - key.size).times { print " " } if key.size < split
|
225
224
|
puts "#{key}: #{value}"
|
226
225
|
end
|
227
|
-
|
226
|
+
|
228
227
|
def banner(str)
|
229
228
|
puts "\n#{str}"
|
230
229
|
str.size.times { print "-" }
|
231
230
|
puts "\n"
|
232
231
|
end
|
233
|
-
|
232
|
+
|
234
233
|
def report(str)
|
235
234
|
puts "\n#{str}\n"
|
236
235
|
end
|
@@ -240,7 +239,7 @@ module OAI
|
|
240
239
|
print "\t"
|
241
240
|
end
|
242
241
|
end
|
243
|
-
|
242
|
+
|
244
243
|
def prompt(text, default = nil, split = 20)
|
245
244
|
prompt_text = "#{text} [#{default}]: "
|
246
245
|
(split - prompt_text.size).times { print " " } if prompt_text.size < split
|
@@ -248,7 +247,7 @@ module OAI
|
|
248
247
|
raise RuntimeError.new("Exit loop") unless value
|
249
248
|
return value.empty? ? default : value
|
250
249
|
end
|
251
|
-
|
250
|
+
|
252
251
|
def verify(url)
|
253
252
|
begin
|
254
253
|
client = OAI::Client.new(url, :redirects => false)
|
@@ -264,7 +263,7 @@ module OAI
|
|
264
263
|
end
|
265
264
|
end
|
266
265
|
end
|
267
|
-
|
266
|
+
|
268
267
|
def metadata(url)
|
269
268
|
formats = []
|
270
269
|
client = OAI::Client.new url
|
@@ -274,7 +273,7 @@ module OAI
|
|
274
273
|
end
|
275
274
|
formats
|
276
275
|
end
|
277
|
-
|
276
|
+
|
278
277
|
def sets(url)
|
279
278
|
sets = []
|
280
279
|
client = OAI::Client.new url
|
@@ -292,35 +291,35 @@ module OAI
|
|
292
291
|
end
|
293
292
|
true
|
294
293
|
end
|
295
|
-
|
294
|
+
|
296
295
|
def expand_period(str)
|
297
296
|
return str if Config::PERIODS.include?(str)
|
298
297
|
Config::PERIODS.each { |p| return p if p =~ /^#{str}/}
|
299
298
|
nil
|
300
299
|
end
|
301
|
-
|
300
|
+
|
302
301
|
def parse_emails(emails)
|
303
302
|
return nil unless emails
|
304
303
|
addresses = emails.split(/[,\s|\s|,]/)
|
305
304
|
end
|
306
|
-
|
305
|
+
|
307
306
|
def list_config
|
308
307
|
display("storage directory", @conf.storage, 20)
|
309
308
|
display("email", @conf.email.join(', '), 20) if @conf.email
|
310
309
|
display("mail server", @conf.mail_server, 20) if @conf.mail_server
|
311
310
|
display("log location", @conf.logfile, 20) if @conf.logfile
|
312
311
|
end
|
313
|
-
|
312
|
+
|
314
313
|
def list_sites
|
315
314
|
banner "Sites"
|
316
315
|
@conf.sites.each_key { |site| print_site(site) }
|
317
316
|
end
|
318
|
-
|
317
|
+
|
319
318
|
def print_site(site)
|
320
319
|
puts site
|
321
320
|
@conf.sites[site].each { |k,v| display(k, v, 15)}
|
322
321
|
end
|
323
|
-
|
322
|
+
|
324
323
|
def setup_cron
|
325
324
|
banner "Scheduling Automatic Harvesting"
|
326
325
|
puts "To activate automatic harvesting you must add an entry to"
|
@@ -330,9 +329,9 @@ module OAI
|
|
330
329
|
puts "Windows users should use WinAt to schedule"
|
331
330
|
puts "#{$0} to run every night.\n\n\n"
|
332
331
|
end
|
333
|
-
|
332
|
+
|
334
333
|
end
|
335
334
|
|
336
335
|
end
|
337
|
-
end
|
338
|
-
|
336
|
+
end
|
337
|
+
|
data/lib/oai/provider.rb
CHANGED
@@ -11,21 +11,21 @@ end
|
|
11
11
|
|
12
12
|
%w{ response metadata_format resumption_token model partial_result
|
13
13
|
response/record_response response/identify response/get_record
|
14
|
-
response/list_identifiers response/list_records
|
14
|
+
response/list_identifiers response/list_records
|
15
15
|
response/list_metadata_formats response/list_sets response/error
|
16
16
|
}.each { |lib| require File.dirname(__FILE__) + "/provider/#{lib}" }
|
17
|
-
|
17
|
+
|
18
18
|
if defined?(ActiveRecord)
|
19
19
|
require File.dirname(__FILE__) + "/provider/model/activerecord_wrapper"
|
20
20
|
require File.dirname(__FILE__) + "/provider/model/activerecord_caching_wrapper"
|
21
21
|
end
|
22
22
|
|
23
|
-
#
|
23
|
+
# # OAI::Provider
|
24
24
|
#
|
25
|
-
# Open Archives Initiative - Protocol for Metadata Harvesting see
|
26
|
-
# http://www.openarchives.org
|
25
|
+
# Open Archives Initiative - Protocol for Metadata Harvesting see
|
26
|
+
# <http://www.openarchives.org/>
|
27
27
|
#
|
28
|
-
#
|
28
|
+
# ## Features
|
29
29
|
# * Easily setup a simple repository
|
30
30
|
# * Simple integration with ActiveRecord
|
31
31
|
# * Dublin Core metadata format included
|
@@ -33,13 +33,14 @@ end
|
|
33
33
|
# * Adaptable to any data source
|
34
34
|
# * Simple resumption token support
|
35
35
|
#
|
36
|
-
#
|
36
|
+
# ## Usage
|
37
37
|
#
|
38
|
-
# To create a functional provider either subclass Provider::Base,
|
38
|
+
# To create a functional provider either subclass {OAI::Provider::Base},
|
39
39
|
# or reconfigure the defaults.
|
40
|
-
#
|
41
|
-
# === Sub classing a provider
|
42
40
|
#
|
41
|
+
# ### Sub classing a provider
|
42
|
+
#
|
43
|
+
# ```ruby
|
43
44
|
# class MyProvider < Oai::Provider
|
44
45
|
# repository_name 'My little OAI provider'
|
45
46
|
# repository_url 'http://localhost/provider'
|
@@ -47,9 +48,11 @@ end
|
|
47
48
|
# admin_email 'root@localhost' # String or Array
|
48
49
|
# source_model MyModel.new # Subclass of OAI::Provider::Model
|
49
50
|
# end
|
51
|
+
# ```
|
50
52
|
#
|
51
|
-
#
|
53
|
+
# ### Configuring the default provider
|
52
54
|
#
|
55
|
+
# ```ruby
|
53
56
|
# class Oai::Provider::Base
|
54
57
|
# repository_name 'My little OAI Provider'
|
55
58
|
# repository_url 'http://localhost/provider'
|
@@ -58,23 +61,27 @@ end
|
|
58
61
|
# sample_identifier 'oai:pubmedcentral.gov:13900'
|
59
62
|
# source_model MyModel.new
|
60
63
|
# end
|
64
|
+
# ```
|
61
65
|
#
|
62
66
|
# The provider does allow a URL to be passed in at request processing time
|
63
67
|
# in case the repository URL cannot be determined ahead of time.
|
64
68
|
#
|
65
|
-
#
|
69
|
+
# ## Integrating with frameworks
|
66
70
|
#
|
67
|
-
#
|
71
|
+
# ### Camping
|
68
72
|
#
|
69
73
|
# In the Models module of your camping application post model definition:
|
70
|
-
#
|
74
|
+
#
|
75
|
+
# ```ruby
|
71
76
|
# class CampingProvider < OAI::Provider::Base
|
72
77
|
# repository_name 'Camping Test OAI Repository'
|
73
78
|
# source_model ActiveRecordWrapper.new(YOUR_ACTIVE_RECORD_MODEL)
|
74
79
|
# end
|
80
|
+
# ```
|
75
81
|
#
|
76
82
|
# In the Controllers module:
|
77
83
|
#
|
84
|
+
# ```ruby
|
78
85
|
# class Oai
|
79
86
|
# def get
|
80
87
|
# @headers['Content-Type'] = 'text/xml'
|
@@ -82,15 +89,17 @@ end
|
|
82
89
|
# provider.process_request(@input.merge(:url => "http:"+URL(Oai).to_s))
|
83
90
|
# end
|
84
91
|
# end
|
92
|
+
# ```
|
85
93
|
#
|
86
94
|
# The provider will be available at "/oai"
|
87
95
|
#
|
88
|
-
#
|
96
|
+
# ### Rails
|
89
97
|
#
|
90
98
|
# At the bottom of environment.rb create a OAI Provider:
|
91
99
|
#
|
100
|
+
# ```ruby
|
92
101
|
# # forgive the standard blog example.
|
93
|
-
#
|
102
|
+
#
|
94
103
|
# require 'oai'
|
95
104
|
# class BlogProvider < OAI::Provider::Base
|
96
105
|
# repository_name 'My little OAI Provider'
|
@@ -100,9 +109,11 @@ end
|
|
100
109
|
# source_model OAI::Provider::ActiveRecordWrapper.new(Post)
|
101
110
|
# sample_identifier 'oai:pubmedcentral.gov:13900'
|
102
111
|
# end
|
112
|
+
# ```
|
103
113
|
#
|
104
114
|
# Create a custom controller:
|
105
115
|
#
|
116
|
+
# ```ruby
|
106
117
|
# class OaiController < ApplicationController
|
107
118
|
# def index
|
108
119
|
# # Remove controller and action from the options. Rails adds them automatically.
|
@@ -112,50 +123,54 @@ end
|
|
112
123
|
# render :text => response, :content_type => 'text/xml'
|
113
124
|
# end
|
114
125
|
# end
|
126
|
+
# ```
|
115
127
|
#
|
116
|
-
# Special thanks to Jose Hales-Garcia for this solution.
|
128
|
+
# Special thanks to Jose Hales-Garcia for this solution.
|
117
129
|
#
|
118
|
-
#
|
130
|
+
# ## Supporting custom metadata formats
|
119
131
|
#
|
120
|
-
# See
|
121
|
-
#
|
122
|
-
# == ActiveRecord Integration
|
132
|
+
# See {OAI::MetadataFormat} for details.
|
123
133
|
#
|
124
|
-
# ActiveRecord
|
134
|
+
# ## ActiveRecord Integration
|
135
|
+
#
|
136
|
+
# ActiveRecord integration is provided by the `ActiveRecordWrapper` class.
|
125
137
|
# It takes one required paramater, the class name of the AR class to wrap,
|
126
138
|
# and optional hash of options.
|
127
139
|
#
|
128
140
|
# Valid options include:
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
133
|
-
#
|
134
|
-
#
|
141
|
+
#
|
142
|
+
# * `timestamp_field` - Specifies the model field to use as the update
|
143
|
+
# filter. Defaults to `updated_at`.
|
144
|
+
# * `limit` - Maximum number of records to return in each page/set.
|
145
|
+
# Defaults to 100.
|
146
|
+
# The wrapper will paginate the result via resumption tokens.
|
147
|
+
# _Caution: specifying too large a limit will adversely affect performance._
|
148
|
+
#
|
135
149
|
# Mapping from a ActiveRecord object to a specific metadata format follows
|
136
150
|
# this set of rules:
|
137
151
|
#
|
138
|
-
# 1. Does Model#to_{metadata_prefix} exist? If so just return the result.
|
139
|
-
# 2. Does the model provide a map via Model.map_{metadata_prefix}
|
152
|
+
# 1. Does `Model#to_{metadata_prefix}` exist? If so just return the result.
|
153
|
+
# 2. Does the model provide a map via `Model.map_{metadata_prefix}`? If so
|
140
154
|
# use the map to generate the xml document.
|
141
155
|
# 3. Loop thru the fields of the metadata format and check to see if the
|
142
156
|
# model responds to either the plural, or singular of the field.
|
143
157
|
#
|
144
158
|
# For maximum control of the xml metadata generated, it's usually best to
|
145
|
-
# provide a
|
146
|
-
# not to include any instruct
|
147
|
-
#
|
148
|
-
# === Explicit creation example
|
159
|
+
# provide a `to_{metadata_prefix}` in the model. If using Builder be sure
|
160
|
+
# not to include any `instruct!` in the xml object.
|
149
161
|
#
|
162
|
+
# ### Explicit creation example
|
163
|
+
#
|
164
|
+
# ```ruby
|
150
165
|
# class Post < ActiveRecord::Base
|
151
166
|
# def to_oai_dc
|
152
167
|
# xml = Builder::XmlMarkup.new
|
153
|
-
# xml.tag!("oai_dc:dc",
|
168
|
+
# xml.tag!("oai_dc:dc",
|
154
169
|
# 'xmlns:oai_dc' => "http://www.openarchives.org/OAI/2.0/oai_dc/",
|
155
170
|
# 'xmlns:dc' => "http://purl.org/dc/elements/1.1/",
|
156
171
|
# 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
|
157
|
-
# 'xsi:schemaLocation' =>
|
158
|
-
# %{http://www.openarchives.org/OAI/2.0/oai_dc/
|
172
|
+
# 'xsi:schemaLocation' =>
|
173
|
+
# %{http://www.openarchives.org/OAI/2.0/oai_dc/
|
159
174
|
# http://www.openarchives.org/OAI/2.0/oai_dc.xsd}) do
|
160
175
|
# xml.tag!('oai_dc:title', title)
|
161
176
|
# xml.tag!('oai_dc:description', text)
|
@@ -166,24 +181,27 @@ end
|
|
166
181
|
# end
|
167
182
|
# xml.target!
|
168
183
|
# end
|
169
|
-
# end
|
184
|
+
# end
|
185
|
+
# ```
|
170
186
|
#
|
171
|
-
#
|
187
|
+
# ### Mapping Example
|
172
188
|
#
|
189
|
+
# ```ruby
|
173
190
|
# # Extremely contrived mapping
|
174
191
|
# class Post < ActiveRecord::Base
|
175
192
|
# def self.map_oai_dc
|
176
|
-
# {:subject => :tags,
|
177
|
-
# :description => :text,
|
178
|
-
# :creator => :user,
|
193
|
+
# {:subject => :tags,
|
194
|
+
# :description => :text,
|
195
|
+
# :creator => :user,
|
179
196
|
# :contibutor => :comments}
|
180
197
|
# end
|
181
198
|
# end
|
199
|
+
# ```
|
182
200
|
#
|
183
201
|
module OAI::Provider
|
184
202
|
class Base
|
185
203
|
include OAI::Provider
|
186
|
-
|
204
|
+
|
187
205
|
class << self
|
188
206
|
attr_reader :formats
|
189
207
|
attr_accessor :name, :url, :prefix, :email, :delete_support, :granularity, :model, :identifier, :description
|
@@ -192,11 +210,11 @@ module OAI::Provider
|
|
192
210
|
@formats ||= {}
|
193
211
|
@formats[format.prefix] = format
|
194
212
|
end
|
195
|
-
|
213
|
+
|
196
214
|
def format_supported?(prefix)
|
197
215
|
@formats.keys.include?(prefix)
|
198
216
|
end
|
199
|
-
|
217
|
+
|
200
218
|
def format(prefix)
|
201
219
|
if @formats[prefix].nil?
|
202
220
|
raise OAI::FormatException.new
|
@@ -205,8 +223,8 @@ module OAI::Provider
|
|
205
223
|
end
|
206
224
|
end
|
207
225
|
|
208
|
-
protected
|
209
|
-
|
226
|
+
protected
|
227
|
+
|
210
228
|
def inherited(klass)
|
211
229
|
self.instance_variables.each do |iv|
|
212
230
|
klass.instance_variable_set(iv, self.instance_variable_get(iv))
|
@@ -217,12 +235,12 @@ module OAI::Provider
|
|
217
235
|
alias_method :repository_url, :url=
|
218
236
|
alias_method :record_prefix, :prefix=
|
219
237
|
alias_method :admin_email, :email=
|
220
|
-
alias_method :deletion_support, :delete_support=
|
221
|
-
alias_method :update_granularity, :granularity=
|
238
|
+
alias_method :deletion_support, :delete_support=
|
239
|
+
alias_method :update_granularity, :granularity=
|
222
240
|
alias_method :source_model, :model=
|
223
241
|
alias_method :sample_id, :identifier=
|
224
242
|
alias_method :extra_description, :description=
|
225
|
-
|
243
|
+
|
226
244
|
end
|
227
245
|
|
228
246
|
# Default configuration of a repository
|
@@ -235,7 +253,7 @@ module OAI::Provider
|
|
235
253
|
Base.sample_id '13900'
|
236
254
|
|
237
255
|
Base.register_format(OAI::Provider::Metadata::DublinCore.instance)
|
238
|
-
|
256
|
+
|
239
257
|
# Equivalent to '&verb=Identify', returns information about the repository
|
240
258
|
def identify(options = {})
|
241
259
|
Response::Identify.new(self.class, options).to_xml
|
@@ -246,33 +264,33 @@ module OAI::Provider
|
|
246
264
|
def list_sets(options = {})
|
247
265
|
Response::ListSets.new(self.class, options).to_xml
|
248
266
|
end
|
249
|
-
|
267
|
+
|
250
268
|
# Equivalent to '&verb=ListMetadataFormats', returns a list of metadata formats
|
251
269
|
# supported by the repository.
|
252
270
|
def list_metadata_formats(options = {})
|
253
271
|
Response::ListMetadataFormats.new(self.class, options).to_xml
|
254
272
|
end
|
255
273
|
|
256
|
-
# Equivalent to '&verb=ListIdentifiers', returns a list of record headers that
|
274
|
+
# Equivalent to '&verb=ListIdentifiers', returns a list of record headers that
|
257
275
|
# meet the supplied criteria.
|
258
276
|
def list_identifiers(options = {})
|
259
277
|
Response::ListIdentifiers.new(self.class, options).to_xml
|
260
278
|
end
|
261
|
-
|
262
|
-
# Equivalent to '&verb=ListRecords', returns a list of records that meet the
|
279
|
+
|
280
|
+
# Equivalent to '&verb=ListRecords', returns a list of records that meet the
|
263
281
|
# supplied criteria.
|
264
282
|
def list_records(options = {})
|
265
283
|
Response::ListRecords.new(self.class, options).to_xml
|
266
284
|
end
|
267
|
-
|
285
|
+
|
268
286
|
# Equivalent to '&verb=GetRecord', returns a record matching the required
|
269
|
-
# :identifier option
|
287
|
+
# :identifier option
|
270
288
|
def get_record(options = {})
|
271
289
|
Response::GetRecord.new(self.class, options).to_xml
|
272
290
|
end
|
273
|
-
|
274
|
-
# xml_response = process_verb('ListRecords', :from => 'October 1, 2005',
|
275
|
-
# :until => 'November 1, 2005')
|
291
|
+
|
292
|
+
# xml_response = process_verb('ListRecords', :from => 'October 1, 2005',
|
293
|
+
# :until => 'November 1, 2005')
|
276
294
|
#
|
277
295
|
# If you are implementing a web interface using process_request is the
|
278
296
|
# preferred way.
|
@@ -281,14 +299,14 @@ module OAI::Provider
|
|
281
299
|
|
282
300
|
# Allow the request to pass in a url
|
283
301
|
self.class.url = params['url'] ? params.delete('url') : self.class.url
|
284
|
-
|
302
|
+
|
285
303
|
verb = params.delete('verb') || params.delete(:verb)
|
286
|
-
|
304
|
+
|
287
305
|
unless verb and OAI::Const::VERBS.keys.include?(verb)
|
288
306
|
raise OAI::VerbException.new
|
289
307
|
end
|
290
|
-
|
291
|
-
send(methodize(verb), params)
|
308
|
+
|
309
|
+
send(methodize(verb), params)
|
292
310
|
|
293
311
|
rescue => err
|
294
312
|
if err.respond_to?(:code)
|
@@ -298,12 +316,12 @@ module OAI::Provider
|
|
298
316
|
end
|
299
317
|
end
|
300
318
|
end
|
301
|
-
|
319
|
+
|
302
320
|
# Convert valid OAI-PMH verbs into ruby method calls
|
303
321
|
def methodize(verb)
|
304
322
|
verb.gsub(/[A-Z]/) {|m| "_#{m.downcase}"}.sub(/^\_/,'')
|
305
323
|
end
|
306
|
-
|
324
|
+
|
307
325
|
end
|
308
|
-
|
326
|
+
|
309
327
|
end
|