valkyrie 3.4.0 → 3.5.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: a4fcef8213a3e959929dbdb15bffd20c70bf3a5a07ff977a4937271d137d51a7
4
- data.tar.gz: 875ced504589316a4d837fbd4d2d8bfe2e590d0e5f5f3c792edefe1461575a25
3
+ metadata.gz: '019ff2935ad45b095aa1a480fa630b3acd45486ba398fcc33b881c76f94856c9'
4
+ data.tar.gz: 6d5be6b5d178daaef17769f561fd2d3bc2d6795ea8d33bd76ccf038772b290c5
5
5
  SHA512:
6
- metadata.gz: 98945b1fdd7f8cce636eea9abf5fc44c05d8291d1955b22ff91e218e12b81f4b3b617f52d0c846b179e040f623665a62027c60de2b50c50d7b027a7008d78e12
7
- data.tar.gz: 844db5f6507ff3678ffe3c253a29dedd7753d26da6495ffcf51e2a275e6175fc9a904361a5724ec385330366eb84eaf13acf66db014bc783201e2e06e11168fb
6
+ metadata.gz: '08ca3c0f78259f19bbf327363177c0560926d791e832cff00a6707540f00051ee02b1e2aade681104aa90140e1aa5c268a43686df6d49cc6d2545381362c9088'
7
+ data.tar.gz: 7edd93eb4e765b35f458cf1c6d372103249d191bd580f78e96f4a7e5c2f7012071371eb5bfc4315121d448fa17ecb7b09a76c58cf0658a0859a4ca654b6d2c3a
data/.circleci/config.yml CHANGED
@@ -24,11 +24,7 @@ jobs:
24
24
  environment:
25
25
  CATALINA_OPTS: "-Djava.awt.headless=true -Dfile.encoding=UTF-8 -server -Xms512m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:PermSize=256m -XX:MaxPermSize=256m -XX:+DisableExplicitGC"
26
26
  JAVA_OPTIONS: "-Djetty.http.port=8988"
27
- - image: samvera/fcrepo4:5.1.0
28
- environment:
29
- CATALINA_OPTS: "-Djava.awt.headless=true -Dfile.encoding=UTF-8 -server -Xms512m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:PermSize=256m -XX:MaxPermSize=256m -XX:+DisableExplicitGC"
30
- JAVA_OPTIONS: "-Djetty.http.port=8998 -Dfcrepo.dynamic.jms.port=61618 -Dfcrepo.dynamic.stomp.port=61614"
31
- - image: fcrepo/fcrepo:6.4.0
27
+ - image: fcrepo/fcrepo:6.5.1-RC3-tomcat9
32
28
  environment:
33
29
  CATALINA_OPTS: "-Djava.awt.headless=true -Dfile.encoding=UTF-8 -server -Xms512m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:PermSize=256m -XX:MaxPermSize=256m -XX:+DisableExplicitGC -Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true"
34
30
  JAVA_OPTS: "-Djetty.http.port=8978 -Dfcrepo.dynamic.jms.port=61619 -Dfcrepo.dynamic.stomp.port=61615 -Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true"
@@ -65,7 +61,7 @@ jobs:
65
61
  - run: gem install bundler -v '~> 2.0'
66
62
  - restore_cache:
67
63
  keys:
68
- - bundle-{{ checksum "<< parameters.gemfile >>" }}-{{ checksum "valkyrie.gemspec" }}-<< parameters.ruby >>-6
64
+ - bundle-{{ checksum "<< parameters.gemfile >>" }}-{{ checksum "valkyrie.gemspec" }}-<< parameters.ruby >>-7
69
65
  - run: sudo apt update -y && sudo apt-get install -y libpq-dev lsof
70
66
  - run:
71
67
  name: Set BUNDLE_GEMFILE
@@ -75,7 +71,7 @@ jobs:
75
71
  name: Install dependencies
76
72
  command: bundle install --path=vendor/bundle --jobs 4 --retry 3
77
73
  - save_cache:
78
- key: bundle-{{ checksum "<< parameters.gemfile >>" }}-{{ checksum "valkyrie.gemspec" }}-<< parameters.ruby >>-6
74
+ key: bundle-{{ checksum "<< parameters.gemfile >>" }}-{{ checksum "valkyrie.gemspec" }}-<< parameters.ruby >>-7
79
75
  paths:
80
76
  - "vendor/bundle"
81
77
  - "gemfiles/vendor/bundle"
@@ -103,10 +99,6 @@ workflows:
103
99
  gemfile: "gemfiles/activerecord_7_1.gemfile"
104
100
  ruby: 3.3.5
105
101
  name: "Ruby3-3_rails7-1"
106
- - build:
107
- gemfile: "gemfiles/activerecord_7_0.gemfile"
108
- ruby: 3.3.5
109
- name: "Ruby3-3_rails7-0"
110
102
  - build:
111
103
  gemfile: "gemfiles/activerecord_7_2.gemfile"
112
104
  ruby: 3.2.5
@@ -115,10 +107,6 @@ workflows:
115
107
  gemfile: "gemfiles/activerecord_7_1.gemfile"
116
108
  ruby: 3.2.5
117
109
  name: "Ruby3-2_rails7-1"
118
- - build:
119
- gemfile: "gemfiles/activerecord_7_0.gemfile"
120
- ruby: 3.2.5
121
- name: "Ruby3-2_rails7-0"
122
110
  - build:
123
111
  gemfile: "gemfiles/activerecord_7_2.gemfile"
124
112
  ruby: 3.1.6
@@ -127,10 +115,6 @@ workflows:
127
115
  gemfile: "gemfiles/activerecord_7_1.gemfile"
128
116
  ruby: 3.1.6
129
117
  name: "Ruby3-1_rails7-1"
130
- - build:
131
- gemfile: "gemfiles/activerecord_7_0.gemfile"
132
- ruby: 3.1.6
133
- name: "Ruby3-1_rails7-0"
134
118
  - build:
135
119
  gemfile: "gemfiles/faraday_1.gemfile"
136
120
  ruby: 3.1.6
@@ -145,6 +129,10 @@ workflows:
145
129
  only:
146
130
  - master
147
131
  jobs:
132
+ - build:
133
+ gemfile: "gemfiles/activerecord_8_0.gemfile"
134
+ ruby: 3.3.5
135
+ name: "Ruby3-3_rails8-0"
148
136
  - build:
149
137
  gemfile: "gemfiles/activerecord_7_2.gemfile"
150
138
  ruby: 3.3.5
@@ -153,10 +141,6 @@ workflows:
153
141
  gemfile: "gemfiles/activerecord_7_1.gemfile"
154
142
  ruby: 3.3.5
155
143
  name: "Ruby3-3_rails7-1"
156
- - build:
157
- gemfile: "gemfiles/activerecord_7_0.gemfile"
158
- ruby: 3.3.5
159
- name: "Ruby3-3_rails7-0"
160
144
  - build:
161
145
  gemfile: "gemfiles/activerecord_7_2.gemfile"
162
146
  ruby: 3.2.5
@@ -177,10 +161,6 @@ workflows:
177
161
  gemfile: "gemfiles/activerecord_7_1.gemfile"
178
162
  ruby: 3.1.6
179
163
  name: "Ruby3-1_rails7-1"
180
- - build:
181
- gemfile: "gemfiles/activerecord_7_0.gemfile"
182
- ruby: 3.1.6
183
- name: "Ruby3-1_rails7-0"
184
164
  - build:
185
165
  gemfile: "gemfiles/faraday_1.gemfile"
186
166
  ruby: 3.1.6
data/.gitignore CHANGED
@@ -9,3 +9,4 @@
9
9
  /tmp/
10
10
  /vendor
11
11
  /gemfiles/*.lock
12
+ .byebug_history
data/.lando.yml CHANGED
@@ -27,31 +27,13 @@ services:
27
27
  environment:
28
28
  CATALINA_OPTS: "-Djava.awt.headless=true -Dfile.encoding=UTF-8 -server -Xms512m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:PermSize=256m -XX:MaxPermSize=256m -XX:+DisableExplicitGC"
29
29
  portforward: 8988
30
- valkyrie_fedora_5:
31
- type: compose
32
- app_mount: false
33
- volumes:
34
- fedora5:
35
- services:
36
- image: fcrepo/fcrepo:5.1.1-multiplatform
37
- command:
38
- - "catalina.sh"
39
- - "run"
40
- volumes:
41
- - fedora5:/data
42
- ports:
43
- - 8998:8080
44
- environment:
45
- CATALINA_OPTS: "-Djava.awt.headless=true -Dfile.encoding=UTF-8 -server -Xms512m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:PermSize=256m -XX:MaxPermSize=256m -XX:+DisableExplicitGC -Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true"
46
- JAVA_OPTS: "-Dfcrepo.dynamic.jms.port=61620 -Dfcrepo.dynamic.stomp.port=61617 -Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true"
47
- portforward: true
48
30
  valkyrie_fedora_6:
49
31
  type: compose
50
32
  app_mount: false
51
33
  volumes:
52
34
  fedora6:
53
35
  services:
54
- image: fcrepo/fcrepo:6.4.0
36
+ image: fcrepo/fcrepo:6.5.1-RC3-tomcat9
55
37
  command:
56
38
  - "catalina.sh"
57
39
  - "run"
data/Appraisals CHANGED
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ appraise "activerecord-8-0" do
4
+ gem "activerecord", "~> 8.0.0"
5
+ end
6
+
3
7
  appraise "activerecord-7-2" do
4
8
  gem "activerecord", "~> 7.2.0"
5
9
  end
@@ -8,11 +12,7 @@ appraise "activerecord-7-1" do
8
12
  gem "activerecord", "~> 7.1.0"
9
13
  end
10
14
 
11
- appraise "activerecord-7-0" do
12
- gem "activerecord", "~> 7.0.0"
13
- end
14
-
15
15
  appraise "faraday-1" do
16
16
  gem "faraday", "~> 1.0"
17
- gem "activerecord", "~> 7.0.0"
17
+ gem "activerecord", "~> 7.1.0"
18
18
  end
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ # v3.5.1 2026-01-16
2
+
3
+ * Remove Fedora 5. ([tpendragon](https://github.com/tpendragon))
4
+ * Limit LSOF command to the current process ID ([hackartisan](https://github.com/hackartisan))
5
+
6
+ # v3.5.0 2024-12-10
7
+
8
+ * Provides new boolean configuration setting `auto_cast_iso8601_as_datetime` ([dchandekstark](https://github.com/dchandekstark))
9
+ * Align behavior of server managed properties with Fedora. ([dlpierce](https://github.com/dlpierce))
10
+ * Check for nil id to avoid InvalidURIError ([dlpierce](https://github.com/dlpierce))
11
+ * Support Fedora 6.5 ([dlpierce](https://github.com/dlpierce))
12
+ * Make storage adapters report their protocol string ([dlpierce](https://github.com/dlpierce))
13
+
1
14
  # v3.4.0 2024-10-11
2
15
 
3
16
  ## Changes since last release
data/README.md CHANGED
@@ -224,14 +224,14 @@ custom adapters, but your application will still work.
224
224
  Using the shared specs in your own models is described in more detail on the [Shared Specs Wiki
225
225
  page](https://github.com/samvera/valkyrie/wiki/Shared-Specs).
226
226
 
227
- ### Fedora 5/6 Compatibility
227
+ ### Fedora 6 Compatibility
228
228
  When configuring your adapter, include the `fedora_version` parameter in your metadata or storage adapter
229
229
  config. If Fedora requires auth, you can also include that in the URL, e.g.:
230
230
 
231
231
  ```
232
232
  Valkyrie::Storage::Fedora.new(
233
233
  connection: Ldp::Client.new("http://fedoraAdmin:fedoraAdmin@localhost:8988/rest"),
234
- fedora_version: 5
234
+ fedora_version: 6
235
235
  )
236
236
  ```
237
237
  #### Pairtree paths in Fedora 4/6
@@ -3,7 +3,7 @@
3
3
 
4
4
  source "https://rubygems.org"
5
5
 
6
- gem "activerecord", "~> 7.0.0"
6
+ gem "activerecord", "~> 8.0.0"
7
7
  gem "ldp"
8
8
  gem "pg"
9
9
  gem "rsolr"
@@ -3,7 +3,7 @@
3
3
 
4
4
  source "https://rubygems.org"
5
5
 
6
- gem "activerecord", "~> 7.0.0"
6
+ gem "activerecord", "~> 7.1.0"
7
7
  gem "ldp"
8
8
  gem "pg"
9
9
  gem "rsolr"
@@ -105,7 +105,8 @@ module Valkyrie::Persistence::Fedora
105
105
  # @param [Property] value
106
106
  # @return [Boolean]
107
107
  def self.handles?(value)
108
- value.statement.object.to_s.start_with?("http://www.w3.org/ns/ldp", "http://fedora.info")
108
+ value.statement.predicate == RDF.type &&
109
+ value.statement.object.to_s.start_with?('http://www.w3.org/ns/ldp#', 'http://fedora.info/definitions/v4/repository#')
109
110
  end
110
111
 
111
112
  # Provide the NullApplicator Class for any Property in a deny listed namespace
@@ -562,7 +563,7 @@ module Valkyrie::Persistence::Fedora
562
563
  # @return [Array<String>]
563
564
  def denylist
564
565
  [
565
- "http://fedora.info/definitions",
566
+ "http://fedora.info/definitions/v4/repository#",
566
567
  "http://www.iana.org/assignments/relation/last"
567
568
  ]
568
569
  end
@@ -162,6 +162,7 @@ module Valkyrie::Persistence::Fedora
162
162
  # @return [Valkyrie::Resource]
163
163
  # @raise [Valkyrie::Persistence::ObjectNotFoundError]
164
164
  def resource_from_uri(uri)
165
+ raise ::Valkyrie::Persistence::ObjectNotFoundError if uri.nil?
165
166
  resource = Ldp::Resource.for(connection, uri, connection.get(uri))
166
167
  resource_factory.to_resource(object: resource)
167
168
  rescue ::Ldp::Gone, ::Ldp::NotFound
@@ -20,6 +20,6 @@ module Valkyrie::Persistence
20
20
  require 'valkyrie/persistence/fedora/ordered_reader'
21
21
  require 'valkyrie/persistence/fedora/list_node'
22
22
 
23
- DEFAULT_FEDORA_VERSION = 5
23
+ DEFAULT_FEDORA_VERSION = 6
24
24
  end
25
25
  end
@@ -9,12 +9,12 @@ module Valkyrie::Persistence::Postgres
9
9
  #
10
10
  # @see https://github.com/samvera-labs/valkyrie/wiki/Set-up-Valkyrie-database-in-a-Rails-Application
11
11
  class MetadataAdapter
12
- # @return [Class] {Valkyrie::Persistence::Postgres::Persister}
12
+ # @return [Valkyrie::Persistence::Postgres::Persister]
13
13
  def persister
14
14
  Valkyrie::Persistence::Postgres::Persister.new(adapter: self)
15
15
  end
16
16
 
17
- # @return [Class] {Valkyrie::Persistence::Postgres::QueryService}
17
+ # @return [Valkyrie::Persistence::Postgres::QueryService]
18
18
  def query_service
19
19
  @query_service ||= Valkyrie::Persistence::Postgres::QueryService.new(
20
20
  resource_factory: resource_factory,
@@ -22,7 +22,7 @@ module Valkyrie::Persistence::Postgres
22
22
  )
23
23
  end
24
24
 
25
- # @return [Class] {Valkyrie::Persistence::Postgres::ResourceFactory}
25
+ # @return [Valkyrie::Persistence::Postgres::ResourceFactory]
26
26
  def resource_factory
27
27
  @resource_factory ||= Valkyrie::Persistence::Postgres::ResourceFactory.new(adapter: self)
28
28
  end
@@ -139,6 +139,7 @@ module Valkyrie::Persistence::Shared
139
139
  # @return [Boolean]
140
140
  # rubocop:disable Metrics/CyclomaticComplexity
141
141
  def self.handles?(value)
142
+ return false unless ::Valkyrie.config.auto_cast_iso8601_as_datetime
142
143
  return false unless value.is_a?(String)
143
144
  return false unless value[4] == "-" && value[10] == "T"
144
145
  year = value.to_s[0..3]
@@ -86,8 +86,13 @@ RSpec.shared_examples 'a Valkyrie query provider' do
86
86
  expect { query_service.find_by(id: Valkyrie::ID.new("123123123")) }.to raise_error ::Valkyrie::Persistence::ObjectNotFoundError
87
87
  end
88
88
 
89
+ it 'returns a Valkyrie::Persistence::ObjectNotFoundError for an empty string' do
90
+ expect { query_service.find_by(id: '') }.to raise_error ::Valkyrie::Persistence::ObjectNotFoundError
91
+ end
92
+
89
93
  it 'raises an error if the id is not a Valkyrie::ID or a string' do
90
94
  expect { query_service.find_by(id: 123) }.to raise_error ArgumentError
95
+ expect { query_service.find_by(id: nil) }.to raise_error ArgumentError
91
96
  end
92
97
  end
93
98
 
@@ -117,8 +122,13 @@ RSpec.shared_examples 'a Valkyrie query provider' do
117
122
  expect { query_service.find_by_alternate_identifier(alternate_identifier: Valkyrie::ID.new("123123123")) }.to raise_error ::Valkyrie::Persistence::ObjectNotFoundError
118
123
  end
119
124
 
125
+ it "raises a Valkyrie::Persistence::ObjectNotFoundError for a non-found alternate identifier" do
126
+ expect { query_service.find_by_alternate_identifier(alternate_identifier: '') }.to raise_error ::Valkyrie::Persistence::ObjectNotFoundError
127
+ end
128
+
120
129
  it 'raises an error if the alternate identifier is not a Valkyrie::ID or a string' do
121
130
  expect { query_service.find_by_alternate_identifier(alternate_identifier: 123) }.to raise_error ArgumentError
131
+ expect { query_service.find_by_alternate_identifier(alternate_identifier: nil) }.to raise_error ArgumentError
122
132
  end
123
133
 
124
134
  it 'can have multiple alternate identifiers' do
@@ -12,6 +12,7 @@ RSpec.shared_examples 'a Valkyrie::StorageAdapter' do
12
12
  Valkyrie::Specs.send(:remove_const, :CustomResource)
13
13
  end
14
14
  subject { storage_adapter }
15
+ it { is_expected.to respond_to(:protocol) }
15
16
  it { is_expected.to respond_to(:handles?).with_keywords(:id) }
16
17
  it { is_expected.to respond_to(:find_by).with_keywords(:id) }
17
18
  it { is_expected.to respond_to(:delete).with_keywords(:id) }
@@ -63,19 +64,21 @@ RSpec.shared_examples 'a Valkyrie::StorageAdapter' do
63
64
  pre_open_files = open_files
64
65
  the_file = storage_adapter.find_by(id: uploaded_file.id)
65
66
  expect(the_file).to be_kind_of Valkyrie::StorageAdapter::File
66
- expect(pre_open_files.size).to eq open_files.size
67
+ expect(pre_open_files.size).to be <= open_files.size
67
68
  end
68
69
 
69
70
  def open_files
70
- `lsof +D .`.split("\n").map { |r| r.split("\t").last }
71
+ GC.start
72
+ `lsof -p #{Process.pid}`.split("\n").map { |r| r.split(/\s+/).last }
71
73
  end
72
74
 
73
75
  it "can upload, validate, re-fetch, and delete a file" do
74
76
  resource = Valkyrie::Specs::CustomResource.new(id: "test#{SecureRandom.uuid}")
75
77
  sha1 = Digest::SHA1.file(file).to_s
76
78
  size = file.size
77
- expect(uploaded_file = storage_adapter.upload(file: file, original_filename: 'foo.jpg', resource: resource, fake_upload_argument: true)).to be_kind_of Valkyrie::StorageAdapter::File
79
+ uploaded_file = storage_adapter.upload(file: file, original_filename: 'foo.jpg', resource: resource, fake_upload_argument: true)
78
80
 
81
+ expect(uploaded_file).to be_kind_of Valkyrie::StorageAdapter::File
79
82
  expect(uploaded_file).to respond_to(:checksum).with_keywords(:digests)
80
83
  expect(uploaded_file).to respond_to(:valid?).with_keywords(:size, :digests)
81
84
  expect(uploaded_file.checksum(digests: [Digest::SHA1.new])).to eq([sha1])
@@ -133,28 +136,42 @@ RSpec.shared_examples 'a Valkyrie::StorageAdapter' do
133
136
  # Deleting a version should leave the current versions
134
137
  if storage_adapter.supports?(:version_deletion)
135
138
  storage_adapter.delete(id: uploaded_file.version_id)
136
- expect(storage_adapter.find_versions(id: uploaded_file.id).length).to eq 1
139
+ remnants = storage_adapter.find_versions(id: uploaded_file.id)
140
+ expect(remnants.length).to eq 1
141
+ expect(remnants.first.version_id).to eq new_version.version_id
137
142
  expect { storage_adapter.find_by(id: uploaded_file.version_id) }.to raise_error Valkyrie::StorageAdapter::FileNotFound
138
143
  end
139
144
  current_length = storage_adapter.find_versions(id: new_version.id).length
140
145
 
141
146
  # Restoring a previous version is just pumping its file into upload_version
142
147
  newest_version = storage_adapter.upload_version(file: new_version, id: new_version.id)
148
+ current_length += 1
143
149
  expect(newest_version.version_id).not_to eq new_version.id
144
150
  expect(storage_adapter.find_by(id: newest_version.id).version_id).to eq newest_version.version_id
145
151
 
146
152
  # I can restore a version twice
147
153
  newest_version = storage_adapter.upload_version(file: new_version, id: new_version.id)
154
+ current_length += 1
148
155
  expect(newest_version.version_id).not_to eq new_version.id
149
156
  expect(storage_adapter.find_by(id: newest_version.id).version_id).to eq newest_version.version_id
150
- expect(storage_adapter.find_versions(id: newest_version.id).length).to eq current_length + 2
157
+ expect(storage_adapter.find_versions(id: newest_version.id).length).to eq current_length
158
+
159
+ # Fedora 6.5 may not create versions when the timestamp is the same?
160
+ # See: https://fedora-repository.atlassian.net/browse/FCREPO-3958
161
+ sleep 1 if storage_adapter.supports?(:list_deleted_versions)
151
162
 
152
163
  # NOTE: We originally wanted deleting the current record to push it into the
153
164
  # versions history, but FCRepo 4/5/6 doesn't work that way, so we changed to
154
165
  # instead make deleting delete everything.
155
166
  storage_adapter.delete(id: new_version.id)
156
167
  expect { storage_adapter.find_by(id: new_version.id) }.to raise_error Valkyrie::StorageAdapter::FileNotFound
157
- expect(storage_adapter.find_versions(id: new_version.id).length).to eq 0
168
+
169
+ if storage_adapter.supports?(:list_deleted_versions)
170
+ expect(storage_adapter.find_versions(id: new_version.id).length).to eq current_length
171
+ else
172
+ expect(storage_adapter.find_versions(id: new_version.id).length).to eq 0
173
+ end
174
+
158
175
  ensure
159
176
  f&.close
160
177
  end
@@ -3,6 +3,8 @@ module Valkyrie::Storage
3
3
  # Implements the DataMapper Pattern to store binary data on disk
4
4
  class Disk
5
5
  attr_reader :base_path, :path_generator, :file_mover
6
+ PROTOCOL = 'disk://'
7
+
6
8
  def initialize(base_path:, path_generator: BucketedStorage, file_mover: FileUtils.method(:mv))
7
9
  @base_path = Pathname.new(base_path.to_s)
8
10
  @path_generator = path_generator.new(base_path: base_path)
@@ -18,13 +20,13 @@ module Valkyrie::Storage
18
20
  new_path = path_generator.generate(resource: resource, file: file, original_filename: original_filename)
19
21
  FileUtils.mkdir_p(new_path.parent)
20
22
  file_mover.call(file.path, new_path)
21
- find_by(id: Valkyrie::ID.new("disk://#{new_path}"))
23
+ find_by(id: Valkyrie::ID.new("#{protocol}#{new_path}"))
22
24
  end
23
25
 
24
26
  # @param id [Valkyrie::ID]
25
27
  # @return [Boolean] true if this adapter can handle this type of identifer
26
28
  def handles?(id:)
27
- id.to_s.start_with?("disk://#{base_path}")
29
+ id.to_s.start_with?("#{protocol}#{base_path}")
28
30
  end
29
31
 
30
32
  # @param feature [Symbol] Feature to test for.
@@ -33,8 +35,13 @@ module Valkyrie::Storage
33
35
  false
34
36
  end
35
37
 
38
+ # @return [String] identifier prefix
39
+ def protocol
40
+ PROTOCOL
41
+ end
42
+
36
43
  def file_path(id)
37
- id.to_s.gsub(/^disk:\/\//, '')
44
+ id.to_s.delete_prefix(protocol)
38
45
  end
39
46
 
40
47
  # Return the file associated with the given identifier
@@ -27,7 +27,7 @@ module Valkyrie::Storage
27
27
  # @param id [Valkyrie::ID]
28
28
  # @return [Boolean] true if this adapter can handle this type of identifer
29
29
  def handles?(id:)
30
- id.to_s.start_with?(PROTOCOL)
30
+ id.to_s.start_with?(protocol)
31
31
  end
32
32
 
33
33
  # @param feature [Symbol] Feature to test for.
@@ -36,9 +36,16 @@ module Valkyrie::Storage
36
36
  return true if feature == :versions
37
37
  # Fedora 6 auto versions and you can't delete versions.
38
38
  return true if feature == :version_deletion && fedora_version < 6
39
+ # Fedora 6.5+ lists versions for deleted objects
40
+ return true if feature == :list_deleted_versions && fedora_version >= 6.5
39
41
  false
40
42
  end
41
43
 
44
+ # @return [String] identifier prefix
45
+ def protocol
46
+ PROTOCOL
47
+ end
48
+
42
49
  # Return the file associated with the given identifier
43
50
  # @param id [Valkyrie::ID]
44
51
  # @return [Valkyrie::StorageAdapter::StreamFile]
@@ -61,7 +68,7 @@ module Valkyrie::Storage
61
68
  # Fedora 6 auto versions, so check to see if there's a version for this
62
69
  # initial upload. If not, then mint one (fedora 4/5)
63
70
  version_id = current_version_id(id: valkyrie_identifier(uri: identifier)) || mint_version(identifier, latest_version(identifier))
64
- perform_find(id: Valkyrie::ID.new(identifier.to_s.sub(/^.+\/\//, PROTOCOL)), version_id: version_id)
71
+ perform_find(id: Valkyrie::ID.new(identifier.to_s.sub(/^.+\/\//, protocol)), version_id: version_id)
65
72
  end
66
73
 
67
74
  # @param id [Valkyrie::ID] ID of the Valkyrie::StorageAdapter::StreamFile to
@@ -77,7 +84,7 @@ module Valkyrie::Storage
77
84
  end
78
85
  upload_file(fedora_uri: uri, io: file)
79
86
  version_id = mint_version(uri, latest_version(uri))
80
- perform_find(id: Valkyrie::ID.new(uri.to_s.sub(/^.+\/\//, PROTOCOL)), version_id: version_id)
87
+ perform_find(id: Valkyrie::ID.new(uri.to_s.sub(/^.+\/\//, protocol)), version_id: version_id)
81
88
  end
82
89
 
83
90
  # @param id [Valkyrie::ID]
@@ -88,7 +95,9 @@ module Valkyrie::Storage
88
95
  version_list.map do |version|
89
96
  id = valkyrie_identifier(uri: version["@id"])
90
97
  perform_find(id: id, version_id: id)
91
- end
98
+ rescue Valkyrie::StorageAdapter::FileNotFound
99
+ nil
100
+ end.compact
92
101
  end
93
102
 
94
103
  # Delete the file in Fedora associated with the given identifier.
@@ -107,7 +116,7 @@ module Valkyrie::Storage
107
116
  if fedora_version == 4
108
117
  version_graph&.fetch("http://fedora.info/definitions/v4/repository#hasVersion", [])
109
118
  else
110
- # Fedora 5/6 use Memento.
119
+ # Fedora 6 uses Memento.
111
120
  version_graph&.fetch("http://www.w3.org/ns/ldp#contains", [])&.sort_by { |x| x["@id"] }&.reverse
112
121
  end
113
122
  end
@@ -167,12 +176,6 @@ module Valkyrie::Storage
167
176
  end
168
177
  # If there's a deletion marker, don't return anything. (Fedora 4)
169
178
  return nil if response.status == 410
170
- # This is awful, but versioning is locked to per-second increments,
171
- # returns a 409 in Fedora 5 if there's a conflict.
172
- if response.status == 409
173
- sleep(0.5)
174
- return mint_version(identifier, version_name)
175
- end
176
179
  raise "Version unable to be created" unless response.status == 201
177
180
  valkyrie_identifier(uri: response.headers["location"].gsub("/fcr:metadata", ""))
178
181
  end
@@ -197,12 +200,12 @@ module Valkyrie::Storage
197
200
  # Translate the Valkrie ID into a URL for the fedora file
198
201
  # @return [RDF::URI]
199
202
  def fedora_identifier(id:)
200
- identifier = id.to_s.sub(PROTOCOL, "#{connection.http.scheme}://")
203
+ identifier = id.to_s.sub(protocol, "#{connection.http.scheme}://")
201
204
  RDF::URI(identifier)
202
205
  end
203
206
 
204
207
  def valkyrie_identifier(uri:)
205
- id = uri.to_s.sub("http://", PROTOCOL)
208
+ id = uri.to_s.sub("http://", protocol)
206
209
  Valkyrie::ID.new(id)
207
210
  end
208
211
 
@@ -211,7 +214,7 @@ module Valkyrie::Storage
211
214
  # @return [IOProxy]
212
215
  def response(id:)
213
216
  response = connection.http.get(fedora_identifier(id: id))
214
- raise Valkyrie::StorageAdapter::FileNotFound unless response.success?
217
+ raise Valkyrie::StorageAdapter::FileNotFound, "HTTP #{response.status} #{response.body}" unless response.success?
215
218
  IOProxy.new(response.body)
216
219
  end
217
220
 
@@ -6,6 +6,8 @@ module Valkyrie::Storage
6
6
  # in cases where you want to preserve real data
7
7
  class Memory
8
8
  attr_reader :cache
9
+ PROTOCOL = 'memory://'
10
+
9
11
  def initialize
10
12
  @cache = {}
11
13
  end
@@ -16,7 +18,7 @@ module Valkyrie::Storage
16
18
  # @param _extra_arguments [Hash] additional arguments which may be passed to other adapters
17
19
  # @return [Valkyrie::StorageAdapter::StreamFile]
18
20
  def upload(file:, original_filename:, resource: nil, **_extra_arguments)
19
- identifier = Valkyrie::ID.new("memory://#{resource.id}")
21
+ identifier = Valkyrie::ID.new("#{protocol}#{resource.id}")
20
22
  version_id = Valkyrie::ID.new("#{identifier}##{SecureRandom.uuid}")
21
23
  cache[identifier] ||= {}
22
24
  cache[identifier][:current] = Valkyrie::StorageAdapter::StreamFile.new(id: identifier, io: file, version_id: version_id)
@@ -67,7 +69,7 @@ module Valkyrie::Storage
67
69
  # @param id [Valkyrie::ID]
68
70
  # @return [Boolean] true if this adapter can handle this type of identifer
69
71
  def handles?(id:)
70
- id.to_s.start_with?("memory://")
72
+ id.to_s.start_with?(protocol)
71
73
  end
72
74
 
73
75
  # @param feature [Symbol] Feature to test for.
@@ -83,6 +85,11 @@ module Valkyrie::Storage
83
85
  end
84
86
  end
85
87
 
88
+ # @return [String] identifier prefix
89
+ def protocol
90
+ PROTOCOL
91
+ end
92
+
86
93
  def id_and_version(id)
87
94
  id, version = id.to_s.split("#")
88
95
  [Valkyrie::ID.new(id), version]
@@ -7,6 +7,8 @@ module Valkyrie::Storage
7
7
  # with "deletionmarker" in the name of the file.
8
8
  class VersionedDisk
9
9
  attr_reader :base_path, :path_generator, :file_mover
10
+ PROTOCOL = 'versiondisk://'
11
+
10
12
  def initialize(base_path:, path_generator: ::Valkyrie::Storage::Disk::BucketedStorage, file_mover: FileUtils.method(:cp))
11
13
  @base_path = Pathname.new(base_path.to_s)
12
14
  @path_generator = path_generator.new(base_path: base_path)
@@ -26,7 +28,7 @@ module Valkyrie::Storage
26
28
  return sleep(0.001) && upload(file: file, original_filename: original_filename, resource: resource, paused: true, **extra_arguments) if !paused && File.exist?(new_path)
27
29
  FileUtils.mkdir_p(new_path.parent)
28
30
  file_mover.call(file.try(:path) || file.try(:disk_path), new_path)
29
- find_by(id: Valkyrie::ID.new("versiondisk://#{new_path}"))
31
+ find_by(id: Valkyrie::ID.new("#{protocol}#{new_path}"))
30
32
  end
31
33
 
32
34
  def current_timestamp
@@ -50,13 +52,13 @@ module Valkyrie::Storage
50
52
  return sleep(0.001) && upload_version(id: id, file: file, paused: true) if !paused && File.exist?(new_path)
51
53
  FileUtils.mkdir_p(new_path.parent)
52
54
  file_mover.call(file.try(:path) || file.try(:disk_path), new_path)
53
- find_by(id: Valkyrie::ID.new("versiondisk://#{new_path}"))
55
+ find_by(id: Valkyrie::ID.new("#{protocol}#{new_path}"))
54
56
  end
55
57
 
56
58
  # @param id [Valkyrie::ID]
57
59
  # @return [Boolean] true if this adapter can handle this type of identifer
58
60
  def handles?(id:)
59
- id.to_s.start_with?("versiondisk://#{base_path}")
61
+ id.to_s.start_with?("#{protocol}#{base_path}")
60
62
  end
61
63
 
62
64
  # @param feature [Symbol] Feature to test for.
@@ -66,6 +68,11 @@ module Valkyrie::Storage
66
68
  false
67
69
  end
68
70
 
71
+ # @return [String] identifier prefix
72
+ def protocol
73
+ PROTOCOL
74
+ end
75
+
69
76
  # Return the file associated with the given identifier
70
77
  # @param id [Valkyrie::ID]
71
78
  # @return [Valkyrie::StorageAdapter::File]
@@ -95,7 +102,7 @@ module Valkyrie::Storage
95
102
  # @return [Array<Valkyrie::StorageAdapter::File>]
96
103
  def find_versions(id:)
97
104
  version_files(id: id).select { |x| !x.to_s.include?("deletionmarker") }.map do |file|
98
- find_by(id: Valkyrie::ID.new("versiondisk://#{file}"))
105
+ find_by(id: Valkyrie::ID.new("#{protocol}#{file}"))
99
106
  end
100
107
  end
101
108
 
@@ -106,7 +113,7 @@ module Valkyrie::Storage
106
113
  end
107
114
 
108
115
  def file_path(version_id)
109
- version_id.to_s.gsub(/^versiondisk:\/\//, '')
116
+ version_id.to_s.gsub(/^#{Regexp.escape(protocol)}/, '')
110
117
  end
111
118
 
112
119
  # @return VersionId A VersionId value that's resolved a current reference,
@@ -128,6 +135,10 @@ module Valkyrie::Storage
128
135
  @id = id
129
136
  end
130
137
 
138
+ def protocol
139
+ PROTOCOL
140
+ end
141
+
131
142
  def current_reference_id
132
143
  self.class.new(Valkyrie::ID.new(string_id.gsub(version, "current")))
133
144
  end
@@ -139,13 +150,13 @@ module Valkyrie::Storage
139
150
  end
140
151
 
141
152
  def file_path
142
- @file_path ||= string_id.gsub(/^versiondisk:\/\//, '')
153
+ @file_path ||= string_id.gsub(/^#{Regexp.escape(protocol)}/, '')
143
154
  end
144
155
 
145
156
  def version_files
146
157
  root = Pathname.new(file_path)
147
158
  root.parent.children.select { |file| file.basename.to_s.end_with?(filename) }.sort.reverse.map do |file|
148
- VersionId.new(Valkyrie::ID.new("versiondisk://#{file}"))
159
+ VersionId.new(Valkyrie::ID.new("#{protocol}#{file}"))
149
160
  end
150
161
  end
151
162
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Valkyrie
3
- VERSION = "3.4.0"
3
+ VERSION = "3.5.1"
4
4
  end
data/lib/valkyrie.rb CHANGED
@@ -101,6 +101,10 @@ module Valkyrie
101
101
  self[:index_tsim_only_threshold].to_i
102
102
  end
103
103
 
104
+ def auto_cast_iso8601_as_datetime
105
+ self[:auto_cast_iso8601_as_datetime]
106
+ end
107
+
104
108
  # @api public
105
109
  #
106
110
  # The returned anonymous method (e.g. responds to #call) has a signature of
@@ -123,7 +127,8 @@ module Valkyrie
123
127
  def defaults
124
128
  {
125
129
  resource_class_resolver: method(:default_resource_class_resolver),
126
- index_tsim_only_threshold: 1000
130
+ index_tsim_only_threshold: 1000,
131
+ auto_cast_iso8601_as_datetime: true
127
132
  }
128
133
  end
129
134
 
data/valkyrie.gemspec CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.add_dependency 'dry-struct'
26
26
  spec.add_dependency 'activemodel'
27
27
  spec.add_dependency 'dry-types', '~> 1.0'
28
- spec.add_dependency 'rdf', '~> 3.0', '>= 3.0.10'
28
+ spec.add_dependency 'rdf', '~> 3.0', '>= 3.3.2'
29
29
  spec.add_dependency 'activesupport'
30
30
  spec.add_dependency 'railties' # To use generators and engines
31
31
  spec.add_dependency 'reform', '~> 2.2'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: valkyrie
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.4.0
4
+ version: 3.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Trey Pendragon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-11 00:00:00.000000000 Z
11
+ date: 2026-01-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-struct
@@ -61,7 +61,7 @@ dependencies:
61
61
  version: '3.0'
62
62
  - - ">="
63
63
  - !ruby/object:Gem::Version
64
- version: 3.0.10
64
+ version: 3.3.2
65
65
  type: :runtime
66
66
  prerelease: false
67
67
  version_requirements: !ruby/object:Gem::Requirement
@@ -71,7 +71,7 @@ dependencies:
71
71
  version: '3.0'
72
72
  - - ">="
73
73
  - !ruby/object:Gem::Version
74
- version: 3.0.10
74
+ version: 3.3.2
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: activesupport
77
77
  requirement: !ruby/object:Gem::Requirement
@@ -452,9 +452,9 @@ files:
452
452
  - db/migrate/20180212092225_create_updated_at_index.rb
453
453
  - db/migrate/20180802220739_add_optimistic_locking_to_orm_resources.rb
454
454
  - db/schema.rb
455
- - gemfiles/activerecord_7_0.gemfile
456
455
  - gemfiles/activerecord_7_1.gemfile
457
456
  - gemfiles/activerecord_7_2.gemfile
457
+ - gemfiles/activerecord_8_0.gemfile
458
458
  - gemfiles/faraday_1.gemfile
459
459
  - lib/valkyrie.rb
460
460
  - lib/valkyrie/adapter_container.rb