Class: AtlasRb::Work
Overview
The bibliographic unit in Atlas — an article, thesis, dataset, image, etc.
A Work belongs to exactly one Collection and aggregates one or more FileSets, each of which holds binary content via a Blob. MODS metadata is attached at the Work level.
See also: Collection, FileSet, Blob.
Constant Summary collapse
- ROUTE =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Atlas REST endpoint prefix for this resource.
"/works/"
Constants included from FaradayHelper
FaradayHelper::ASSERTION_AUDIENCE, FaradayHelper::ASSERTION_ISSUER, FaradayHelper::ASSERTION_TTL
Class Method Summary collapse
-
.add_linked_member(work_id, collection_id, nuid: nil, on_behalf_of: nil) ⇒ Array<String>
Add a linked membership: surface a Work in an additional Collection.
- .assets(id, nuid: nil, on_behalf_of: nil) ⇒ Array<AtlasRb::Mash>
-
.complete(id, nuid: nil, on_behalf_of: nil) ⇒ Faraday::Response
Mark a Work complete.
-
.create(id, xml_path = nil, idempotency_key: nil, nuid: nil, on_behalf_of: nil, depositor: nil) ⇒ Hash
Create a new Work in an existing Collection.
-
.file_sets(id, nuid: nil, on_behalf_of: nil) ⇒ Array<AtlasRb::Mash>
List a Work's page FileSets in order, each with its assets.
-
.find(id, nuid: nil, on_behalf_of: nil) ⇒ Hash?
Fetch a single Work by ID.
-
.linked_members(id, nuid: nil, on_behalf_of: nil) ⇒ Array<String>
List the Collections a Work is a linked member of.
-
.list(in_progress: nil, page: nil, per_page: nil, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
List Works, paginated.
-
.metadata(id, values, nuid: nil, on_behalf_of: nil) ⇒ Hash
Patch individual descriptive-metadata fields without uploading a full MODS document.
-
.mets(id, nuid: nil, on_behalf_of: nil) ⇒ Hash?
Fetch the Work-level METS structural metadata (page order).
-
.mods(id, kind = nil, nuid: nil, on_behalf_of: nil) ⇒ String
Fetch the Work's MODS representation in the requested format.
-
.remove_linked_member(work_id, collection_id, nuid: nil, on_behalf_of: nil) ⇒ Array<String>
Remove a linked membership: drop a Work from an additional Collection.
-
.reparent(id, new_collection_id, nuid: nil, on_behalf_of: nil) ⇒ Hash
Move a Work to a different parent Collection.
-
.set_derivative_permissions(id, policy:, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Replace a Work's per-asset derivative-visibility policy.
-
.set_full_text(id, text:, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Store a Work's derived full-document text for search indexing.
-
.set_image_derivatives(id, small: nil, medium: nil, large: nil, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Attach the three image-derivative Delegate URIs to a Work.
-
.set_thumbnails(id, thumbnail: nil, thumbnail_2x: nil, preview: nil, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Attach the three thumbnail/preview Delegate URIs to a Work.
-
.tombstone(id, nuid: nil, on_behalf_of: nil) ⇒ Faraday::Response
Tombstone (withdraw) a Work.
-
.update(id, xml_path, nuid: nil, on_behalf_of: nil) ⇒ Hash
Replace a Work's metadata by uploading a MODS XML document.
Methods inherited from Resource
find_many, history, mods_version, mods_versions, permissions, preview
Methods included from FaradayHelper
#connection, #multipart, #system_connection, #with_file_part
Class Method Details
.add_linked_member(work_id, collection_id, nuid: nil, on_behalf_of: nil) ⇒ Array<String>
Add a linked membership: surface a Work in an additional Collection.
Wraps POST /works/<id>/linked_members with a collection_id body.
This does not move the Work — its structural parent (a_member_of)
is untouched; the Collection is added to a_linked_member_of. Atlas
enforces two-sided authorization (edit on the Work and the target
Collection) and the structural guards, surfacing failures as a 422.
Permissions are never changed by this call.
627 628 629 630 631 632 |
# File 'lib/atlas_rb/work.rb', line 627 def self.add_linked_member(work_id, collection_id, nuid: nil, on_behalf_of: nil) JSON.parse( connection({ collection_id: collection_id }, nuid, on_behalf_of: on_behalf_of) .post(ROUTE + work_id + '/linked_members')&.body ) end |
.assets(id, nuid: nil, on_behalf_of: nil) ⇒ Array<AtlasRb::Mash>
List the assets attached to a Work — Blobs and Delegates alike.
Useful for building download UIs — the response includes enough to
render each entry's display name, size or uri, and download URL.
The shape is polymorphic: Blob-backed entries carry fields like
size, while Delegate-backed entries carry uri. Callers should
duck-type on the field they need rather than expecting a single
schema.
Every entry (Blob and Delegate) also carries the advisory read gate set
via set_derivative_permissions: gated (true if the asset must be
authorized rather than fetched directly) and permission (the effective
read-group set, or nil for guests, to whom group names are withheld).
466 467 468 469 470 |
# File 'lib/atlas_rb/work.rb', line 466 def self.assets(id, nuid: nil, on_behalf_of: nil) JSON.parse( connection({}, nuid, on_behalf_of: on_behalf_of).get(ROUTE + id + '/assets')&.body ).map { |entry| AtlasRb::Mash.new(entry) } end |
.complete(id, nuid: nil, on_behalf_of: nil) ⇒ Faraday::Response
Mark a Work complete.
Cerberus's bulk-deposit job calls this once it has confirmed all
expected children (FileSets / Blobs) are deposited. Atlas's monitoring
query GET /works?in_progress=true then drops this Work from the
"stuck" list.
Idempotent on the server: calling complete on an already-complete
Work is a no-op — Atlas simply re-saves with in_progress: false.
Atlas does not currently stamp a completed_by audit field; the
nuid: parameter is plumbed through for parity with the other
lifecycle bindings and in case Atlas adds completion audit later.
223 224 225 |
# File 'lib/atlas_rb/work.rb', line 223 def self.complete(id, nuid: nil, on_behalf_of: nil) connection({}, nuid, on_behalf_of: on_behalf_of).post(ROUTE + id + '/complete') end |
.create(id, xml_path = nil, idempotency_key: nil, nuid: nil, on_behalf_of: nil, depositor: nil) ⇒ Hash
Create a new Work in an existing Collection.
Note: unlike Community.create and Collection.create, the id
parameter here is the parent Collection ID. The underlying request
uses the collection_id query param rather than parent_id.
119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/atlas_rb/work.rb', line 119 def self.create(id, xml_path = nil, idempotency_key: nil, nuid: nil, on_behalf_of: nil, depositor: nil) params = { collection_id: id } params[:depositor] = depositor if depositor result = AtlasRb::Mash.new(JSON.parse( connection(params, nuid, on_behalf_of: on_behalf_of, idempotency_key: idempotency_key).post(ROUTE)&.body ))["work"] return result unless xml_path.present? update(result["id"], xml_path, nuid: nuid, on_behalf_of: on_behalf_of) find(result["id"], nuid: nuid, on_behalf_of: on_behalf_of) end |
.file_sets(id, nuid: nil, on_behalf_of: nil) ⇒ Array<AtlasRb::Mash>
List a Work's page FileSets in order, each with its assets.
Wraps GET /works/<id>/file_sets — the ordered, grouped sibling of
assets (which flattens FileSet membership away). One entry per
page-bearing FileSet, sorted position ascending with unordered
(null-position) FileSets last; metadata and derivative-container
FileSets are excluded as entries. Each entry nests its downloadable
assets — the page's content Blobs plus any per-page IIIF Delegates —
in the same polymorphic shape assets returns.
This is the read a IIIF Presentation manifest assembler needs: the response is unpaginated by design, so the whole page sequence arrives in one call.
501 502 503 504 505 |
# File 'lib/atlas_rb/work.rb', line 501 def self.file_sets(id, nuid: nil, on_behalf_of: nil) JSON.parse( connection({}, nuid, on_behalf_of: on_behalf_of).get(ROUTE + id + '/file_sets')&.body ).map { |entry| AtlasRb::Mash.new(entry) } end |
.find(id, nuid: nil, on_behalf_of: nil) ⇒ Hash?
Fetch a single Work by ID.
33 34 35 36 |
# File 'lib/atlas_rb/work.rb', line 33 def self.find(id, nuid: nil, on_behalf_of: nil) body = fetch_resource(ROUTE + id, nuid: nuid, on_behalf_of: on_behalf_of) body && AtlasRb::Mash.new(body)["work"] end |
.linked_members(id, nuid: nil, on_behalf_of: nil) ⇒ Array<String>
List the Collections a Work is a linked member of.
Wraps GET /works/<id>/linked_members. Linked membership is the DAG
overlay — a Work has exactly one structural parent (a_member_of, set
by create / reparent) but may additionally appear in any number
of other Collections as a linked member (a_linked_member_of). This
returns just those linked Collection noids; the structural parent is
not included.
589 590 591 592 593 |
# File 'lib/atlas_rb/work.rb', line 589 def self.linked_members(id, nuid: nil, on_behalf_of: nil) JSON.parse( connection({}, nuid, on_behalf_of: on_behalf_of).get(ROUTE + id + '/linked_members')&.body ) end |
.list(in_progress: nil, page: nil, per_page: nil, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
List Works, paginated.
Wraps GET /works. Returns the full pagination envelope rather than a
bare array so callers can page through results — the shape matches
Community.children and Collection.children.
63 64 65 66 67 68 69 70 71 |
# File 'lib/atlas_rb/work.rb', line 63 def self.list(in_progress: nil, page: nil, per_page: nil, nuid: nil, on_behalf_of: nil) params = {} params[:in_progress] = in_progress unless in_progress.nil? params[:page] = page if page params[:per_page] = per_page if per_page AtlasRb::Mash.new(JSON.parse( connection(params, nuid, on_behalf_of: on_behalf_of).get(ROUTE)&.body )) end |
.metadata(id, values, nuid: nil, on_behalf_of: nil) ⇒ Hash
Patch individual descriptive-metadata fields without uploading a full MODS document.
Scoped to user-authored descriptive metadata only. Programmatic writes of machine-set Delegate URIs (thumbnails, image derivatives) have their own purpose-specific endpoints — see set_thumbnails and set_image_derivatives.
270 271 272 273 274 |
# File 'lib/atlas_rb/work.rb', line 270 def self.(id, values, nuid: nil, on_behalf_of: nil) AtlasRb::Mash.new(JSON.parse( connection({ metadata: values }, nuid, on_behalf_of: on_behalf_of).patch(ROUTE + id)&.body )) end |
.mets(id, nuid: nil, on_behalf_of: nil) ⇒ Hash?
Fetch the Work-level METS structural metadata (page order).
Wraps GET /works/<id>/mets — the JSON projection of the Work's METS
document, whose physical structMap is the preservation record of page
order. The page sequence surfaces under "mets" => "pages" (one entry
per page: noid / order / label). Atlas builds the document when
the Work is completed (complete) and rebuilds it on page changes
thereafter, so a Work that has never been completed has no METS yet —
Atlas answers 404 and this binding returns nil (matching
User.find_by_nuid's missing-resource convention).
For runtime page listing (e.g. manifest assembly) prefer file_sets, which needs no completion and carries each page's assets; this read is the preservation-record view.
537 538 539 540 541 542 |
# File 'lib/atlas_rb/work.rb', line 537 def self.mets(id, nuid: nil, on_behalf_of: nil) response = connection({}, nuid, on_behalf_of: on_behalf_of).get(ROUTE + id + '/mets') return nil if response.status == 404 AtlasRb::Mash.new(JSON.parse(response.body))["work"] end |
.mods(id, kind = nil, nuid: nil, on_behalf_of: nil) ⇒ String
Fetch the Work's MODS representation in the requested format.
559 560 561 562 563 564 |
# File 'lib/atlas_rb/work.rb', line 559 def self.mods(id, kind = nil, nuid: nil, on_behalf_of: nil) # json default, html, xml connection({}, nuid, on_behalf_of: on_behalf_of).get( ROUTE + id + '/mods' + (kind.present? ? ".#{kind}" : '') )&.body end |
.remove_linked_member(work_id, collection_id, nuid: nil, on_behalf_of: nil) ⇒ Array<String>
Remove a linked membership: drop a Work from an additional Collection.
Wraps DELETE /works/<id>/linked_members/<collection_id> — the
Collection is passed as a path segment, not a body. This removes the
Collection from the Work's a_linked_member_of; the structural parent
(a_member_of) is untouched. Atlas enforces the same two-sided
authorization as add_linked_member. Removing a link that does not
exist is a server-side concern; this binding simply forwards the call.
665 666 667 668 669 670 |
# File 'lib/atlas_rb/work.rb', line 665 def self.remove_linked_member(work_id, collection_id, nuid: nil, on_behalf_of: nil) JSON.parse( connection({}, nuid, on_behalf_of: on_behalf_of) .delete(ROUTE + work_id + '/linked_members/' + collection_id)&.body ) end |
.reparent(id, new_collection_id, nuid: nil, on_behalf_of: nil) ⇒ Hash
Move a Work to a different parent Collection.
Wraps PATCH /works/<id>/parent with a parent_id of the new
Collection. This changes the Work's single structural home
(a_member_of) — distinct from add_linked_member, which adds an
additional linked membership without moving the Work. Atlas
re-parents the Work and synchronously updates its ancestry index; the
structural rules (type, cycle, tombstone guards) are enforced
server-side and surface as a 422.
Note: like create, the destination here is a Collection, but
the underlying request still uses the shared parent_id body key (not
collection_id) — every re-parent endpoint posts { parent_id }.
169 170 171 172 173 174 |
# File 'lib/atlas_rb/work.rb', line 169 def self.reparent(id, new_collection_id, nuid: nil, on_behalf_of: nil) AtlasRb::Mash.new(JSON.parse( connection({ parent_id: new_collection_id }, nuid, on_behalf_of: on_behalf_of) .patch(ROUTE + id + '/parent')&.body ))["work"] end |
.set_derivative_permissions(id, policy:, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Replace a Work's per-asset derivative-visibility policy.
Sets which read groups may fetch each of the Work's downloadable
renditions, reusing the resource read-group vocabulary ("public",
Grouper group names, [] = private). Two media families:
- the image ladder
small/medium/large/service(deep-zoom) /master(the original image), and - independent media
audio/video/pdf.
Unlike set_image_derivatives (which upserts URIs) this is a whole-object
REPLACE: the map you pass is the complete policy. Within the image ladder
omitted tiers inherit by cascade (an absent tier inherits the next
lower-resolution tier; small inherits the Work's own visibility).
Independent media do NOT cascade — an absent audio/video/pdf key
rides the Work. Pass a tier as [] to make it private.
Atlas enforces: a tier may not be more visible than the Work, and — within
the image ladder — visibility must narrow as resolution grows
(master ⊆ service ⊆ large ⊆ medium ⊆ small; independent media
impose no ordering). The gate is advisory — it surfaces on assets as
gated / permission for BOTH Delegate (image tier) and Blob (master /
pdf / audio / video, classified by media type) entries, for the display
layer (Cerberus / the IIIF auth service; Cerberus's download :read check)
to enforce.
397 398 399 400 401 402 |
# File 'lib/atlas_rb/work.rb', line 397 def self.(id, policy:, nuid: nil, on_behalf_of: nil) AtlasRb::Mash.new(JSON.parse( connection({}, nuid, on_behalf_of: on_behalf_of) .patch(ROUTE + id + '/derivative_permissions', JSON.dump(policy))&.body )) end |
.set_full_text(id, text:, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Store a Work's derived full-document text for search indexing.
Purpose-specific PATCH in the same "machine-set derived metadata" family
as set_thumbnails / set_image_derivatives. Hand Atlas the Work-level
aggregate of the extracted body text (the concatenation of the Work's
content FileSets' text); Atlas stores it as the Work's derived full_text
and its FullTextIndexer projects it onto the Work's Solr doc
(all_text_timv) for body-text search + the "Full Text Match" snippet.
Distinct from metadata — this is a machine-extracted search aid (pdftotext / Tika in a Cerberus job), not user-authored descriptive content, and is re-sent on any re-ingest. Empty/blank text clears it.
433 434 435 436 437 438 |
# File 'lib/atlas_rb/work.rb', line 433 def self.set_full_text(id, text:, nuid: nil, on_behalf_of: nil) AtlasRb::Mash.new(JSON.parse( connection({}, nuid, on_behalf_of: on_behalf_of) .patch(ROUTE + id + '/full_text', JSON.dump(text: text))&.body )) end |
.set_image_derivatives(id, small: nil, medium: nil, large: nil, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Attach the three image-derivative Delegate URIs to a Work.
Sibling of set_thumbnails for the small_image /
medium_image / large_image Delegate roles. Atlas dispatches
each URI to its matching role via DelegateUpdater. The
resulting Delegates are downloadable and surface through
assets for the downloads UI. Missing keys are left untouched
server-side; only the URIs you pass are upserted.
340 341 342 343 344 345 346 |
# File 'lib/atlas_rb/work.rb', line 340 def self.set_image_derivatives(id, small: nil, medium: nil, large: nil, nuid: nil, on_behalf_of: nil) body = { small: small, medium: medium, large: large }.compact AtlasRb::Mash.new(JSON.parse( connection({}, nuid, on_behalf_of: on_behalf_of) .patch(ROUTE + id + '/image_derivatives', JSON.dump(body))&.body )) end |
.set_thumbnails(id, thumbnail: nil, thumbnail_2x: nil, preview: nil, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Attach the three thumbnail/preview Delegate URIs to a Work.
Purpose-specific PATCH for the thumbnail_image /
thumbnail_image_2x / preview_image Delegate roles. Atlas
dispatches each URI to its matching role via DelegateUpdater.
Distinct from metadata — these are machine-set IIIF URIs, not
user-authored descriptive content. Missing keys are left
untouched server-side; only the URIs you pass are upserted.
304 305 306 307 308 309 310 |
# File 'lib/atlas_rb/work.rb', line 304 def self.set_thumbnails(id, thumbnail: nil, thumbnail_2x: nil, preview: nil, nuid: nil, on_behalf_of: nil) body = { thumbnail: thumbnail, thumbnail_2x: thumbnail_2x, preview: preview }.compact AtlasRb::Mash.new(JSON.parse( connection({}, nuid, on_behalf_of: on_behalf_of) .patch(ROUTE + id + '/thumbnails', JSON.dump(body))&.body )) end |
.tombstone(id, nuid: nil, on_behalf_of: nil) ⇒ Faraday::Response
Tombstone (withdraw) a Work.
The Work remains in Atlas storage along with its FileSets and Blobs, but is marked as withdrawn: search and show pages return a withdrawn stub for every user. Unlike Communities and Collections, Works are always tombstoneable regardless of how many files they hold — the FileSets and Blobs ride along.
194 195 196 |
# File 'lib/atlas_rb/work.rb', line 194 def self.tombstone(id, nuid: nil, on_behalf_of: nil) connection({}, nuid, on_behalf_of: on_behalf_of).post(ROUTE + id + '/tombstone') end |
.update(id, xml_path, nuid: nil, on_behalf_of: nil) ⇒ Hash
Replace a Work's metadata by uploading a MODS XML document.
241 242 243 244 245 246 247 248 |
# File 'lib/atlas_rb/work.rb', line 241 def self.update(id, xml_path, nuid: nil, on_behalf_of: nil) payload = { binary: Faraday::Multipart::FilePart.new(File.open(xml_path), "application/xml", File.basename(xml_path)) } AtlasRb::Mash.new(JSON.parse( multipart(nuid, on_behalf_of: on_behalf_of).patch(ROUTE + id, payload)&.body )) end |