oai_talia 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. data/README +81 -0
  2. data/Rakefile +127 -0
  3. data/bin/oai +68 -0
  4. data/examples/models/file_model.rb +63 -0
  5. data/examples/providers/dublin_core.rb +474 -0
  6. data/lib/oai/client/get_record.rb +15 -0
  7. data/lib/oai/client/header.rb +18 -0
  8. data/lib/oai/client/identify.rb +30 -0
  9. data/lib/oai/client/list_identifiers.rb +12 -0
  10. data/lib/oai/client/list_metadata_formats.rb +12 -0
  11. data/lib/oai/client/list_records.rb +21 -0
  12. data/lib/oai/client/list_sets.rb +19 -0
  13. data/lib/oai/client/metadata_format.rb +12 -0
  14. data/lib/oai/client/record.rb +26 -0
  15. data/lib/oai/client/response.rb +35 -0
  16. data/lib/oai/client.rb +301 -0
  17. data/lib/oai/constants.rb +34 -0
  18. data/lib/oai/exception.rb +75 -0
  19. data/lib/oai/harvester/config.rb +41 -0
  20. data/lib/oai/harvester/harvest.rb +150 -0
  21. data/lib/oai/harvester/logging.rb +70 -0
  22. data/lib/oai/harvester/mailer.rb +17 -0
  23. data/lib/oai/harvester/shell.rb +338 -0
  24. data/lib/oai/harvester.rb +39 -0
  25. data/lib/oai/provider/metadata_format/oai_dc.rb +29 -0
  26. data/lib/oai/provider/metadata_format/oai_europeana.rb +38 -0
  27. data/lib/oai/provider/metadata_format.rb +143 -0
  28. data/lib/oai/provider/model/activerecord_caching_wrapper.rb +134 -0
  29. data/lib/oai/provider/model/activerecord_wrapper.rb +139 -0
  30. data/lib/oai/provider/model.rb +74 -0
  31. data/lib/oai/provider/partial_result.rb +18 -0
  32. data/lib/oai/provider/response/error.rb +16 -0
  33. data/lib/oai/provider/response/get_record.rb +26 -0
  34. data/lib/oai/provider/response/identify.rb +25 -0
  35. data/lib/oai/provider/response/list_identifiers.rb +35 -0
  36. data/lib/oai/provider/response/list_metadata_formats.rb +34 -0
  37. data/lib/oai/provider/response/list_records.rb +34 -0
  38. data/lib/oai/provider/response/list_sets.rb +23 -0
  39. data/lib/oai/provider/response/record_response.rb +70 -0
  40. data/lib/oai/provider/response.rb +161 -0
  41. data/lib/oai/provider/resumption_token.rb +106 -0
  42. data/lib/oai/provider.rb +304 -0
  43. data/lib/oai/set.rb +29 -0
  44. data/lib/oai/xpath.rb +75 -0
  45. data/lib/oai.rb +8 -0
  46. data/lib/test.rb +25 -0
  47. data/test/activerecord_provider/config/connection.rb +5 -0
  48. data/test/activerecord_provider/config/database.yml +6 -0
  49. data/test/activerecord_provider/database/ar_migration.rb +59 -0
  50. data/test/activerecord_provider/database/oaipmhtest +0 -0
  51. data/test/activerecord_provider/fixtures/dc.yml +1501 -0
  52. data/test/activerecord_provider/helpers/providers.rb +44 -0
  53. data/test/activerecord_provider/helpers/set_provider.rb +36 -0
  54. data/test/activerecord_provider/models/dc_field.rb +7 -0
  55. data/test/activerecord_provider/models/dc_set.rb +6 -0
  56. data/test/activerecord_provider/models/oai_token.rb +3 -0
  57. data/test/activerecord_provider/tc_ar_provider.rb +113 -0
  58. data/test/activerecord_provider/tc_ar_sets_provider.rb +72 -0
  59. data/test/activerecord_provider/tc_caching_paging_provider.rb +55 -0
  60. data/test/activerecord_provider/tc_simple_paging_provider.rb +57 -0
  61. data/test/activerecord_provider/test_helper.rb +4 -0
  62. data/test/client/helpers/provider.rb +68 -0
  63. data/test/client/helpers/test_wrapper.rb +11 -0
  64. data/test/client/tc_exception.rb +36 -0
  65. data/test/client/tc_get_record.rb +37 -0
  66. data/test/client/tc_identify.rb +13 -0
  67. data/test/client/tc_libxml.rb +61 -0
  68. data/test/client/tc_list_identifiers.rb +52 -0
  69. data/test/client/tc_list_metadata_formats.rb +18 -0
  70. data/test/client/tc_list_records.rb +13 -0
  71. data/test/client/tc_list_sets.rb +19 -0
  72. data/test/client/tc_low_resolution_dates.rb +14 -0
  73. data/test/client/tc_utf8_escaping.rb +11 -0
  74. data/test/client/tc_xpath.rb +26 -0
  75. data/test/client/test_helper.rb +5 -0
  76. data/test/provider/models.rb +234 -0
  77. data/test/provider/tc_exceptions.rb +96 -0
  78. data/test/provider/tc_functional_tokens.rb +43 -0
  79. data/test/provider/tc_provider.rb +71 -0
  80. data/test/provider/tc_resumption_tokens.rb +46 -0
  81. data/test/provider/tc_simple_provider.rb +92 -0
  82. data/test/provider/test_helper.rb +36 -0
  83. data/test/test.xml +22 -0
  84. metadata +181 -0
@@ -0,0 +1,134 @@
1
+ require 'active_record'
2
+
3
+ module OAI::Provider
4
+
5
+ # ActiveRecord model class in support of the caching wrapper.
6
+ class OaiToken < ActiveRecord::Base
7
+ has_many :entries, :class_name => 'OaiEntry',
8
+ :order => "record_id", :dependent => :destroy
9
+
10
+ validates_uniqueness_of :token
11
+
12
+ # Make sanitize_sql a public method so we can make use of it.
13
+ public
14
+
15
+ def self.sanitize_sql(*arg)
16
+ super(*arg)
17
+ end
18
+
19
+ def new_record_before_save?
20
+ @new_record_before_save
21
+ end
22
+
23
+ end
24
+
25
+ # ActiveRecord model class in support of the caching wrapper.
26
+ class OaiEntry < ActiveRecord::Base
27
+ belongs_to :oai_token
28
+
29
+ validates_uniqueness_of :record_id, :scope => :oai_token
30
+ end
31
+
32
+ # = OAI::Provider::ActiveRecordCachingWrapper
33
+ #
34
+ # This class wraps an ActiveRecord model and delegates all of the record
35
+ # selection/retrieval to the AR model. It accepts options for specifying
36
+ # the update timestamp field, a timeout, and a limit. The limit option
37
+ # is used for doing pagination with resumption tokens. The timeout is
38
+ # used to expire old tokens from the cache. Default timeout is 12 hours.
39
+ #
40
+ # The difference between ActiveRecordWrapper and this class is how the
41
+ # pagination is accomplished. ActiveRecordWrapper encodes all the
42
+ # information in the token. That approach should work 99% of the time.
43
+ # If you have an extremely active respository you may want to consider
44
+ # the caching wrapper. The caching wrapper takes the entire result set
45
+ # from a request and caches it in another database table, well tables
46
+ # actually. So the result returned to the client will always be
47
+ # internally consistent.
48
+ #
49
+ class ActiveRecordCachingWrapper < ActiveRecordWrapper
50
+
51
+ attr_reader :model, :timestamp_field, :expire
52
+
53
+ def initialize(model, options={})
54
+ @expire = options.delete(:timeout) || 12.hours
55
+ super(model, options)
56
+ end
57
+
58
+ def find(selector, options={})
59
+ sweep_cache
60
+ return next_set(options[:resumption_token]) if options[:resumption_token]
61
+
62
+ conditions = sql_conditions(options)
63
+
64
+ if :all == selector
65
+ total = model.count(:id, :conditions => conditions)
66
+ if @limit && total > @limit
67
+ select_partial(
68
+ ResumptionToken.new(options.merge({:last => 0})))
69
+ else
70
+ model.find(:all, :conditions => conditions)
71
+ end
72
+ else
73
+ model.find(selector, :conditions => conditions)
74
+ end
75
+ end
76
+
77
+ protected
78
+
79
+ def next_set(token_string)
80
+ raise ResumptionTokenException.new unless @limit
81
+
82
+ token = ResumptionToken.parse(token_string)
83
+ total = model.count(:id, :conditions => token_conditions(token))
84
+
85
+ if token.last * @limit + @limit < total
86
+ select_partial(token)
87
+ else
88
+ select_partial(token).records
89
+ end
90
+ end
91
+
92
+ # select a subset of the result set, and return it with a
93
+ # resumption token to get the next subset
94
+ def select_partial(token)
95
+ if 0 == token.last
96
+ oaitoken = OaiToken.find_or_create_by_token(token.to_s)
97
+ if oaitoken.new_record_before_save?
98
+ OaiToken.connection.execute("insert into " +
99
+ "#{OaiEntry.table_name} (oai_token_id, record_id) " +
100
+ "select #{oaitoken.id}, id from #{model.table_name} where " +
101
+ "#{OaiToken.sanitize_sql(token_conditions(token))}")
102
+ end
103
+ end
104
+
105
+ oaitoken = OaiToken.find_by_token(token.to_s)
106
+ raise ResumptionTokenException.new unless oaitoken
107
+
108
+ PartialResult.new(
109
+ hydrate_records(oaitoken.entries.find(:all, :limit => @limit,
110
+ :offset => token.last * @limit)), token.next(token.last + 1)
111
+ )
112
+ end
113
+
114
+ def sweep_cache
115
+ OaiToken.destroy_all(["created_at < ?", Time.now - expire])
116
+ end
117
+
118
+ def hydrate_records(records)
119
+ model.find(records.collect {|r| r.record_id })
120
+ end
121
+
122
+ def token_conditions(token)
123
+ sql_conditions token.to_conditions_hash
124
+ end
125
+
126
+ private
127
+
128
+ def expires_at(creation)
129
+ created = Time.parse(creation.strftime("%Y-%m-%d %H:%M:%S"))
130
+ created.utc + expire
131
+ end
132
+
133
+ end
134
+ end
@@ -0,0 +1,139 @@
1
+ require 'active_record'
2
+ module OAI::Provider
3
+ # = OAI::Provider::ActiveRecordWrapper
4
+ #
5
+ # This class wraps an ActiveRecord model and delegates all of the record
6
+ # selection/retrieval to the AR model. It accepts options for specifying
7
+ # the update timestamp field, a timeout, and a limit. The limit option
8
+ # is used for doing pagination with resumption tokens. The
9
+ # expiration timeout is ignored, since all necessary information is
10
+ # encoded in the token.
11
+ #
12
+ class ActiveRecordWrapper < Model
13
+
14
+ attr_reader :model, :timestamp_field
15
+
16
+ def initialize(model, options={})
17
+ @model = model
18
+ @timestamp_field = options.delete(:timestamp_field) || 'updated_at'
19
+ @limit = options.delete(:limit)
20
+
21
+ unless options.empty?
22
+ raise ArgumentError.new(
23
+ "Unsupported options [#{options.keys.join(', ')}]"
24
+ )
25
+ end
26
+ end
27
+
28
+ def earliest
29
+ model.find(:first,
30
+ :order => "#{timestamp_field} asc").send(timestamp_field)
31
+ end
32
+
33
+ def latest
34
+ model.find(:first,
35
+ :order => "#{timestamp_field} desc").send(timestamp_field)
36
+ end
37
+ # A model class is expected to provide a method Model.sets that
38
+ # returns all the sets the model supports. See the
39
+ # activerecord_provider tests for an example.
40
+ def sets
41
+ model.sets if model.respond_to?(:sets)
42
+ end
43
+
44
+ def find(selector, options={})
45
+ return next_set(options[:resumption_token]) if options[:resumption_token]
46
+ conditions = sql_conditions(options)
47
+ if :all == selector
48
+ total = model.count(:id, :conditions => conditions)
49
+ if @limit && total > @limit
50
+ select_partial(ResumptionToken.new(options.merge({:last => 0})))
51
+ else
52
+ model.find(:all, :conditions => conditions)
53
+ end
54
+ else
55
+ begin
56
+ model.find(selector, :conditions => conditions)
57
+ rescue ActiveRecord::RecordNotFound
58
+ raise OAI::IdException.new
59
+ end
60
+ end
61
+ end
62
+
63
+ def deleted?(record)
64
+ if record.respond_to?(:deleted_at)
65
+ return record.deleted_at
66
+ elsif record.respond_to?(:deleted)
67
+ return record.deleted
68
+ end
69
+ false
70
+ end
71
+
72
+ protected
73
+
74
+ # Request the next set in this sequence.
75
+ def next_set(token_string)
76
+ raise OAI::ResumptionTokenException.new unless @limit
77
+
78
+ token = ResumptionToken.parse(token_string)
79
+ total = model.count(:id, :conditions => token_conditions(token))
80
+
81
+ if @limit < total
82
+ select_partial(token)
83
+ else # end of result set
84
+ model.find(:all,
85
+ :conditions => token_conditions(token),
86
+ :limit => @limit, :order => "#{model.primary_key} asc")
87
+ end
88
+ end
89
+
90
+ # select a subset of the result set, and return it with a
91
+ # resumption token to get the next subset
92
+ def select_partial(token)
93
+ records = model.find(:all,
94
+ :conditions => token_conditions(token),
95
+ :limit => @limit,
96
+ :order => "#{model.primary_key} asc")
97
+ raise OAI::ResumptionTokenException.new unless records
98
+ offset = records.last.send(model.primary_key.to_sym)
99
+
100
+ PartialResult.new(records, token.next(offset))
101
+ end
102
+
103
+ # build a sql conditions statement from the content
104
+ # of a resumption token. It is very important not to
105
+ # miss any changes as records may change scope as the
106
+ # harvest is in progress. To avoid loosing any changes
107
+ # the last 'id' of the previous set is used as the
108
+ # filter to the next set.
109
+ def token_conditions(token)
110
+ last = token.last
111
+ sql = sql_conditions token.to_conditions_hash
112
+
113
+ return sql if 0 == last
114
+ # Now add last id constraint
115
+ sql[0] << " AND #{model.primary_key} > ?"
116
+ sql << last
117
+
118
+ return sql
119
+ end
120
+
121
+ # build a sql conditions statement from an OAI options hash
122
+ def sql_conditions(opts)
123
+ sql = []
124
+ sql << "#{timestamp_field} >= ?" << "#{timestamp_field} <= ?"
125
+ sql << "set = ?" if opts[:set]
126
+ esc_values = [sql.join(" AND ")]
127
+ esc_values << get_time(opts[:from]).localtime << get_time(opts[:until]).localtime #-- OAI 2.0 hack - UTC fix from record_responce
128
+ esc_values << opts[:set] if opts[:set]
129
+
130
+ return esc_values
131
+ end
132
+
133
+ def get_time(time)
134
+ time.kind_of?(Time) ? time : Time.parse(time)
135
+ end
136
+
137
+ end
138
+ end
139
+
@@ -0,0 +1,74 @@
1
+ module OAI::Provider
2
+ # = OAI::Provider::Model
3
+ #
4
+ # Model implementers should subclass OAI::Provider::Model and override
5
+ # Model#earliest, Model#latest, and Model#find. Optionally Model#sets and
6
+ # Model#deleted? can be used to support sets and record deletions. It
7
+ # is also the responsibility of the model implementer to account for
8
+ # resumption tokens if support is required. Models that don't support
9
+ # resumption tokens should raise an exception if a limit is requested
10
+ # during initialization.
11
+ #
12
+ # earliest - should return the earliest update time in the repository.
13
+ # latest - should return the most recent update time in the repository.
14
+ # sets - should return an array of sets supported by the repository.
15
+ # deleted? - individual records returned should respond true or false
16
+ # when sent the deleted? message.
17
+ # available_formats - if overridden, individual records should return an
18
+ # array of prefixes for all formats in which that record is available,
19
+ # if other than ["oai_dc"]
20
+ #
21
+ # == Resumption Tokens
22
+ #
23
+ # For examples of using resumption tokens see the
24
+ # ActiveRecordWrapper, and ActiveRecordCachingWrapper classes.
25
+ #
26
+ # There are several helper models for dealing with resumption tokens please
27
+ # see the ResumptionToken class for more details.
28
+ #
29
+
30
+ class Model
31
+ attr_reader :timestamp_field
32
+
33
+ def initialize(limit = nil, timestamp_field = 'updated_at')
34
+ @limit = limit
35
+ @timestamp_field = timestamp_field
36
+ end
37
+
38
+ # should return the earliest timestamp available from this model.
39
+ def earliest
40
+ raise NotImplementedError.new
41
+ end
42
+
43
+ # should return the latest timestamp available from this model.
44
+ def latest
45
+ raise NotImplementedError.new
46
+ end
47
+
48
+ def sets
49
+ nil
50
+ end
51
+
52
+ # find is the core method of a model, it returns records from the model
53
+ # bases on the parameters passed in.
54
+ #
55
+ # <tt>selector</tt> can be a singular id, or the symbol :all
56
+ # <tt>options</tt> is a hash of options to be used to constrain the query.
57
+ #
58
+ # Valid options:
59
+ # * :from => earliest timestamp to be included in the results
60
+ # * :until => latest timestamp to be included in the results
61
+ # * :set => the set from which to retrieve the results
62
+ # * :metadata_prefix => type of metadata requested (this may be useful if
63
+ # not all records are available in all formats)
64
+ def find(selector, options={})
65
+ raise NotImplementedError.new
66
+ end
67
+
68
+ def deleted?
69
+ false
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,18 @@
1
+ module OAI::Provider
2
+ # = OAI::Provider::PartialResult
3
+ #
4
+ # PartialResult is used for returning a set/page of results from a model
5
+ # that supports resumption tokens. It should contain and array of
6
+ # records, and a resumption token for getting the next set/page.
7
+ #
8
+ class PartialResult
9
+ attr_reader :records, :token
10
+
11
+ def initialize(records, token = nil)
12
+ @records = records
13
+ @token = token
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,16 @@
1
+ module OAI::Provider::Response
2
+ class Error < Base
3
+
4
+ def initialize(provider, error)
5
+ super(provider)
6
+ @error = error
7
+ end
8
+
9
+ def to_xml
10
+ response do |r|
11
+ r.error @error.to_s, :code => @error.code
12
+ end
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ module OAI::Provider::Response
2
+
3
+ class GetRecord < RecordResponse
4
+ required_parameters :identifier, :metadata_prefix
5
+
6
+ def to_xml
7
+ id = extract_identifier(options.delete(:identifier))
8
+ unless record = provider.model.find(id, options)
9
+ raise OAI::IdException.new
10
+ end
11
+
12
+ response do |r|
13
+ r.GetRecord do
14
+ r.record do
15
+ header_for record
16
+ data_for record unless deleted?(record)
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+
26
+
@@ -0,0 +1,25 @@
1
+ module OAI::Provider::Response
2
+
3
+ class Identify < Base
4
+
5
+ def to_xml
6
+ response do |r|
7
+ r.Identify do
8
+ r.repositoryName provider.name
9
+ r.baseURL provider.url
10
+ r.protocolVersion 2.0
11
+ if provider.email and provider.email.respond_to?(:each)
12
+ provider.email.each { |address| r.adminEmail address }
13
+ else
14
+ r.adminEmail provider.email.to_s
15
+ end
16
+ r.earliestDatestamp Time.parse(provider.model.earliest.to_s).utc.xmlschema
17
+ r.deletedRecord provider.delete_support.to_s
18
+ r.granularity provider.granularity
19
+ end
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,35 @@
1
+ module OAI::Provider::Response
2
+
3
+ class ListIdentifiers < RecordResponse
4
+ required_parameters :metadata_prefix
5
+
6
+ def to_xml
7
+ result = provider.model.find(:all, options)
8
+
9
+ # result may be an array of records, or a partial result
10
+ records = result.respond_to?(:records) ? result.records : result
11
+
12
+ raise OAI::NoMatchException.new if records.nil? or records.empty?
13
+ format = requested_format # not call this in each iteration
14
+ records.reject! do |r|
15
+ !record_supports(r, format)
16
+ end
17
+
18
+ response do |r|
19
+ r.ListIdentifiers do
20
+ records.each do |rec|
21
+ header_for rec
22
+ end
23
+
24
+ # append resumption token for getting next group of records
25
+ if result.respond_to?(:token)
26
+ r.target! << result.token.to_xml
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,34 @@
1
+ module OAI::Provider::Response
2
+ class ListMetadataFormats < RecordResponse
3
+ valid_parameters :identifier
4
+
5
+ def to_xml
6
+ # Get a list of all the formats the provider understands.
7
+ formats = provider.formats.values
8
+
9
+ # if it's a doc-specific request
10
+ if options.include?(:identifier)
11
+ id = extract_identifier(options[:identifier])
12
+ unless record = provider.model.find(id, options)
13
+ raise OAI::IdException.new
14
+ end
15
+
16
+ # Remove any format that this particular record can't be provided in.
17
+ formats.reject! { |f| !record_supports(record, f.prefix) }
18
+ end
19
+ response do |r|
20
+ r.ListMetadataFormats do
21
+ formats.each do |format|
22
+ r.metadataFormat do
23
+ r.metadataPrefix format.prefix
24
+ r.schema format.schema
25
+ r.metadataNamespace format.namespace
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ module OAI::Provider::Response
2
+
3
+ class ListRecords < RecordResponse
4
+ required_parameters :metadata_prefix
5
+
6
+ def to_xml
7
+ result = provider.model.find(:all, options)
8
+ # result may be an array of records, or a partial result
9
+ records = result.respond_to?(:records) ? result.records : result
10
+
11
+ raise OAI::NoMatchException.new if records.nil? or records.empty?
12
+
13
+ response do |r|
14
+ r.ListRecords do
15
+ records.each do |rec|
16
+ r.record do
17
+ header_for rec
18
+ data_for rec unless deleted?(rec)
19
+ end
20
+ end
21
+
22
+ # append resumption token for getting next group of records
23
+ if result.respond_to?(:token)
24
+ r.target! << result.token.to_xml
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
@@ -0,0 +1,23 @@
1
+ module OAI::Provider::Response
2
+
3
+ class ListSets < Base
4
+
5
+ def to_xml
6
+ raise OAI::SetException.new unless provider.model.sets
7
+
8
+ response do |r|
9
+ r.ListSets do
10
+ provider.model.sets.each do |set|
11
+ r.set do
12
+ r.setSpec set.spec
13
+ r.setName set.name
14
+ r.setDescription(set.description) if set.respond_to?(:description)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,70 @@
1
+ module OAI::Provider::Response
2
+ class RecordResponse < Base
3
+ def self.inherited(klass)
4
+ klass.valid_parameters :metadata_prefix, :from, :until, :set
5
+ klass.default_parameters :from => Proc.new {|x| Time.parse(x.provider.model.earliest.to_s) },
6
+ :until => Proc.new {|x| Time.parse(x.provider.model.latest.to_s) }
7
+ end
8
+
9
+ # emit record header
10
+ def header_for(record)
11
+ param = Hash.new
12
+ param[:status] = 'deleted' if deleted?(record)
13
+ @builder.header param do
14
+ @builder.identifier identifier_for(record)
15
+ @builder.datestamp timestamp_for(record)
16
+ sets_for(record).each do |set|
17
+ @builder.setSpec set.spec
18
+ end
19
+ end
20
+ end
21
+ # metadata - core routine for delivering metadata records
22
+ #
23
+ def data_for(record)
24
+ @builder.metadata do
25
+ @builder.target! << provider.format(requested_format).encode(provider.model, record)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def identifier_for(record)
32
+ "#{provider.prefix}/#{record.id}"
33
+ end
34
+
35
+ def timestamp_for(record)
36
+ record.send(provider.model.timestamp_field).utc.xmlschema
37
+ end
38
+
39
+ def sets_for(record)
40
+ return [] unless record.respond_to?(:sets) and record.sets
41
+ record.sets.respond_to?(:each) ? record.sets : [record.sets]
42
+ end
43
+
44
+ def requested_format
45
+ format =
46
+ if options[:metadata_prefix]
47
+ options[:metadata_prefix]
48
+ elsif options[:resumption_token]
49
+ OAI::Provider::ResumptionToken.extract_format(options[:resumption_token])
50
+ end
51
+ raise OAI::FormatException.new unless provider.format_supported?(format)
52
+
53
+ format
54
+ end
55
+
56
+ def deleted?(record)
57
+ return record.deleted? if record.respond_to?(:deleted?)
58
+ return record.deleted if record.respond_to?(:deleted)
59
+ return record.deleted_at if record.respond_to?(:deleted_at)
60
+ false
61
+ end
62
+
63
+ def record_supports(record, prefix)
64
+ prefix == 'oai_dc' or
65
+ record.respond_to?("to_#{prefix}") or
66
+ record.respond_to?("map_#{prefix}")
67
+ end
68
+
69
+ end
70
+ end