ezid-client 0.9.1 → 0.10.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.
- checksums.yaml +4 -4
- data/README.md +37 -1
- data/VERSION +1 -1
- data/lib/ezid/configuration.rb +4 -0
- data/lib/ezid/identifier.rb +59 -31
- data/lib/ezid/metadata.rb +54 -42
- data/spec/unit/identifier_spec.rb +79 -15
- data/spec/unit/metadata_spec.rb +17 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a8c3c449a9447e7176421f705a537c9919442a9
|
4
|
+
data.tar.gz: 9d16f67d27605bc7066f6dd3df2e9a9be5dc4e26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94f6cf47f52bb030e0f7cf4442ef5aedbcb31f9348ebb1e528a11e9727b2a3e94260002ff43dd2fb1240fdeadbc3b2adde341fa83e5ad6b28f44e9e0f982eedd
|
7
|
+
data.tar.gz: 4a70172958a729e05c2b5ba2d5a51081e5a4865135c9107111045a9993f9e5881f10d3ca50d0b180d2f1b73cc383913955111beeac1660da9f6635f17754ef01
|
data/README.md
CHANGED
@@ -113,7 +113,43 @@ I, [2014-12-04T15:12:48.853964 #86734] INFO -- : EZID DELETE ark:/99999/fk4n58p
|
|
113
113
|
|
114
114
|
## Metadata handling
|
115
115
|
|
116
|
-
|
116
|
+
In order to ease metadata management access to EZID [reserved metadata elements](http://ezid.cdlib.org/doc/apidoc.html#internal-metadata) and [metadata profiles](http://ezid.cdlib.org/doc/apidoc.html#metadata-profiles) is provided through `#method_missing` according to these heuristics:
|
117
|
+
|
118
|
+
**Reserved elements** can be read and written using the name of the element without the leading underscore:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
>> identifier.status # reads "_status" element
|
122
|
+
=> "public"
|
123
|
+
>> identifier.status = "unavailable" # writes "_status" element
|
124
|
+
=> "unavailable"
|
125
|
+
```
|
126
|
+
|
127
|
+
Notes:
|
128
|
+
- `_crossref` is an exception because `crossref` is also the name of a metadata profile and a special element. Use `identifier._crossref` to read and `identifier._crossref = value` to write.
|
129
|
+
- Reserved elements which are not user-writeable do not implement writers.
|
130
|
+
- Special readers are implemented for reserved elements having date/time values -- `_created` and `_updated` -- which convert the string time values of EZID to Ruby `Time` instances.
|
131
|
+
|
132
|
+
**Metadata profile elements** can be read and written using the name of the element, replacing the dot (".") with an underscore:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
>> identifier.dc_type # reads "dc.type" element
|
136
|
+
=> "Collection"
|
137
|
+
>> identifier.dc_type = "Image" # writes "dc.type" element
|
138
|
+
=> "Image"
|
139
|
+
```
|
140
|
+
|
141
|
+
**Registering custom metadata elements**
|
142
|
+
|
143
|
+
Custom metadata element accessors can be created by a registration process:
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
Ezid::Client.configure do |config|
|
147
|
+
# register the element "custom"
|
148
|
+
config.metadata.register_element :custom
|
149
|
+
# register the element "dc.identifier" under the accessor :dc_identifier
|
150
|
+
config.metadata.register_element :dc_identifier, name: "dc.identifier"
|
151
|
+
end
|
152
|
+
```
|
117
153
|
|
118
154
|
**Setting default metadata values**
|
119
155
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.10.0
|
data/lib/ezid/configuration.rb
CHANGED
data/lib/ezid/identifier.rb
CHANGED
@@ -1,20 +1,16 @@
|
|
1
|
-
require "forwardable"
|
2
|
-
|
3
1
|
module Ezid
|
4
2
|
#
|
5
3
|
# Represents an EZID identifier as a resource.
|
6
4
|
#
|
5
|
+
# Ezid::Identifier delegates access to registered metadata elements through #method_missing.
|
6
|
+
#
|
7
7
|
# @api public
|
8
8
|
#
|
9
9
|
class Identifier
|
10
|
-
extend Forwardable
|
11
10
|
|
12
11
|
attr_reader :id, :client
|
13
12
|
attr_accessor :shoulder, :metadata
|
14
13
|
|
15
|
-
def_delegators :metadata, *(Metadata.elements.readers)
|
16
|
-
def_delegators :metadata, *(Metadata.elements.writers)
|
17
|
-
|
18
14
|
# Attributes to display on inspect
|
19
15
|
INSPECT_ATTRS = %w( id status target created )
|
20
16
|
|
@@ -126,10 +122,11 @@ module Ezid
|
|
126
122
|
end
|
127
123
|
|
128
124
|
# Deletes the identifier from EZID
|
125
|
+
# @see http://ezid.cdlib.org/doc/apidoc.html#operation-delete-identifier
|
129
126
|
# @return [Ezid::Identifier] the identifier
|
130
127
|
# @raise [Ezid::Error]
|
131
128
|
def delete
|
132
|
-
raise Error, "
|
129
|
+
raise Error, "Only persisted, reserved identifiers may be deleted: #{inspect}." unless deletable?
|
133
130
|
client.delete_identifier(id)
|
134
131
|
@deleted = true
|
135
132
|
reset
|
@@ -150,40 +147,71 @@ module Ezid
|
|
150
147
|
# Is the identifier unavailable?
|
151
148
|
# @return [Boolean]
|
152
149
|
def unavailable?
|
153
|
-
status
|
150
|
+
status =~ /^#{UNAVAILABLE}/
|
154
151
|
end
|
155
152
|
|
156
|
-
|
157
|
-
|
158
|
-
def
|
159
|
-
|
160
|
-
@metadata = Metadata.new(response.metadata)
|
153
|
+
# Is the identifier deletable?
|
154
|
+
# @return [Boolean]
|
155
|
+
def deletable?
|
156
|
+
persisted? && reserved?
|
161
157
|
end
|
162
158
|
|
163
|
-
|
164
|
-
|
159
|
+
# Mark the identifier as unavailable
|
160
|
+
# @param reason [String] an optional reason
|
161
|
+
# @return [String] the new status
|
162
|
+
def unavailable!(reason = nil)
|
163
|
+
raise Error, "Cannot make a reserved identifier unavailable." if persisted? && reserved?
|
164
|
+
value = UNAVAILABLE
|
165
|
+
value << " | #{reason}" if reason
|
166
|
+
self.status = value
|
165
167
|
end
|
166
168
|
|
167
|
-
|
168
|
-
|
169
|
+
# Mark the identifier as public
|
170
|
+
# @return [String] the new status
|
171
|
+
def public!
|
172
|
+
self.status = PUBLIC
|
169
173
|
end
|
170
174
|
|
171
|
-
|
172
|
-
id ? create : mint
|
173
|
-
end
|
175
|
+
protected
|
174
176
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
177
|
+
def method_missing(method, *args)
|
178
|
+
metadata.send(method, *args)
|
179
|
+
rescue NoMethodError
|
180
|
+
super
|
181
|
+
end
|
179
182
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
+
private
|
184
|
+
|
185
|
+
def refresh_metadata
|
186
|
+
response = client.get_identifier_metadata(id)
|
187
|
+
@metadata = Metadata.new(response.metadata)
|
188
|
+
end
|
189
|
+
|
190
|
+
def clear_metadata
|
191
|
+
@metadata.clear
|
192
|
+
end
|
193
|
+
|
194
|
+
def modify
|
195
|
+
client.modify_identifier(id, metadata)
|
196
|
+
end
|
197
|
+
|
198
|
+
def create_or_mint
|
199
|
+
id ? create : mint
|
200
|
+
end
|
201
|
+
|
202
|
+
def mint
|
203
|
+
response = client.mint_identifier(shoulder, metadata)
|
204
|
+
@id = response.id
|
205
|
+
end
|
206
|
+
|
207
|
+
def create
|
208
|
+
client.create_identifier(id, metadata)
|
209
|
+
end
|
210
|
+
|
211
|
+
def init_metadata(args)
|
212
|
+
@metadata = Metadata.new(args.delete(:metadata))
|
213
|
+
update_metadata(self.class.defaults.merge(args))
|
214
|
+
end
|
183
215
|
|
184
|
-
def init_metadata(args)
|
185
|
-
@metadata = Metadata.new(args.delete(:metadata))
|
186
|
-
update_metadata(self.class.defaults.merge(args))
|
187
|
-
end
|
188
216
|
end
|
189
217
|
end
|
data/lib/ezid/metadata.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
require "delegate"
|
2
|
-
require "singleton"
|
3
2
|
|
4
3
|
module Ezid
|
5
4
|
#
|
6
|
-
# EZID metadata collection for an identifier
|
5
|
+
# EZID metadata collection for an identifier.
|
7
6
|
#
|
8
7
|
# @note Although this API is not private, its direct use is discouraged.
|
9
8
|
# Instead use the metadata element accessors through Ezid::Identifier.
|
@@ -64,71 +63,53 @@ module Ezid
|
|
64
63
|
# @see http://ezid.cdlib.org/doc/apidoc.html#internal-metadata
|
65
64
|
RESERVED_ELEMENTS = RESERVED_READONLY_ELEMENTS + RESERVED_READWRITE_ELEMENTS
|
66
65
|
|
67
|
-
# Metadata element registry
|
68
|
-
class ElementRegistry < SimpleDelegator
|
69
|
-
include Singleton
|
70
|
-
|
71
|
-
def initialize
|
72
|
-
super(Hash.new)
|
73
|
-
end
|
74
|
-
|
75
|
-
def readers
|
76
|
-
keys
|
77
|
-
end
|
78
|
-
|
79
|
-
def writers
|
80
|
-
keys.select { |k| self[k].writer }.map(&:to_s).map { |k| k.concat("=") }.map(&:to_sym)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
66
|
def self.initialize!
|
85
67
|
register_elements
|
86
|
-
define_element_accessors
|
87
68
|
end
|
88
69
|
|
89
|
-
def self.
|
90
|
-
|
70
|
+
def self.registered_elements
|
71
|
+
@@registered_elements ||= {}
|
91
72
|
end
|
92
73
|
|
93
74
|
def self.register_elements
|
94
75
|
register_profile_elements
|
95
76
|
register_reserved_elements
|
96
|
-
elements.freeze
|
97
77
|
end
|
98
78
|
|
99
|
-
def self.
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
if element.writer
|
104
|
-
define_method("#{accessor}=") { |value| writer(element.name, value) }
|
105
|
-
end
|
79
|
+
def self.register_element(accessor, opts={})
|
80
|
+
if element = registered_elements[accessor.to_sym]
|
81
|
+
raise Error, "Element \"#{element.name}\" already registered under key :#{accessor}"
|
106
82
|
end
|
83
|
+
writer = opts.fetch(:writer, true)
|
84
|
+
name = opts.fetch(:name, accessor.to_s)
|
85
|
+
registered_elements[accessor.to_sym] = Element.new(name, writer).freeze
|
107
86
|
end
|
108
87
|
|
109
|
-
def self.
|
110
|
-
|
111
|
-
elements[accessor] = Element.new(element, writer).freeze
|
88
|
+
def self.register_profile_element(profile, element)
|
89
|
+
register_element("#{profile}_#{element}", name: "#{profile}.#{element}")
|
112
90
|
end
|
113
91
|
|
114
|
-
def self.register_profile_elements
|
115
|
-
|
116
|
-
|
117
|
-
|
92
|
+
def self.register_profile_elements(profile = nil)
|
93
|
+
if profile
|
94
|
+
PROFILES[profile].each { |element| register_profile_element(profile, element) }
|
95
|
+
else
|
96
|
+
PROFILES.keys.each do |profile|
|
97
|
+
register_profile_elements(profile)
|
98
|
+
register_element(profile) unless profile == "dc"
|
118
99
|
end
|
119
|
-
register_element(profile.to_sym, profile) unless profile == "dc"
|
120
100
|
end
|
121
101
|
end
|
122
102
|
|
123
103
|
def self.register_reserved_elements
|
124
104
|
RESERVED_ELEMENTS.each do |element|
|
125
|
-
accessor = (
|
126
|
-
register_element(accessor, element, writer: RESERVED_READWRITE_ELEMENTS.include?(element))
|
105
|
+
accessor = (element == "_crossref") ? element : element.sub("_", "")
|
106
|
+
register_element(accessor, name: element, writer: RESERVED_READWRITE_ELEMENTS.include?(element))
|
127
107
|
end
|
128
108
|
end
|
129
109
|
|
130
|
-
private_class_method :
|
131
|
-
:
|
110
|
+
private_class_method :register_elements,
|
111
|
+
:register_reserved_elements,
|
112
|
+
:register_profile_elements
|
132
113
|
|
133
114
|
def initialize(data={})
|
134
115
|
super(coerce(data))
|
@@ -147,8 +128,28 @@ module Ezid
|
|
147
128
|
to_anvl
|
148
129
|
end
|
149
130
|
|
131
|
+
def registered_elements
|
132
|
+
self.class.registered_elements
|
133
|
+
end
|
134
|
+
|
135
|
+
protected
|
136
|
+
|
137
|
+
def method_missing(method, *args)
|
138
|
+
return registered_reader(method) if registered_reader?(method, *args)
|
139
|
+
return registered_writer(method, *args) if registered_writer?(method, *args)
|
140
|
+
super
|
141
|
+
end
|
142
|
+
|
150
143
|
private
|
151
144
|
|
145
|
+
def registered_reader?(accessor, *args)
|
146
|
+
args.empty? && registered_elements.include?(accessor)
|
147
|
+
end
|
148
|
+
|
149
|
+
def registered_reader(accessor)
|
150
|
+
reader registered_elements[accessor].name
|
151
|
+
end
|
152
|
+
|
152
153
|
def reader(element)
|
153
154
|
value = self[element]
|
154
155
|
if RESERVED_TIME_ELEMENTS.include?(element)
|
@@ -158,6 +159,17 @@ module Ezid
|
|
158
159
|
value
|
159
160
|
end
|
160
161
|
|
162
|
+
def registered_writer?(method, *args)
|
163
|
+
return false unless method.to_s.end_with?("=") && args.size == 1
|
164
|
+
accessor = method.to_s.sub("=", "").to_sym
|
165
|
+
registered_elements.include?(accessor) && registered_elements[accessor].writer
|
166
|
+
end
|
167
|
+
|
168
|
+
def registered_writer(method, *args)
|
169
|
+
accessor = method.to_s.sub("=", "").to_sym
|
170
|
+
writer(registered_elements[accessor].name, *args)
|
171
|
+
end
|
172
|
+
|
161
173
|
def writer(element, value)
|
162
174
|
self[element] = value
|
163
175
|
end
|
@@ -108,7 +108,7 @@ module Ezid
|
|
108
108
|
context "when id and `created' are present" do
|
109
109
|
before do
|
110
110
|
allow(subject).to receive(:id) { "ark:/99999/fk4fn19h88" }
|
111
|
-
|
111
|
+
subject.metadata["_created"] = "1416507086"
|
112
112
|
end
|
113
113
|
it "should be true" do
|
114
114
|
expect(subject).to be_persisted
|
@@ -117,11 +117,28 @@ module Ezid
|
|
117
117
|
end
|
118
118
|
|
119
119
|
describe "#delete" do
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
120
|
+
context "when the identifier is reserved" do
|
121
|
+
subject { described_class.new(id: "id", status: Identifier::RESERVED) }
|
122
|
+
context "and is persisted" do
|
123
|
+
before { allow(subject).to receive(:persisted?) { true } }
|
124
|
+
it "should delete the identifier" do
|
125
|
+
expect(subject.client).to receive(:delete_identifier).with("id") { double(id: "id") }
|
126
|
+
subject.delete
|
127
|
+
expect(subject).to be_deleted
|
128
|
+
end
|
129
|
+
end
|
130
|
+
context "and is not persisted" do
|
131
|
+
before { allow(subject).to receive(:persisted?) { false } }
|
132
|
+
it "should raise an exception" do
|
133
|
+
expect { subject.delete }.to raise_error
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
context "when identifier is not reserved" do
|
138
|
+
subject { described_class.new(id: "id", status: Identifier::PUBLIC) }
|
139
|
+
it "should raise an exception" do
|
140
|
+
expect { subject.delete }.to raise_error
|
141
|
+
end
|
125
142
|
end
|
126
143
|
end
|
127
144
|
|
@@ -167,23 +184,70 @@ module Ezid
|
|
167
184
|
end
|
168
185
|
|
169
186
|
describe "boolean status methods" do
|
170
|
-
context "when the
|
171
|
-
before {
|
187
|
+
context "when the identifier is public" do
|
188
|
+
before { subject.public! }
|
172
189
|
it { is_expected.to be_public }
|
173
190
|
it { is_expected.not_to be_reserved }
|
174
191
|
it { is_expected.not_to be_unavailable }
|
175
192
|
end
|
176
|
-
context "when the
|
177
|
-
before {
|
193
|
+
context "when the identifier is reserved" do
|
194
|
+
before { subject.status = Identifier::RESERVED }
|
178
195
|
it { is_expected.not_to be_public }
|
179
196
|
it { is_expected.to be_reserved }
|
180
197
|
it { is_expected.not_to be_unavailable }
|
181
198
|
end
|
182
|
-
context "when the
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
199
|
+
context "when the identifier is unavailable" do
|
200
|
+
context "and it has no reason" do
|
201
|
+
before { subject.unavailable! }
|
202
|
+
it { is_expected.not_to be_public }
|
203
|
+
it { is_expected.not_to be_reserved }
|
204
|
+
it { is_expected.to be_unavailable }
|
205
|
+
end
|
206
|
+
context "and it has a reason" do
|
207
|
+
before { subject.unavailable!("withdrawn") }
|
208
|
+
it { is_expected.not_to be_public }
|
209
|
+
it { is_expected.not_to be_reserved }
|
210
|
+
it { is_expected.to be_unavailable }
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
describe "status-changing methods" do
|
216
|
+
describe "#unavailable!" do
|
217
|
+
context "when the identifier is reserved" do
|
218
|
+
subject { described_class.new(id: "id", status: Identifier::RESERVED) }
|
219
|
+
context "and persisted" do
|
220
|
+
before { allow(subject).to receive(:persisted?) { true } }
|
221
|
+
it "should raise an exception" do
|
222
|
+
expect { subject.unavailable! }.to raise_error
|
223
|
+
end
|
224
|
+
end
|
225
|
+
context "and not persisted" do
|
226
|
+
before { allow(subject).to receive(:persisted?) { false } }
|
227
|
+
it "should changed the status" do
|
228
|
+
expect { subject.unavailable! }.to change(subject, :status).from(Identifier::RESERVED).to(Identifier::UNAVAILABLE)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
context "when the identifier is public" do
|
233
|
+
subject { described_class.new(id: "id", status: Identifier::PUBLIC) }
|
234
|
+
context "and no reason is given" do
|
235
|
+
it "should change the status" do
|
236
|
+
expect { subject.unavailable! }.to change(subject, :status).from(Identifier::PUBLIC).to(Identifier::UNAVAILABLE)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
context "and a reason is given" do
|
240
|
+
it "should change the status and append the reason" do
|
241
|
+
expect { subject.unavailable!("withdrawn") }.to change(subject, :status).from(Identifier::PUBLIC).to("#{Identifier::UNAVAILABLE} | withdrawn")
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
describe "#public!" do
|
247
|
+
subject { described_class.new(id: "id", status: Identifier::UNAVAILABLE) }
|
248
|
+
it "should change the status" do
|
249
|
+
expect { subject.public! }.to change(subject, :status).from(Identifier::UNAVAILABLE).to(Identifier::PUBLIC)
|
250
|
+
end
|
187
251
|
end
|
188
252
|
end
|
189
253
|
|
data/spec/unit/metadata_spec.rb
CHANGED
@@ -33,6 +33,7 @@ module Ezid
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
36
|
+
|
36
37
|
describe "metadata profiles" do
|
37
38
|
Metadata::PROFILES.each do |profile, elements|
|
38
39
|
describe "the '#{profile}' metadata profile" do
|
@@ -65,6 +66,22 @@ module Ezid
|
|
65
66
|
end
|
66
67
|
end
|
67
68
|
|
69
|
+
describe "custom element" do
|
70
|
+
let(:element) { Metadata::Element.new("custom", true) }
|
71
|
+
before do
|
72
|
+
allow(subject.registered_elements).to receive(:include?).with(:custom) { true }
|
73
|
+
allow(subject.registered_elements).to receive(:[]).with(:custom) { element }
|
74
|
+
end
|
75
|
+
it "should have a reader" do
|
76
|
+
expect(subject).to receive(:reader).with("custom")
|
77
|
+
subject.custom
|
78
|
+
end
|
79
|
+
it "should have a writer" do
|
80
|
+
expect(subject).to receive(:writer).with("custom", "value")
|
81
|
+
subject.custom = "value"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
68
85
|
describe "ANVL output" do
|
69
86
|
let(:elements) do
|
70
87
|
{ "_target" => "http://example.com/path%20with%20spaces",
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ezid-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Chandek-Stark
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-12-
|
11
|
+
date: 2014-12-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|