oai 1.0.0 → 1.0.1

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.
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