Class: AtlasRb::Resource
- Inherits:
-
Object
- Object
- AtlasRb::Resource
- Extended by:
- FaradayHelper
- Defined in:
- lib/atlas_rb/resource.rb,
lib/atlas_rb/resource_types.rb,
lib/atlas_rb/resource_writes.rb
Overview
Reopens Resource with the writes that need no type. Atlas serves each one
as a verb on the /resources/{id} sub-resource that already serves its
GET, so a caller holding only a NOID never resolves the type first.
Loaded after the subclasses because the typed writes delegate here, and
because TYPE_MAP in resource_types.rb has the same ordering
requirement.
The names are deliberately not update and metadata. Neither typed
name says which document it writes, and Resource.mods / .permissions
are already taken by the reads — overloading them by arity would give one
name two behaviours on the ACL surface.
Atlas refuses a type that cannot take the write; the gem does not
pre-check. A MODS write aimed at a FileSet answers 404, the same as the
GET on that path.
There are no typed counterparts. One URL serves every type, so a typed
write would name a type it could not enforce. The subclasses still answer
these methods, because they inherit them — AtlasRb::Work.tombstone(id) is
the same call as AtlasRb::Resource.tombstone(id), and neither checks that
id names a Work. That has always been true of the generic reads too;
Resource.find is the call that reports a type.
Direct Known Subclasses
Blob, Collection, Community, Compilation, Delegate, FileSet, Person, Work
Constant Summary collapse
- TYPE_MAP =
Every type the Atlas resolver can answer with, keyed by each spelling the DRS stack produces for it: Atlas's wire key (
"file_set"), the Ruby class name Solr indexes asinternal_resource("FileSet"), and the capitalize-of-the-wire-key form ("File_set") that a caller can still be holding from a value this gem emitted before class_for existed. A caller cannot tell which of the three it holds, so all three resolve.Stated rather than derived: Blob's
ROUTEis/files/, so nothing in the routes turns a type into its class either. { "work" => Work, "Work" => Work, "collection" => Collection, "Collection" => Collection, "community" => Community, "Community" => Community, "compilation" => Compilation, "Compilation" => Compilation, "file_set" => FileSet, "FileSet" => FileSet, "File_set" => FileSet, "blob" => Blob, "Blob" => Blob, "delegate" => Delegate, "Delegate" => Delegate, "person" => Person, "Person" => Person }.freeze
Constants included from FaradayHelper
FaradayHelper::ASSERTION_AUDIENCE, FaradayHelper::ASSERTION_ISSUER, FaradayHelper::ASSERTION_TTL, FaradayHelper::INSTRUMENTATION_EVENT
Class Method Summary collapse
-
.class_for(name) ⇒ Class
Resolve a resource-type string to the class that models it.
-
.descendant_works(id, page: nil, per_page: nil, include_linked: nil, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash?
Every Work beneath a resource, at any depth — the structural counterpart to Compilation.contents.
-
.find(id, nuid: nil, on_behalf_of: nil) ⇒ Hash{String => String, Hash}?
Resolve any Atlas resource by ID without knowing its type up front.
-
.find_many(ids, nuid: nil, on_behalf_of: nil) ⇒ Array<AtlasRb::Mash>?
Resolve many resources by NOID in a single round-trip.
-
.history(id, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash?
Fetch the audit-event history for a resource.
-
.mods(id, kind = nil, nuid: nil, on_behalf_of: nil) ⇒ String?
Fetch the CURRENT MODS of any Modsable resource by NOID — the polymorphic sibling of Work.mods / Collection.mods / Community.mods.
-
.mods_version(id, version_id, kind: nil, nuid: nil, on_behalf_of: nil) ⇒ String?
Fetch the MODS document as of a specific version.
-
.mods_versions(id, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash?
List the retained MODS versions for a resource.
-
.permissions(id, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash?
Fetch the access-control entries for a resource.
-
.preview(xml_path, nuid: nil, on_behalf_of: nil) ⇒ String
Validate a MODS XML document against Atlas's schema without persisting it.
-
.put_mods(id, xml_path, nuid: nil, on_behalf_of: nil, origin: nil) ⇒ AtlasRb::Mash
Replace a resource's MODS document.
-
.reparent(id, new_parent_id = nil, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Move a resource under a different parent.
-
.set_permissions(id, values, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Adjust a resource's ACL.
-
.set_thumbnails(id, thumbnail: nil, thumbnail_2x: nil, preview: nil, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Attach the three thumbnail-family IIIF Delegate URIs to a resource.
-
.tombstone(id, nuid: nil, on_behalf_of: nil) ⇒ Faraday::Response
Restore is the operator's counterpart and lives in Admin::Resource, where the namespace is the marker.
Methods included from FaradayHelper
connection, multipart, read_body, read_raw, system_connection, with_file_part
Class Method Details
.class_for(name) ⇒ Class
Resolve a resource-type string to the class that models it.
Use this on any type that arrives as runtime data — find's
"klass", a Solr internal_resource value, or Atlas's wire key — rather
than reaching into this namespace with const_get. The set is closed and
stated in TYPE_MAP; it is not a naming rule.
An unrecognized type raises instead of resolving to nil or to whatever
constant happens to bear that name. A caller holding a type this gem does
not define has to hear about it here, where the cause is, rather than at
the NoMethodError a few frames later.
54 55 56 57 58 |
# File 'lib/atlas_rb/resource_types.rb', line 54 def self.class_for(name) TYPE_MAP.fetch(name.to_s) do raise ArgumentError, "unknown Atlas resource type: #{name.inspect}" end end |
.descendant_works(id, page: nil, per_page: nil, include_linked: nil, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash?
Every Work beneath a resource, at any depth — the structural counterpart
to Compilation.contents. Wraps GET /resources/<id>/descendant_works,
which flattens the resource's full descendant subtree to the Works it
contains, gated to what the caller may read and paginated Solr-side. Gives
a Collection the flatten-to-Works capability a Set already has, so a bulk
export (e.g. hyperion) pages one gated, fast call family instead of the
client-side children → find_many → recurse walk.
Returns the same digest shape as find_many / Compilation.contents
({ "id", "noid", "klass", "title", "thumbnail" }) under a "works" key,
plus a "pagination" envelope (total / page / per_page / pages).
Membership is structural (a_member_of) only; pass
include_linked: true to also surface linked members
(a_linked_member_of). Restricted Works never appear for a caller who may
not read them; tombstoned Works are dropped.
149 150 151 152 153 154 155 156 |
# File 'lib/atlas_rb/resource.rb', line 149 def self.descendant_works(id, page: nil, per_page: nil, include_linked: nil, nuid: nil, on_behalf_of: nil) params = {} params[:page] = page if page params[:per_page] = per_page if per_page params[:include_linked] = include_linked unless include_linked.nil? read_body(connection(params, nuid, on_behalf_of: on_behalf_of) .get('/resources/' + id + '/descendant_works')) { |body| AtlasRb::Mash.new(body) } end |
.find(id, nuid: nil, on_behalf_of: nil) ⇒ Hash{String => String, Hash}?
Resolve any Atlas resource by ID without knowing its type up front.
The Atlas server returns a single-key JSON object whose key names the
resource type ("community", "collection", "work", etc.); this
method splits that into a normalized { "klass" => ..., "resource" => ... }
pair so callers can dispatch on type.
What the resolver covers
Atlas answers /resources/:id for its Valkyrie-backed types only:
Work, Collection, Community, FileSet, Blob, Delegate and
Person. A Compilation is an ActiveRecord row in Atlas rather than a
Valkyrie resource, so the resolver never finds one — use
Compilation.find for those. That makes nil ambiguous: it means "no
such id" or "that id names a Compilation".
58 59 60 61 62 63 64 65 66 67 |
# File 'lib/atlas_rb/resource.rb', line 58 def self.find(id, nuid: nil, on_behalf_of: nil) result = fetch_resource('/resources/' + id, nuid: nuid, on_behalf_of: on_behalf_of) return nil if result.nil? # The class's own name, never `capitalize` over the wire key: `capitalize` # answers `"File_set"` for a `file_set`, which is not a constant in this # namespace, and callers resolve this string to a class. AtlasRb::Mash.new("klass" => class_for(result.first[0]).name.split("::").last, "resource" => result.first[1]) end |
.find_many(ids, nuid: nil, on_behalf_of: nil) ⇒ Array<AtlasRb::Mash>?
Resolve many resources by NOID in a single round-trip.
Wraps Atlas's POST /resources/find_many, which returns one lightweight
digest per resolvable resource — { "id", "noid", "klass", "title", "thumbnail", "tombstoned" } — rather than full typed payloads. Use it
anywhere a set of ids would otherwise be resolved with a find-per-id
fan-out (breadcrumb chains, linked-member lists, load-destination
pickers): one HTTP call instead of N.
The ids travel in the request body, so the list is not bounded by
URL length. The result is unordered and may be shorter than the
input — unresolvable ids are dropped silently, and tombstoned
resources come back flagged ("tombstoned" => true) rather than
omitted. Index the result by "noid"; do not assume positional
correspondence with ids.
105 106 107 108 109 110 |
# File 'lib/atlas_rb/resource.rb', line 105 def self.find_many(ids, nuid: nil, on_behalf_of: nil) read_body( connection({}, nuid, on_behalf_of: on_behalf_of) .post('/resources/find_many', JSON.dump(ids: Array(ids))) ) { |body| body.map { |node| AtlasRb::Mash.new(node) } } end |
.history(id, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash?
Add pagination support once Atlas's history endpoint exposes page / per_page query params. Today the endpoint returns the full history in one shot.
Fetch the audit-event history for a resource.
Wraps Atlas's GET /resources/<id>/history endpoint, which returns the
full envelope (resource_id + reverse-chronological events array).
The whole envelope is preserved so callers can confirm the events
belong to the requested resource; access events as result["events"].
Authorization errors (401 / 403) are intentionally not caught
here — they surface as raw Faraday responses for the calling
application's rescue layer to translate.
249 250 251 252 |
# File 'lib/atlas_rb/resource.rb', line 249 def self.history(id, nuid: nil, on_behalf_of: nil) read_body(connection({}, nuid, on_behalf_of: on_behalf_of) .get('/resources/' + id + '/history')) { |body| AtlasRb::Mash.new(body) } end |
.mods(id, kind = nil, nuid: nil, on_behalf_of: nil) ⇒ String?
Fetch the CURRENT MODS of any Modsable resource by NOID — the polymorphic
sibling of Work.mods / Collection.mods / Community.mods. Wraps
GET /resources/<id>/mods[.kind] and returns the raw response body
(not parsed), mirroring the typed wrappers. Lets a caller holding only a
NOID (no type) fetch descriptive MODS in one call, instead of resolving
the klass first to pick the typed route — e.g. a bulk Collection/Set MODS
export that has bare member NOIDs from Collection.children.
283 284 285 286 287 |
# File 'lib/atlas_rb/resource.rb', line 283 def self.mods(id, kind = nil, nuid: nil, on_behalf_of: nil) read_raw(connection({}, nuid, on_behalf_of: on_behalf_of).get( '/resources/' + id + '/mods' + (kind.to_s.empty? ? '' : ".#{kind}") )) end |
.mods_version(id, version_id, kind: nil, nuid: nil, on_behalf_of: nil) ⇒ String?
Fetch the MODS document as of a specific version.
Wraps Atlas's GET /resources/<id>/mods/versions/<version_id> and
returns the raw response body (not parsed) — mirroring
Work.mods. Pass a version_id obtained from mods_versions (an
opaque OCFL vN label).
Only XML is version-recoverable: the JSON access copy is overwritten in
place, so the server serves historical XML (the default). kind: is
accepted for parity with Work.mods but XML is currently the only
supported format. An unknown version yields a 404 (raw Faraday
response).
366 367 368 369 370 371 |
# File 'lib/atlas_rb/resource.rb', line 366 def self.mods_version(id, version_id, kind: nil, nuid: nil, on_behalf_of: nil) read_raw(connection({}, nuid, on_behalf_of: on_behalf_of).get( '/resources/' + id + '/mods/versions/' + version_id + (kind.to_s.empty? ? '' : ".#{kind}") )) end |
.mods_versions(id, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash?
List the retained MODS versions for a resource.
Wraps Atlas's GET /resources/<id>/mods/versions, which returns the
full envelope — resource_id plus a reverse-chronological versions
array — as an AtlasRb::Mash. Each version descriptor mirrors the
audit-event shape (version_id, created, actor_nuid,
on_behalf_of_nuid, source, note) so the two streams render with
the same helpers; actor fields are correlated from the audit log and
may be null when a version has no matching edit event.
Type-agnostic: pass any Modsable resource ID (Community, Collection,
Work). A resource with no MODS comes back as { "versions" => [] }.
Version labels are opaque, sortable OCFL vN strings — not a 1-based
counter — so treat them as identifiers to feed back into
mods_version, not as ordinals. The server admin-gates this endpoint
(it exposes edit attribution); 401 / 403 surface as raw Faraday
responses, matching history.
327 328 329 330 |
# File 'lib/atlas_rb/resource.rb', line 327 def self.mods_versions(id, nuid: nil, on_behalf_of: nil) read_body(connection({}, nuid, on_behalf_of: on_behalf_of) .get('/resources/' + id + '/mods/versions')) { |body| AtlasRb::Mash.new(body) } end |
.permissions(id, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash?
Fetch the access-control entries for a resource.
Routed through fetch_resource so a refusal stays a refusal. Atlas gates
this endpoint on the caller's read right over the resource itself and
answers 403 with an { "error", "action", "subject" } envelope, which
has no "resource" key — parsing it directly would hand back the same
nil an unknown id gives, and a caller cannot tell "may not see it" from
"is not there".
207 208 209 210 211 212 |
# File 'lib/atlas_rb/resource.rb', line 207 def self.(id, nuid: nil, on_behalf_of: nil) result = fetch_resource('/resources/' + id + '/permissions', nuid: nuid, on_behalf_of: on_behalf_of) return nil if result.nil? AtlasRb::Mash.new(result)["resource"] end |
.preview(xml_path, nuid: nil, on_behalf_of: nil) ⇒ String
Validate a MODS XML document against Atlas's schema without persisting it.
Useful for surfacing validation errors in UIs before the user commits.
174 175 176 177 178 179 |
# File 'lib/atlas_rb/resource.rb', line 174 def self.preview(xml_path, nuid: nil, on_behalf_of: nil) payload = { binary: Faraday::Multipart::FilePart.new(File.open(xml_path), "application/xml", File.basename(xml_path)) } multipart(nuid, on_behalf_of: on_behalf_of).post('/resources/preview', payload)&.body end |
.put_mods(id, xml_path, nuid: nil, on_behalf_of: nil, origin: nil) ⇒ AtlasRb::Mash
Replace a resource's MODS document.
PUT, not PATCH: the caller assembles the whole document. Descriptive
merge logic lives in the client, so a partial document replaces rather
than merges, and the verb says so.
52 53 54 55 56 57 |
# File 'lib/atlas_rb/resource_writes.rb', line 52 def self.put_mods(id, xml_path, nuid: nil, on_behalf_of: nil, origin: nil) unwrap(write_resource( multipart(nuid, on_behalf_of: on_behalf_of) .put('/resources/' + id + '/mods', mods_upload_payload(xml_path, origin)) )) end |
.reparent(id, new_parent_id = nil, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Move a resource under a different parent.
Authorization is two-sided — the caller needs the right on the moved node
and on the destination. Omit new_parent_id to move a Community to
the top of the tree.
124 125 126 127 128 129 |
# File 'lib/atlas_rb/resource_writes.rb', line 124 def self.reparent(id, new_parent_id = nil, nuid: nil, on_behalf_of: nil) unwrap(write_resource( connection({ parent_id: new_parent_id }, nuid, on_behalf_of: on_behalf_of) .patch('/resources/' + id + '/parent') )) end |
.set_permissions(id, values, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Adjust a resource's ACL.
PATCH, and every key merges: a key you omit keeps its stored value.
Pass an explicit empty array to clear one. So changing a single slot no
longer needs the read-the-whole-envelope-and-write-it-back round trip.
79 80 81 82 83 84 |
# File 'lib/atlas_rb/resource_writes.rb', line 79 def self.(id, values, nuid: nil, on_behalf_of: nil) unwrap(write_resource( connection({ permissions: values }, nuid, on_behalf_of: on_behalf_of) .patch('/resources/' + id + '/permissions') )) end |
.set_thumbnails(id, thumbnail: nil, thumbnail_2x: nil, preview: nil, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash
Attach the three thumbnail-family IIIF Delegate URIs to a resource.
Only the URIs you pass are upserted; an omitted key is left untouched.
101 102 103 104 105 106 107 |
# File 'lib/atlas_rb/resource_writes.rb', line 101 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 unwrap(write_resource( connection({}, nuid, on_behalf_of: on_behalf_of) .patch('/resources/' + id + '/thumbnails', JSON.dump(body)) )) end |
.tombstone(id, nuid: nil, on_behalf_of: nil) ⇒ Faraday::Response
Restore is the operator's counterpart and lives in Admin::Resource, where the namespace is the marker.
Tombstone (withdraw) a resource.
Returns the raw response rather than raising, because Atlas refuses a
container that still holds live children with a 422 carrying
has_live_children — a legitimate answer the caller has to read, not an
error. Reversible via restore.
147 148 149 |
# File 'lib/atlas_rb/resource_writes.rb', line 147 def self.tombstone(id, nuid: nil, on_behalf_of: nil) connection({}, nuid, on_behalf_of: on_behalf_of).post('/resources/' + id + '/tombstone') end |