oai 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74c8bf29b2463c130d3ec55f04a433fb828041fba20d9df6045bfec0ac03dbf3
4
- data.tar.gz: 8cc85a82a2d1006499baa2a80a238eb50c04b7e9c33e64b0aa5e19daa7afabdd
3
+ metadata.gz: e7cdbec6f4b6ca5a672add7fe1463748bd9f8a9cdf50a55400a5711e45be6ef9
4
+ data.tar.gz: 6a22c53d9520777e69bb6e32032880cb53793d89a2712a5b7b7370d02553ff25
5
5
  SHA512:
6
- metadata.gz: fd82dfd51917453cea94d63549975bf60cb928c7c619262cb017a79b5dccfe6ae5d02e6a938aeaf4e3577417aa3644319d5e8c59ed9cdd3a992c4db49c886e1d
7
- data.tar.gz: 4cfb1c2ca5ec4b81fbe9e21462714801e4aa2c36ce58ecbb01facafd0cbcd7e354ba66d0df53f1789bc19dc9d8eb795cc220605381c9d2d6c24d1a611f6a3505
6
+ metadata.gz: 1cf4fa083c9c4486110ec0c7322ac077addd66050a3085291b591597c02ec8d02ca8230594e2fc16070d700dd243ec0f3f74d63ca4684522828f6431f15f6bf7
7
+ data.tar.gz: 8bca0a510ff38a28e0e4119e645759e627a19aca0aed1259b2129592094c4851da7337b0824054660b2b87fd07daf7b529ad7fe30dfe237aa90f9b5a5faac81a
@@ -159,13 +159,13 @@ module OAI::Provider
159
159
  # the last 'id' of the previous set is used as the
160
160
  # filter to the next set.
161
161
  def token_conditions(token)
162
- last = token.last
162
+ last_id = token.last_str
163
163
  sql = sql_conditions token.to_conditions_hash
164
164
 
165
- return sql if 0 == last
165
+ return sql if "0" == last_id
166
166
  # Now add last id constraint
167
167
  sql.first << " AND #{identifier_field} > :id"
168
- sql.last[:id] = last
168
+ sql.last[:id] = last_id
169
169
 
170
170
  return sql
171
171
  end
@@ -8,16 +8,41 @@ module OAI::Provider
8
8
  # The ResumptionToken class forms the basis of paging query results. It
9
9
  # provides several helper methods for dealing with resumption tokens.
10
10
  #
11
+ # OAI-PMH spec does not specify anything about resumptionToken format, they can
12
+ # be purely opaque tokens.
13
+ #
14
+ # Our implementation however encodes everything needed to construct the next page
15
+ # inside the resumption token.
16
+ #
17
+ # == The 'last' component: offset or ID/pk to resume from
18
+ #
19
+ # The `#last` component is an offset or ID to resume from. In the case of it being
20
+ # an ID to resume from, this assumes that ID's are sortable and results are returned
21
+ # in ID order, so that the 'last' ID can be used as the place to resume from.
22
+ #
23
+ # Originally it was assumed that #last was always an integer, but since existing
24
+ # implementations (like ActiveRecordWrapper) used it as an ID, and identifiers and
25
+ # primary keys are _not_ always integers (can be UUID etc), we have expanded to allow
26
+ # any string value.
27
+ #
28
+ # However, for backwards compatibility #last always returns an integer (sometimes 0 if
29
+ # actual last component is not an integer), and #last_str returns the full string version.
30
+ # Trying to change #last itself to be string broke a lot of existing code in this gem
31
+ # in mysterious ways.
32
+ #
33
+ # Also beware that in some cases the value 0/"0" seems to be a special value used
34
+ # to signify some special case. A lot of "code archeology" going on here after significant
35
+ # period of no maintenance to this gem.
11
36
  class ResumptionToken
12
- attr_reader :prefix, :set, :from, :until, :last, :expiration, :total
37
+ attr_reader :prefix, :set, :from, :until, :last, :last_str, :expiration, :total
13
38
 
14
39
  # parses a token string and returns a ResumptionToken
15
40
  def self.parse(token_string)
16
41
  begin
17
42
  options = {}
18
- matches = /(.+):(\d+)$/.match(token_string)
19
- options[:last] = matches.captures[1].to_i
20
-
43
+ matches = /(.+):([^ :]+)$/.match(token_string)
44
+ options[:last] = matches.captures[1]
45
+
21
46
  parts = matches.captures[0].split('.')
22
47
  options[:metadata_prefix] = parts.shift
23
48
  parts.each do |part|
@@ -35,7 +60,7 @@ module OAI::Provider
35
60
  raise OAI::ResumptionTokenException.new
36
61
  end
37
62
  end
38
-
63
+
39
64
  # extracts the metadata prefix from a token string
40
65
  def self.extract_format(token_string)
41
66
  return token_string.split('.')[0]
@@ -44,32 +69,32 @@ module OAI::Provider
44
69
  def initialize(options, expiration = nil, total = nil)
45
70
  @prefix = options[:metadata_prefix]
46
71
  @set = options[:set]
47
- @last = options[:last]
72
+ self.last = options[:last]
48
73
  @from = options[:from] if options[:from]
49
74
  @until = options[:until] if options[:until]
50
75
  @expiration = expiration if expiration
51
76
  @total = total if total
52
77
  end
53
-
78
+
54
79
  # convenience method for setting the offset of the next set of results
55
80
  def next(last)
56
- @last = last
81
+ self.last = last
57
82
  self
58
83
  end
59
-
84
+
60
85
  def ==(other)
61
86
  prefix == other.prefix and set == other.set and from == other.from and
62
- self.until == other.until and last == other.last and
87
+ self.until == other.until and last == other.last and
63
88
  expiration == other.expiration and total == other.total
64
89
  end
65
-
90
+
66
91
  # output an xml resumption token
67
92
  def to_xml
68
93
  xml = Builder::XmlMarkup.new
69
94
  xml.resumptionToken(encode_conditions, hash_of_attributes)
70
95
  xml.target!
71
96
  end
72
-
97
+
73
98
  # return a hash containing just the model selection parameters
74
99
  def to_conditions_hash
75
100
  conditions = {:metadata_prefix => self.prefix }
@@ -78,20 +103,31 @@ module OAI::Provider
78
103
  conditions[:until] = self.until if self.until
79
104
  conditions
80
105
  end
81
-
82
- # return the a string representation of the token minus the offset
106
+
107
+ # return the a string representation of the token minus the offset/ID
108
+ #
109
+ # Q: Why does it eliminate the offset/id "last" on the end? Doesn't fully
110
+ # represent state without it, which is confusing. Not sure, but
111
+ # other code seems to rely on it, tests break if not.
83
112
  def to_s
84
113
  encode_conditions.gsub(/:\w+?$/, '')
85
114
  end
86
115
 
87
116
  private
88
-
117
+
118
+ # take care of our logic to store an integer and a str version, for backwards
119
+ # compat where it was assumed to be an integer, as well as supporting string.
120
+ def last=(value)
121
+ @last = value.to_i
122
+ @last_str = value.to_s
123
+ end
124
+
89
125
  def encode_conditions
90
126
  encoded_token = @prefix.to_s.dup
91
127
  encoded_token << ".s(#{set})" if set
92
128
  encoded_token << ".f(#{self.from.utc.xmlschema})" if self.from
93
129
  encoded_token << ".u(#{self.until.utc.xmlschema})" if self.until
94
- encoded_token << ":#{last}"
130
+ encoded_token << ":#{last_str}"
95
131
  end
96
132
 
97
133
  def hash_of_attributes
@@ -35,6 +35,13 @@ class SimpleResumptionProvider < OAI::Provider::Base
35
35
  source_model ActiveRecordWrapper.new(DCField, :limit => 25)
36
36
  end
37
37
 
38
+ class SimpleResumptionProviderWithNonIntegerID < OAI::Provider::Base
39
+ repository_name 'ActiveRecord Resumption Provider With Non-Integer ID'
40
+ repository_url 'http://localhost'
41
+ record_prefix 'oai:test'
42
+ source_model ActiveRecordWrapper.new(DCField, :limit => 25, identifier_field: "source")
43
+ end
44
+
38
45
  class CachingResumptionProvider < OAI::Provider::Base
39
46
  repository_name 'ActiveRecord Caching Resumption Provider'
40
47
  repository_url 'http://localhost'
@@ -8,19 +8,38 @@ class SimpleResumptionProviderTest < TransactionalTestCase
8
8
  assert_not_nil doc.elements["/OAI-PMH/ListRecords/resumptionToken"]
9
9
  assert_equal 26, doc.elements["/OAI-PMH/ListRecords"].to_a.size
10
10
  token = doc.elements["/OAI-PMH/ListRecords/resumptionToken"].text
11
+
11
12
  doc = Document.new(@provider.list_records(:resumption_token => token))
12
13
  assert_not_nil doc.elements["/OAI-PMH/ListRecords/resumptionToken"]
13
14
  token = doc.elements["/OAI-PMH/ListRecords/resumptionToken"].text
14
15
  assert_equal 26, doc.elements["/OAI-PMH/ListRecords"].to_a.size
16
+
15
17
  doc = Document.new(@provider.list_records(:resumption_token => token))
16
18
  assert_not_nil doc.elements["/OAI-PMH/ListRecords/resumptionToken"]
17
19
  token = doc.elements["/OAI-PMH/ListRecords/resumptionToken"].text
18
20
  assert_equal 26, doc.elements["/OAI-PMH/ListRecords"].to_a.size
21
+
19
22
  doc = Document.new(@provider.list_records(:resumption_token => token))
20
23
  assert_nil doc.elements["/OAI-PMH/ListRecords/resumptionToken"]
21
24
  assert_equal 25, doc.elements["/OAI-PMH/ListRecords"].to_a.size
22
25
  end
23
26
 
27
+ def test_non_integer_identifiers_resumption
28
+ @provider = SimpleResumptionProviderWithNonIntegerID.new
29
+
30
+ doc = Document.new(@provider.list_records(:metadata_prefix => 'oai_dc'))
31
+ assert_not_nil doc.elements["/OAI-PMH/ListRecords/resumptionToken"]
32
+ assert_equal 26, doc.elements["/OAI-PMH/ListRecords"].to_a.size
33
+ token = doc.elements["/OAI-PMH/ListRecords/resumptionToken"].text
34
+
35
+ next_doc = Document.new(@provider.list_records(:resumption_token => token))
36
+ assert_not_nil next_doc.elements["/OAI-PMH/ListRecords/resumptionToken"]
37
+ next_token = next_doc.elements["/OAI-PMH/ListRecords/resumptionToken"].text
38
+ assert_equal 26, next_doc.elements["/OAI-PMH/ListRecords"].to_a.size
39
+
40
+ assert_not_equal token, next_token
41
+ end
42
+
24
43
  def test_from_and_until
25
44
  first_id = DCField.order("id asc").first.id
26
45
  DCField.where("id < #{first_id + 25}").update_all(updated_at: Time.parse("September 15 2005"))
@@ -43,4 +43,10 @@ class ResumptionTokenTest < Test::Unit::TestCase
43
43
  assert_equal "#{@token.to_s}:#{@token.last}", doc.elements['/resumptionToken'].text
44
44
  end
45
45
 
46
+ def test_resumption_token_id_does_not_need_to_be_numeric
47
+ serialized = "oai_dc.s(A).f(2005-01-01T17:00:00Z).u(2005-01-31T17:00:00Z):FA129C"
48
+
49
+ token = ResumptionToken.parse(serialized)
50
+ assert_equal serialized, token.send(:encode_conditions)
51
+ end
46
52
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oai
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ed Summers
8
8
  autorequire: oai
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-11-13 00:00:00.000000000 Z
11
+ date: 2020-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: builder
@@ -195,8 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
195
195
  - !ruby/object:Gem::Version
196
196
  version: '0'
197
197
  requirements: []
198
- rubyforge_project:
199
- rubygems_version: 2.7.6
198
+ rubygems_version: 3.0.3
200
199
  signing_key:
201
200
  specification_version: 4
202
201
  summary: A ruby library for working with the Open Archive Initiative Protocol for