Skip to content

Commit

Permalink
Merge pull request #5886 from samvera/valkyrie-versioning
Browse files Browse the repository at this point in the history
Update versioning service to support Valkyrie
  • Loading branch information
dlpierce authored Oct 11, 2022
2 parents 09aa789 + 564c60b commit dfd92a5
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 36 deletions.
2 changes: 1 addition & 1 deletion app/presenters/hyrax/version_list_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def self.for(file_set:)
else
Hyrax::FileSetFileService.new(file_set: file_set).original_file
end
new(original_file&.versions&.all.to_a)
new(Hyrax::VersioningService.new(resource: original_file).versions)
rescue NoMethodError
raise ArgumentError
end
Expand Down
86 changes: 77 additions & 9 deletions app/services/hyrax/versioning_service.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,80 @@
# frozen_string_literal: true

module Hyrax
##
# Provides methods for dealing with versions of files across both ActiveFedora
# and Valkyrie.
#
# Note that many of the methods pertaining to version creation are currently
# implemented as static methods.
class VersioningService
##
# @!attribute [rw] resource
# @return [ActiveFedora::File | Hyrax::FileMetadata | NilClass]
attr_accessor :resource

##
# @!attribute [r] storage_adapter
# @return [#supports?]
attr_reader :storage_adapter

##
# @param resource [ActiveFedora::File | Hyrax::FileMetadata | NilClass]
def initialize(resource:, storage_adapter: Hyrax.storage_adapter)
@storage_adapter = storage_adapter
self.resource = resource
end

##
# Returns an array of versions for the resource associated with this
# Hyrax::VersioningService.
#
# If the resource is nil, or if it is a Hyrax::FileMetadata and versioning
# is not supported in the storage adapter, an empty array will be returned.
def versions
if resource.nil?
[]
elsif resource.is_a?(Hyrax::FileMetadata)
if storage_adapter.try(:"supports?", :versions)
storage_adapter.find_versions(id: resource.file_identifier).to_a
else
[]
end
else
resource.versions.all.to_a
end
end

##
# Returns the latest version of the file associated with this
# Hyrax::VersioningService.
def latest_version
versions.last
end

##
# Returns the file ID of the latest version of the file associated with this
# Hyrax::VersioningService, or the ID of the file resource itself if no
# latest version is defined.
#
# If the resource is nil, this method returns an empty string.
def versioned_file_id
latest = latest_version
if latest
if latest.respond_to?(:id)
latest.id
else
Hyrax.config.translate_uri_to_id.call(latest.uri)
end
elsif resource.nil?
""
elsif resource.is_a?(Hyrax::FileMetadata)
resource.file_identifier
else
resource.id
end
end

class << self
# Make a version and record the version committer
# @param [ActiveFedora::File | Hyrax::FileMetadata] content
Expand All @@ -11,19 +84,14 @@ def create(content, user = nil)
perform_create(content, user, use_valkyrie)
end

# @param [ActiveFedora::File | Hyrax::FileMetadata] content
# @param [ActiveFedora::File | Hyrax::FileMetadata] file
def latest_version_of(file)
file.versions.last
Hyrax::VersioningService.new(resource: file).latest_version
end

# @param [ActiveFedora::File | Hyrax::FileMetadata] content
# @param [ActiveFedora::File | Hyrax::FileMetadata] file
def versioned_file_id(file)
versions = file.versions.all
if versions.present?
Hyrax.config.translate_uri_to_id.call(versions.last.uri)
else
file.id
end
Hyrax::VersioningService.new(resource: file).versioned_file_id
end

# Record the version committer of the last version
Expand Down
8 changes: 6 additions & 2 deletions lib/wings/valkyrie/storage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def find_versions(id:)
version_graph.query([uri, RDF::Vocab::Fcrepo4.created, :created])
.first_object
.object
Version.new(cast_to_valkyrie_id(uri.to_s), timestamp, self)
Version.new(id: cast_to_valkyrie_id(uri.to_s), created: timestamp, adapter: self)
end.sort
end

Expand All @@ -86,7 +86,7 @@ def find_versions(id:)
# this implementation uses an orderable {#version_token}. in practice
# the token is the fcrepo created date for the version, as extracted from
# the versions graph.
Version = Struct.new(:id, :version_token, :adapter) do
Version = Struct.new("Version", :id, :created, :adapter, keyword_init: true) do
include Comparable

##
Expand All @@ -95,6 +95,10 @@ def io
adapter.find_by(id: id)
end

def version_token
created
end

def <=>(other)
raise ArgumentError unless other.respond_to?(:version_token)
version_token <=> other.version_token
Expand Down
175 changes: 151 additions & 24 deletions spec/services/hyrax/versioning_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,172 @@
let(:user) { build(:user) }
let(:file) { create(:file_set) }

before do
# Add the original_file (this service creates a version after saving when you call it with versioning: true)
Hydra::Works::AddFileToFileSet.call(file, File.open(fixture_path + '/world.png'), :original_file, versioning: true)
end
describe 'using ActiveFedora' do
before do
# Add the original_file (this service creates a version after saving when you call it with versioning: true)
Hydra::Works::AddFileToFileSet.call(file, File.open(fixture_path + '/world.png'), :original_file, versioning: true)
end

describe '#versions' do
subject do
described_class.new(resource: file.original_file).versions.map do |v|
Hyrax.config.translate_uri_to_id.call(v.uri)
end
end

context 'without version data' do
before do
allow(file.original_file).to receive(:has_versions?).and_return(false)
end
it { is_expected.to eq [] }
end

describe '#versioned_file_id' do
subject { described_class.versioned_file_id file.original_file }
context 'with one version' do
it { is_expected.to eq ["#{file.original_file.id}/fcr:versions/version1"] }
end

context 'without version data' do
before do
allow(file.original_file).to receive(:has_versions?).and_return(false)
context 'with two versions' do
before do
file.original_file.create_version
end
it {
is_expected.to eq [
"#{file.original_file.id}/fcr:versions/version1",
"#{file.original_file.id}/fcr:versions/version2"
]
}
end
it { is_expected.to eq file.original_file.id }
end

context 'with one version' do
it { is_expected.to eq "#{file.original_file.id}/fcr:versions/version1" }
describe '.versioned_file_id' do
subject { described_class.versioned_file_id file.original_file }

context 'without version data' do
before do
allow(file.original_file).to receive(:has_versions?).and_return(false)
end
it { is_expected.to eq file.original_file.id }
end

context 'with one version' do
it { is_expected.to eq "#{file.original_file.id}/fcr:versions/version1" }
end

context 'with two versions' do
before do
file.original_file.create_version
end
it { is_expected.to eq "#{file.original_file.id}/fcr:versions/version2" }
end
end

context 'with two versions' do
before do
file.original_file.create_version
describe '.latest_version_of' do
subject { described_class.latest_version_of(file.original_file).label }

context 'with one version' do
it { is_expected.to eq 'version1' }
end

context 'with two versions' do
before do
file.original_file.create_version
end
it { is_expected.to eq 'version2' }
end
it { is_expected.to eq "#{file.original_file.id}/fcr:versions/version2" }
end
end

describe '#latest_version_of' do
subject { described_class.latest_version_of(file.original_file).label }
describe 'using valkyrie' do
let(:file) { fixture_file_upload('/world.png', 'image/png') }
let(:file_set) { FactoryBot.valkyrie_create(:hyrax_file_set) }
let(:query_service) { Hyrax.query_service }
let(:storage_adapter) { Hyrax.storage_adapter }
let(:uploaded) do
storage_adapter.upload(resource: file_set, file: file, original_filename: file.original_filename)
end
let(:file_metadata) { query_service.custom_queries.find_file_metadata_by(id: uploaded.id) }

describe '#versions' do
subject { described_class.new(resource: file_metadata).versions.map(&:id) }

context 'when versions are unsupported' do
before do
allow(storage_adapter).to receive(:supports?).and_return(false)
end
it { is_expected.to eq [] }
end

context 'without version data' do
before do
allow(storage_adapter).to receive(:supports?).and_return(true)
allow(storage_adapter).to receive(:find_versions).and_return([])
end
it { is_expected.to eq [] }
end

context 'with one version' do
it { is_expected.to eq ["#{uploaded.id}/fcr:versions/version1"] }
end

context 'with one version' do
it { is_expected.to eq 'version1' }
context 'with two versions' do
let(:another_file) { fixture_file_upload('/hyrax_generic_stub.txt') }
before do
storage_adapter.upload(resource: file_set, file: another_file, original_filename: 'filenew.txt')
end
it {
is_expected.to eq [
"#{uploaded.id}/fcr:versions/version1",
"#{uploaded.id}/fcr:versions/version2"
]
}
end
end

context 'with two versions' do
before do
file.original_file.create_version
describe '.versioned_file_id' do
subject { described_class.versioned_file_id file_metadata }

context 'when versions are unsupported' do
before do
allow(storage_adapter).to receive(:supports?).and_return(false)
end
it { is_expected.to eq uploaded.id }
end

context 'without version data' do
before do
allow(storage_adapter).to receive(:supports?).and_return(true)
allow(storage_adapter).to receive(:find_versions).and_return([])
end
it { is_expected.to eq uploaded.id }
end

context 'with one version' do
it { is_expected.to eq "#{uploaded.id}/fcr:versions/version1" }
end

context 'with two versions' do
let(:another_file) { fixture_file_upload('/hyrax_generic_stub.txt') }
before do
storage_adapter.upload(resource: file_set, file: another_file, original_filename: 'filenew.txt')
end
it { is_expected.to eq "#{uploaded.id}/fcr:versions/version2" }
end
end

describe '.latest_version_of' do
subject { described_class.latest_version_of(file_metadata).id.to_s.split('/').last }

context 'with one version' do
it { is_expected.to eq 'version1' }
end

context 'with two versions' do
let(:another_file) { fixture_file_upload('/hyrax_generic_stub.txt') }
before do
storage_adapter.upload(resource: file_set, file: another_file, original_filename: 'filenew.txt')
end
it { is_expected.to eq 'version2' }
end
it { is_expected.to eq 'version2' }
end
end
end

0 comments on commit dfd92a5

Please sign in to comment.