diff --git a/.travis.yml b/.travis.yml index 6d4a109..9003b4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ rvm: - 2.3.1 - 2.2 - 2.1 - - 2.0.0 cache: - bundler script: "bundle exec rake ci" diff --git a/README.md b/README.md index d546135..347a9fc 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,11 @@ Or install it yourself as: >> identifier = Ezid::Identifier.mint("ark:/99999/fk4") I, [2016-03-01T22:20:08.505323 #35148] INFO -- : EZID MintIdentifier -- success: ark:/99999/fk4tq65d6k => # + >> identifier.status I, [2016-03-01T22:20:22.323650 #35148] INFO -- : EZID GetIdentifierMetadata -- success: ark:/99999/fk4tq65d6k => "public" + >> identifier.target => "http://ezid.cdlib.org/id/ark:/99999/fk4tq65d6k" ``` @@ -87,11 +89,14 @@ I, [2014-12-04T15:07:00.648676 #86655] INFO -- : EZID GetIdentifierMetadata -- ``` >> identifier.target => "http://ezid.cdlib.org/id/ark:/99999/fk43f4wd4v" + >> identifier.target = "http://example.com" => "http://example.com" + >> identifier.save I, [2014-12-09T11:24:26.321801 #32279] INFO -- : EZID ModifyIdentifier -- success: ark:/99999/fk43f4wd4v => # + >> identifier.target I, [2014-12-09T11:24:27.039288 #32279] INFO -- : EZID GetIdentifierMetadata -- success: ark:/99999/fk43f4wd4v => "http://example.com" @@ -107,6 +112,7 @@ I, [2014-12-09T11:24:27.039288 #32279] INFO -- : EZID GetIdentifierMetadata -- >> identifier = Ezid::Identifier.mint("ark:/99999/fk4", status: "reserved") I, [2016-03-01T22:26:08.645858 #36701] INFO -- : EZID MintIdentifier -- success: ark:/99999/fk4pz5fm1b => # + >> identifier.delete I, [2016-03-01T22:26:14.829731 #36701] INFO -- : EZID GetIdentifierMetadata -- success: ark:/99999/fk4pz5fm1b I, [2016-03-01T22:26:15.711390 #36701] INFO -- : EZID DeleteIdentifier -- success: ark:/99999/fk4pz5fm1b @@ -120,22 +126,57 @@ See http://ezid.cdlib.org/doc/apidoc.html#parameters. Repeated values should be *Added in v1.3.0:* `Ezid::BatchDownload` class. ``` ->> batch = Ezid::BatchDownload.new(:csv) +>> batch_download = Ezid::BatchDownload.new(:csv) => # ->> batch.column = ["_id", "_target"] + +>> batch_download.column = ["_id", "_target"] => ["_id", "_target"] ->> batch.createdAfter = Date.today.to_time + +>> batch_download.createdAfter = Date.today.to_time => 2016-02-24 00:00:00 -0500 ->> batch + +>> batch_download => # ->> batch.download_url + +>> batch_download.url I, [2016-02-24T18:03:40.828005 #1084] INFO -- : EZID BatchDownload -- success: http://ezid.cdlib.org/download/4a63401e17.csv.gz => "http://ezid.cdlib.org/download/4a63401e17.csv.gz" ->> batch.download_file -File successfully download to /current/working/directory/4a63401e17.csv.gz. - => nil + +>> batch_download.file + => /current/working/directory/4a63401e17.csv.gz ``` +## Batch + +*Added in v1.6.0.* `Ezid::Batch` class. + +**Version 1.7.0 upgrade note:** This class was originally named `Ezid::BatchEnumerator`, but it is not a Ruby enumerator. `Ezid::Batch` is the new name with the same API. + +``` +>> require 'ezid/batch' + => true + +>> batch = Ezid::Batch.new(:anvl, "spec/fixtures/anvl_batch.txt") + => # + +>> id = batch.first + => # + +>> id.target + => "http://example.com" + +>> puts id.metadata +_updated: 1488227717 +_target: http://example.com +_profile: erc +_ownergroup: apitest +_owner: apitest +_export: yes +_created: 1488227717 +_status: public + => nil +``` + ## Metadata handling Accessors are provided to ease the use of 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): @@ -145,6 +186,7 @@ Accessors are provided to ease the use of EZID [reserved metadata elements](http ``` >> identifier.status # reads "_status" element => "public" + >> identifier.status = "unavailable" # writes "_status" element => "unavailable" ``` @@ -159,6 +201,7 @@ Notes: ``` >> identifier.dc_type # reads "dc.type" element => "Collection" + >> identifier.dc_type = "Image" # writes "dc.type" element => "Image" ``` diff --git a/VERSION b/VERSION index dc1e644..bd8bf88 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.0 +1.7.0 diff --git a/ezid-client.gemspec b/ezid-client.gemspec index e510ce0..7b63153 100644 --- a/ezid-client.gemspec +++ b/ezid-client.gemspec @@ -17,12 +17,12 @@ Gem::Specification.new do |spec| spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] - spec.required_ruby_version = "~> 2.0" + spec.required_ruby_version = "~> 2.1" spec.add_dependency "hashie", "~> 3.4", ">= 3.4.3" spec.add_development_dependency "bundler", "~> 1.7" - spec.add_development_dependency "rake", "~> 10.5" + spec.add_development_dependency "rake" spec.add_development_dependency "rspec", "~> 3.4" spec.add_development_dependency "rspec-its", "~> 1.2" end diff --git a/lib/ezid/batch_enumerator.rb b/lib/ezid/batch.rb similarity index 89% rename from lib/ezid/batch_enumerator.rb rename to lib/ezid/batch.rb index f7608ba..4829fc9 100644 --- a/lib/ezid/batch_enumerator.rb +++ b/lib/ezid/batch.rb @@ -1,5 +1,5 @@ module Ezid - class BatchEnumerator + class Batch include Enumerable attr_reader :format, :batch_file @@ -25,7 +25,7 @@ def each_anvl(&block) while record = f.gets("") head, metadata = record.split(/\n/, 2) id = head.sub(/\A::/, "").strip - yield Ezid::Identifier.new(id, metadata: metadata) + yield Identifier.load(id, metadata) end end end diff --git a/lib/ezid/batch_download.rb b/lib/ezid/batch_download.rb index 5cd9847..c569ed1 100644 --- a/lib/ezid/batch_download.rb +++ b/lib/ezid/batch_download.rb @@ -84,6 +84,8 @@ def download_url get_response.download_url end + alias_method :url, :download_url + def download_file(path: nil) path ||= Dir.getwd fullpath = File.directory?(path) ? File.join(path, download_filename) : path @@ -123,6 +125,8 @@ def download_file(path: nil) fullpath end + alias_method :file, :download_file + private def download_uri diff --git a/lib/ezid/identifier.rb b/lib/ezid/identifier.rb index e65d669..b8431fc 100644 --- a/lib/ezid/identifier.rb +++ b/lib/ezid/identifier.rb @@ -65,10 +65,25 @@ def mint(*args) # @return [Ezid::Identifier] the identifier # @raise [Ezid::IdentifierNotFoundError] def modify(id, metadata) - i = allocate - i.id = id - i.update_metadata(metadata) - i.modify! + allocate.tap do |i| + i.id = id + i.update_metadata(metadata) + i.modify! + end + end + + # Loads an identifier with provided remote metadata + # The main purpose is to provide an API in a batch processing + # context to instantiate Identifiers from a BatchDownload. + # @see #load_metadata! + # @param id [String] the EZID identifier + # @param metadata [String, Hash, Ezid::Metadata] the provided metadata + # @return [Ezid::Identifier] the identifier + def load(id, metadata = nil) + allocate.tap do |i| + i.id = id + i.load_metadata!(metadata) + end end # Retrieves an identifier @@ -76,9 +91,10 @@ def modify(id, metadata) # @return [Ezid::Identifier] the identifier # @raise [Ezid::IdentifierNotFoundError] if the identifier does not exist in EZID def find(id) - i = allocate - i.id = id - i.load_metadata + allocate.tap do |i| + i.id = id + i.load_metadata + end end end @@ -132,9 +148,14 @@ def to_s # @return [Ezid::Metadata] the metadata def metadata(_=nil) if !_.nil? - warn "[DEPRECATION] The parameter of `metadata` is deprecated and will be removed in 2.0. (called from #{caller.first})" + warn "[DEPRECATION] The parameter of `metadata` is ignored and will be removed in 2.0. " \ + "(called from #{caller.first})" end - @metadata ||= Metadata.new + remote_metadata.merge(local_metadata).freeze + end + + def local_metadata + @local_metadata ||= Metadata.new end def remote_metadata @@ -172,7 +193,7 @@ def modify! # @param attrs [Hash] the metadata # @return [Ezid::Identifier] the identifier def update_metadata(attrs={}) - metadata.update(attrs) + local_metadata.update(attrs) self end @@ -203,13 +224,25 @@ def reload load_metadata end - # Loads the metadata from EZID + # Loads the metadata from EZID and marks the identifier as persisted. # @return [Ezid::Identifier] the identifier - # @raise [Ezid::Error] + # @raise [Ezid::Error] the identifier is not found or other error. def load_metadata response = client.get_identifier_metadata(id) - # self.remote_metadata = Metadata.new(response.metadata) - remote_metadata.replace(response.metadata) + load_remote_metadata(response.metadata) + persists! + self + end + + # Loads provided metadata and marks the identifier as persisted. + # The main purpose is to provide an API in a batch processing + # context to instantiate Identifiers from a BatchDownload. + # @see Ezid::BatchEnumerator + # @see .load + # @param metadata [String, Hash, Ezid::Metadata] the provided metadata + # @return [Ezid::Identifier] the identifier + def load_metadata!(metadata) + load_remote_metadata(metadata) persists! self end @@ -287,7 +320,7 @@ def client end def reset_metadata - metadata.clear unless metadata.empty? + local_metadata.clear unless local_metadata.empty? remote_metadata.clear unless remote_metadata.empty? end @@ -302,7 +335,7 @@ def method_missing(*args) private def local_or_remote_metadata(*args) - value = metadata.send(*args) + value = local_metadata.send(*args) if value.nil? && persisted? load_metadata if remote_metadata.empty? value = remote_metadata.send(*args) @@ -311,7 +344,7 @@ def local_or_remote_metadata(*args) end def modify - client.modify_identifier(id, metadata) + client.modify_identifier(id, local_metadata) end def create_or_mint @@ -319,12 +352,12 @@ def create_or_mint end def mint - response = client.mint_identifier(shoulder, metadata) + response = client.mint_identifier(shoulder, local_metadata) self.id = response.id end def create - client.create_identifier(id, metadata) + client.create_identifier(id, local_metadata) end def persist @@ -340,5 +373,9 @@ def apply_default_metadata update_metadata(self.class.defaults) end + def load_remote_metadata(metadata) + remote_metadata.replace(metadata) + end + end end diff --git a/spec/integration/batch_download_spec.rb b/spec/integration/batch_download_spec.rb index a7f91ac..47b2283 100644 --- a/spec/integration/batch_download_spec.rb +++ b/spec/integration/batch_download_spec.rb @@ -1,18 +1,18 @@ require 'tempfile' module Ezid - RSpec.describe BatchDownload do + RSpec.describe BatchDownload, integration: true do subject do a_week_ago = (Time.now - (7*24*60*60)).to_i described_class.new(:anvl, compression: "zip", permanence: "test", status: "public", createdAfter: a_week_ago) end - its(:download_url) { is_expected.to match(/\Ahttp:\/\/ezid\.cdlib\.org\/download\/\w+\.zip\z/) } - specify { + expect(subject.download_url).to match(/\Ahttp:\/\/ezid\.cdlib\.org\/download\/\w+\.zip\z/) + expect(subject.url).to match(/\Ahttp:\/\/ezid\.cdlib\.org\/download\/\w+\.zip\z/) Dir.mktmpdir do |tmpdir| - expect(subject.download_file(path: tmpdir)) + expect(subject.file(path: tmpdir)) .to match(/\A#{tmpdir}\/\w+\.zip\z/) end } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 65c4903..a57c6f4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -76,7 +76,7 @@ # Print the 10 slowest examples and example groups at the # end of the spec run, to help surface which specs are running # particularly slow. - config.profile_examples = 5 + #config.profile_examples = 5 # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing diff --git a/spec/unit/batch_enumerator_spec.rb b/spec/unit/batch_spec.rb similarity index 90% rename from spec/unit/batch_enumerator_spec.rb rename to spec/unit/batch_spec.rb index 19885aa..25f0980 100644 --- a/spec/unit/batch_enumerator_spec.rb +++ b/spec/unit/batch_spec.rb @@ -1,7 +1,7 @@ -require 'ezid/batch_enumerator' +require 'ezid/batch' module Ezid - RSpec.describe BatchEnumerator do + RSpec.describe Batch do let(:batch_file) { File.expand_path("../../fixtures/anvl_batch.txt", __FILE__) } diff --git a/spec/unit/identifier_spec.rb b/spec/unit/identifier_spec.rb index 5299bf6..a3c57f8 100644 --- a/spec/unit/identifier_spec.rb +++ b/spec/unit/identifier_spec.rb @@ -1,6 +1,39 @@ module Ezid RSpec.describe Identifier do + describe "class methods" do + + describe ".load" do + subject { described_class.load("ark:/99999/fk4086hs23", metadata) } + describe "with ANVL metadata" do + let(:metadata) do + <<-EOS +_updated: 1488227717 +_target: http://example.com +_profile: erc +_ownergroup: apitest +_owner: apitest +_export: yes +_created: 1488227717 +_status: public + EOS + end + its(:remote_metadata) { + is_expected.to eq({"_updated"=>"1488227717", + "_target"=>"http://example.com", + "_profile"=>"erc", + "_ownergroup"=>"apitest", + "_owner"=>"apitest", + "_export"=>"yes", + "_created"=>"1488227717", + "_status"=>"public"}) + } + end + describe "with nil" do + let(:metadata) { nil } + its(:remote_metadata) { is_expected.to be_empty } + end + end describe ".create" do describe "with id and metadata args" do it "instantiates a new Identifier and saves it" do @@ -63,6 +96,7 @@ module Ezid end describe "instance methods" do + describe "#initialize" do before { allow(described_class).to receive(:defaults) { defaults } @@ -129,7 +163,7 @@ module Ezid its(:client) { is_expected.to_not eq(client) } end end - end + end # initialize describe "#update" do let(:metadata) { {"status" => "unavailable"} } @@ -172,12 +206,23 @@ module Ezid end describe "#load_metadata" do + subject { described_class.new("id") } let(:metadata) { "_profile: erc" } - before { allow(subject).to receive(:id) { "id" } } it "replaces the remote metadata with metadata from EZID" do expect(subject.client).to receive(:get_identifier_metadata).with("id") { double(id: "id", metadata: metadata) } - expect(subject.remote_metadata).to receive(:replace).with(metadata) subject.load_metadata + expect(subject.remote_metadata).to eq({"_profile"=>"erc"}) + expect(subject).to be_persisted + end + end + + describe "#load_metadata!" do + subject { described_class.new("id") } + let(:metadata) { "_profile: erc" } + it "replaces the remote metadata with the provided metadata" do + subject.load_metadata!(metadata) + expect(subject.remote_metadata).to eq({"_profile"=>"erc"}) + expect(subject).to be_persisted end end @@ -323,23 +368,11 @@ module Ezid context "when the status is \"unavailable\"" do let(:status) { "#{Status::UNAVAILABLE} | whatever" } context "and no reason is given" do - it "logs a warning" do - pending "https://github.com/duke-libraries/ezid-client/issues/46" - allow_message_expectations_on_nil - expect(subject.logger).to receive(:warn) - subject.unavailable! - end it "does not change the status" do expect { subject.unavailable! }.not_to change(subject, :status) end end context "and a reason is given" do - it "logs a warning" do - pending "https://github.com/duke-libraries/ezid-client/issues/46" - allow_message_expectations_on_nil - expect(subject.logger).to receive(:warn) - subject.unavailable!("because") - end it "should change the status" do expect { subject.unavailable!("because") }.to change(subject, :status).from(status).to("#{Status::UNAVAILABLE} | because") end @@ -382,5 +415,11 @@ module Ezid end end end + + describe "#metadata" do + it "is frozen" do + expect { subject.metadata["foo"] = "bar" }.to raise_error(RuntimeError) + end + end end end