ezid-client 0.9.1 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|