Class: AtlasRb::Compilation

Inherits:
Resource show all
Defined in:
lib/atlas_rb/compilation.rb

Overview

A Compilation (DRS "Set") — a personal, curated, recipe-based grouping of Works and Collections.

The recipe is three noid lists: included collections (resolved transitively — the collection plus everything beneath it), individually included works, and excluded works ("set-asides", subtracted from the resolved union). Atlas resolves the recipe at read time via Compilation.contents; nothing is materialized.

Compilations are Atlas-side ActiveRecord (ephemeral curation, not repository content), but carry a minted NOID as their public id — so ids here look exactly like every other resource's. There is no /mods, thumbnail, or tombstone surface to bind. Membership rules (Works and Collections only, no Communities) are enforced server-side; a rejected add surfaces as CompilationError (422), an authorization refusal as ForbiddenError (403).

See also: Work.add_linked_member — the membership add/remove pairs here mirror that precedent.

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.

"/compilations/"

Constants included from FaradayHelper

FaradayHelper::ASSERTION_AUDIENCE, FaradayHelper::ASSERTION_ISSUER, FaradayHelper::ASSERTION_TTL

Class Method Summary collapse

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_exclusion(id, work_id, nuid: nil, on_behalf_of: nil) ⇒ Hash

Set a Work aside: subtract it from the Set's resolved contents even though an included collection covers it.

Idempotent. The noid must resolve to a Work. Setting aside a Work that no inclusion currently covers is legal — the recipe lines are independent; the subtraction just matches nothing.

Examples:

AtlasRb::Compilation.add_exclusion("c-123", "w-789", nuid: "000000002")

Parameters:

  • id (String)

    the Compilation ID.

  • work_id (String)

    the Work NOID to set aside.

  • nuid (String, nil) (defaults to: nil)

    optional acting user's NUID. On the relay-signing path it is signed into the assertion sub; on the BYO-JWT (ATLAS_JWT) path it is ignored (identity lives in the token).

  • on_behalf_of (String, nil) (defaults to: nil)

    optional NUID for the On-Behalf-Of header. Falls through to AtlasRb.config.default_on_behalf_of when omitted.

Returns:

  • (Hash)

    the updated "compilation" object.

Raises:



336
337
338
339
340
341
# File 'lib/atlas_rb/compilation.rb', line 336

def self.add_exclusion(id, work_id, nuid: nil, on_behalf_of: nil)
  AtlasRb::Mash.new(JSON.parse(
    connection({ work_id: work_id }, nuid, on_behalf_of: on_behalf_of)
      .post(ROUTE + id + '/exclusions')&.body
  ))["compilation"]
end

.add_included_collection(id, collection_id, nuid: nil, on_behalf_of: nil) ⇒ Hash

Add an include-collection recipe line: everything under the Collection (transitively) joins the Set's resolved contents.

Idempotent — re-adding an included collection is a no-op. The noid must resolve to a Collection: Communities and unknown ids are rejected server-side as a 422.

Examples:

AtlasRb::Compilation.add_included_collection("c-123", "col-456", nuid: "000000002")

Parameters:

  • id (String)

    the Compilation ID.

  • collection_id (String)

    the Collection NOID to include.

  • nuid (String, nil) (defaults to: nil)

    optional acting user's NUID. On the relay-signing path it is signed into the assertion sub; on the BYO-JWT (ATLAS_JWT) path it is ignored (identity lives in the token).

  • on_behalf_of (String, nil) (defaults to: nil)

    optional NUID for the On-Behalf-Of header. Falls through to AtlasRb.config.default_on_behalf_of when omitted.

Returns:

  • (Hash)

    the updated "compilation" object — the response is the full recipe, so chip counts refresh without a follow-up find.

Raises:



236
237
238
239
240
241
# File 'lib/atlas_rb/compilation.rb', line 236

def self.add_included_collection(id, collection_id, nuid: nil, on_behalf_of: nil)
  AtlasRb::Mash.new(JSON.parse(
    connection({ collection_id: collection_id }, nuid, on_behalf_of: on_behalf_of)
      .post(ROUTE + id + '/included_collections')&.body
  ))["compilation"]
end

.add_included_work(id, work_id, nuid: nil, on_behalf_of: nil) ⇒ Hash

Add an include-work recipe line: one Work, included individually.

Idempotent. The noid must resolve to a Work; anything else is a 422.

Examples:

AtlasRb::Compilation.add_included_work("c-123", "w-789", nuid: "000000002")

Parameters:

  • id (String)

    the Compilation ID.

  • work_id (String)

    the Work NOID to include.

  • nuid (String, nil) (defaults to: nil)

    optional acting user's NUID. On the relay-signing path it is signed into the assertion sub; on the BYO-JWT (ATLAS_JWT) path it is ignored (identity lives in the token).

  • on_behalf_of (String, nil) (defaults to: nil)

    optional NUID for the On-Behalf-Of header. Falls through to AtlasRb.config.default_on_behalf_of when omitted.

Returns:

  • (Hash)

    the updated "compilation" object.

Raises:



286
287
288
289
290
291
# File 'lib/atlas_rb/compilation.rb', line 286

def self.add_included_work(id, work_id, nuid: nil, on_behalf_of: nil)
  AtlasRb::Mash.new(JSON.parse(
    connection({ work_id: work_id }, nuid, on_behalf_of: on_behalf_of)
      .post(ROUTE + id + '/included_works')&.body
  ))["compilation"]
end

.contents(id, page: nil, per_page: nil, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash

Resolve a Compilation's recipe into the Works it currently denotes.

Wraps GET /compilations/<id>/contents — included for completeness; the endpoint's primary consumer is CERES (which calls Atlas directly), and Cerberus resolves Set contents via its own Blacklight query. Results are gated to what the caller may discover (public + the caller's groups; admins see everything; tombstoned works excluded) — the same semantics as Cerberus gated discovery.

Examples:

page = AtlasRb::Compilation.contents("c-123", nuid: "000000002")
page.contents.map(&:noid)
page.pagination.total

Parameters:

  • id (String)

    the Compilation ID.

  • page (Integer, nil) (defaults to: nil)

    1-indexed page number (default 1).

  • per_page (Integer, nil) (defaults to: nil)

    page size (default 25, capped at 100).

  • nuid (String, nil) (defaults to: nil)

    optional acting user's NUID. On the relay-signing path it is signed into the assertion sub; on the BYO-JWT (ATLAS_JWT) path it is ignored (identity lives in the token).

  • on_behalf_of (String, nil) (defaults to: nil)

    optional NUID for the On-Behalf-Of header. Falls through to AtlasRb.config.default_on_behalf_of when omitted.

Returns:

  • (AtlasRb::Mash)

    { "contents" => [...], "pagination" => { "total", "page", "per_page", "pages" } }. Each entry is a lightweight digest in the Resource.find_many vocabulary — id / noid / klass / title / thumbnail.

Raises:



393
394
395
396
397
398
399
400
# File 'lib/atlas_rb/compilation.rb', line 393

def self.contents(id, page: nil, per_page: nil, nuid: nil, on_behalf_of: nil)
  params = {}
  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 + id + '/contents')&.body
  ))
end

.create(title, description: nil, nuid: nil, on_behalf_of: nil) ⇒ Hash

Create a Compilation owned by the acting user.

The depositor (owner) is stamped server-side from the authenticated NUID — it is not a parameter and is immutable post-create. New Sets are born private: empty ACLs, no staff default.

Examples:

AtlasRb::Compilation.create("Course readings",
                            description: "HIST 1101",
                            nuid: "000000002")

Parameters:

  • title (String)

    the Set's title (required; blank is a 422).

  • description (String, nil) (defaults to: nil)

    optional free-text description.

  • nuid (String, nil) (defaults to: nil)

    the acting user's NUID — signed into the assertion sub on the relay-signing path, and the created Set's owner.

  • on_behalf_of (String, nil) (defaults to: nil)

    optional NUID for the On-Behalf-Of header. Falls through to AtlasRb.config.default_on_behalf_of when omitted.

Returns:

  • (Hash)

    the created "compilation" object, already unwrapped.

Raises:



143
144
145
146
147
148
149
# File 'lib/atlas_rb/compilation.rb', line 143

def self.create(title, description: nil, nuid: nil, on_behalf_of: nil)
  params = { title: title }
  params[:description] = description if description
  AtlasRb::Mash.new(JSON.parse(
    connection(params, nuid, on_behalf_of: on_behalf_of).post(ROUTE)&.body
  ))["compilation"]
end

.destroy(id, nuid: nil, on_behalf_of: nil) ⇒ Faraday::Response

Destroy a Compilation.

Owner (or edit-grantee / admin) only. The recipe rows go with it; the Works and Collections it referenced are untouched — a Set is a view, not a container.

Examples:

AtlasRb::Compilation.destroy("c-123", nuid: "000000002")

Parameters:

  • id (String)

    the Compilation ID.

  • nuid (String, nil) (defaults to: nil)

    optional acting user's NUID. On the relay-signing path it is signed into the assertion sub; on the BYO-JWT (ATLAS_JWT) path it is ignored (identity lives in the token).

  • on_behalf_of (String, nil) (defaults to: nil)

    optional NUID for the On-Behalf-Of header. Falls through to AtlasRb.config.default_on_behalf_of when omitted.

Returns:

  • (Faraday::Response)

    the raw response. Status 204 on success.

Raises:



210
211
212
# File 'lib/atlas_rb/compilation.rb', line 210

def self.destroy(id, nuid: nil, on_behalf_of: nil)
  connection({}, nuid, on_behalf_of: on_behalf_of).delete(ROUTE + id)
end

.find(id, nuid: nil, on_behalf_of: nil) ⇒ Hash

Fetch a single Compilation by ID.

Visibility is per-row: the owner, holders of an explicit read/edit grant, and (for public Sets) anyone — a private Set read by a non-grantee raises ForbiddenError.

Examples:

AtlasRb::Compilation.find("c-123", nuid: "000000002")
# => { "id" => "c-123", "title" => "Course readings", ... }

Parameters:

  • id (String)

    the Compilation ID (NOID).

  • nuid (String, nil) (defaults to: nil)

    optional acting user's NUID. On the relay-signing path it is signed into the assertion sub; on the BYO-JWT (ATLAS_JWT) path it is ignored (identity lives in the token).

  • on_behalf_of (String, nil) (defaults to: nil)

    optional NUID for the On-Behalf-Of header. Falls through to AtlasRb.config.default_on_behalf_of when omitted.

Returns:

  • (Hash)

    the "compilation" object, already unwrapped — id, title, description, depositor, the three recipe arrays (included_collections, included_works, excluded_works), the ACL arrays, and timestamps.

Raises:



50
51
52
53
54
# File 'lib/atlas_rb/compilation.rb', line 50

def self.find(id, nuid: nil, on_behalf_of: nil)
  AtlasRb::Mash.new(JSON.parse(
    connection({}, nuid, on_behalf_of: on_behalf_of).get(ROUTE + id)&.body
  ))["compilation"]
end

.list(owner: nil, scope: nil, q: nil, page: nil, per_page: nil, nuid: nil, on_behalf_of: nil) ⇒ AtlasRb::Mash

List Compilations, paginated (newest first), in one of three modes.

Default (no scope:) is owner-scoped: the acting user's own Sets. Pass owner: to list another user's — admin-only, raising ForbiddenError for anyone else; there is no public browse surface.

Pass scope: for grant-scoped discovery — Sets where the acting user is a grantee but not the owner (owned Sets are always excluded; list those with the default mode):

  • scope: :editable — Sets the caller may edit (edit_users / edit_groups grants).
  • scope: :shared — Sets shared with the caller (read_groups grants, plus the edit grants that imply read). Grant-scoped modes are keyed on the acting user; owner: is ignored and group membership is resolved server-side. An unknown scope: is a 400.

Pass q: to narrow by case-insensitive title substring in any mode; the filter applies before pagination, so the "pagination" block describes the filtered result.

Examples:

My Sets

AtlasRb::Compilation.list(nuid: "000000002")

Another user's Sets (admin)

AtlasRb::Compilation.list(owner: "000000002", nuid: "000000004")

Sets shared with me that I can edit (not owned)

AtlasRb::Compilation.list(scope: :editable, nuid: "000000002")

Sets shared with me to view (read + edit grants, not owned)

AtlasRb::Compilation.list(scope: :shared, nuid: "000000002")

Title typeahead

AtlasRb::Compilation.list(q: "course", nuid: "000000002")

Parameters:

  • owner (String, nil) (defaults to: nil)

    NUID whose Sets to list (admin-only when it isn't the acting user). Omit for "my Sets". Ignored when scope: is set.

  • scope (Symbol, String, nil) (defaults to: nil)

    grant-scoped mode — :editable or :shared. Omit for the owner-scoped default.

  • q (String, nil) (defaults to: nil)

    case-insensitive title substring filter.

  • page (Integer, nil) (defaults to: nil)

    1-indexed page number.

  • per_page (Integer, nil) (defaults to: nil)

    page size override.

  • nuid (String, nil) (defaults to: nil)

    optional acting user's NUID. On the relay-signing path it is signed into the assertion sub; on the BYO-JWT (ATLAS_JWT) path it is ignored (identity lives in the token).

  • on_behalf_of (String, nil) (defaults to: nil)

    optional NUID for the On-Behalf-Of header. Falls through to AtlasRb.config.default_on_behalf_of when omitted.

Returns:

  • (AtlasRb::Mash)

    { "compilations" => [...], "pagination" => {...} }. Each entry wraps the same "compilation" object find returns.

Raises:



108
109
110
111
112
113
114
115
116
117
118
# File 'lib/atlas_rb/compilation.rb', line 108

def self.list(owner: nil, scope: nil, q: nil, page: nil, per_page: nil, nuid: nil, on_behalf_of: nil)
  params = {}
  params[:owner]    = owner    if owner
  params[:scope]    = scope    if scope
  params[:q]        = q        if q
  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

.remove_exclusion(id, work_id, nuid: nil, on_behalf_of: nil) ⇒ Hash

Put a set-aside Work back. Idempotent.

Examples:

AtlasRb::Compilation.remove_exclusion("c-123", "w-789", nuid: "000000002")

Parameters:

  • id (String)

    the Compilation ID.

  • work_id (String)

    the Work NOID to restore to the resolved set.

  • nuid (String, nil) (defaults to: nil)

    optional acting user's NUID. On the relay-signing path it is signed into the assertion sub; on the BYO-JWT (ATLAS_JWT) path it is ignored (identity lives in the token).

  • on_behalf_of (String, nil) (defaults to: nil)

    optional NUID for the On-Behalf-Of header. Falls through to AtlasRb.config.default_on_behalf_of when omitted.

Returns:

  • (Hash)

    the updated "compilation" object.

Raises:



358
359
360
361
362
363
# File 'lib/atlas_rb/compilation.rb', line 358

def self.remove_exclusion(id, work_id, nuid: nil, on_behalf_of: nil)
  AtlasRb::Mash.new(JSON.parse(
    connection({}, nuid, on_behalf_of: on_behalf_of)
      .delete(ROUTE + id + '/exclusions/' + work_id)&.body
  ))["compilation"]
end

.remove_included_collection(id, collection_id, nuid: nil, on_behalf_of: nil) ⇒ Hash

Remove an include-collection recipe line.

Idempotent — removing a collection that is not in the recipe is a 200 no-op (nothing for a client to recover from).

Examples:

AtlasRb::Compilation.remove_included_collection("c-123", "col-456", nuid: "000000002")

Parameters:

  • id (String)

    the Compilation ID.

  • collection_id (String)

    the Collection NOID to remove.

  • nuid (String, nil) (defaults to: nil)

    optional acting user's NUID. On the relay-signing path it is signed into the assertion sub; on the BYO-JWT (ATLAS_JWT) path it is ignored (identity lives in the token).

  • on_behalf_of (String, nil) (defaults to: nil)

    optional NUID for the On-Behalf-Of header. Falls through to AtlasRb.config.default_on_behalf_of when omitted.

Returns:

  • (Hash)

    the updated "compilation" object.

Raises:



261
262
263
264
265
266
# File 'lib/atlas_rb/compilation.rb', line 261

def self.remove_included_collection(id, collection_id, nuid: nil, on_behalf_of: nil)
  AtlasRb::Mash.new(JSON.parse(
    connection({}, nuid, on_behalf_of: on_behalf_of)
      .delete(ROUTE + id + '/included_collections/' + collection_id)&.body
  ))["compilation"]
end

.remove_included_work(id, work_id, nuid: nil, on_behalf_of: nil) ⇒ Hash

Remove an include-work recipe line. Idempotent.

Examples:

AtlasRb::Compilation.remove_included_work("c-123", "w-789", nuid: "000000002")

Parameters:

  • id (String)

    the Compilation ID.

  • work_id (String)

    the Work NOID to remove.

  • nuid (String, nil) (defaults to: nil)

    optional acting user's NUID. On the relay-signing path it is signed into the assertion sub; on the BYO-JWT (ATLAS_JWT) path it is ignored (identity lives in the token).

  • on_behalf_of (String, nil) (defaults to: nil)

    optional NUID for the On-Behalf-Of header. Falls through to AtlasRb.config.default_on_behalf_of when omitted.

Returns:

  • (Hash)

    the updated "compilation" object.

Raises:



308
309
310
311
312
313
# File 'lib/atlas_rb/compilation.rb', line 308

def self.remove_included_work(id, work_id, nuid: nil, on_behalf_of: nil)
  AtlasRb::Mash.new(JSON.parse(
    connection({}, nuid, on_behalf_of: on_behalf_of)
      .delete(ROUTE + id + '/included_works/' + work_id)&.body
  ))["compilation"]
end

.update(id, title: nil, description: nil, permissions: nil, nuid: nil, on_behalf_of: nil) ⇒ Hash

Update a Compilation's title / description / ACL.

Only the keys you pass are written. The permissions: hash replaces all three grant lists at once (read: / edit: group lists plus edit_users: NUIDs); the depositor is never writable. Server-side, an ACL change emits a permissions audit event (no-op ACL writes are suppressed); recipe membership has its own calls and emits nothing.

Examples:

Rename

AtlasRb::Compilation.update("c-123", title: "Renamed", nuid: "000000002")

Make public (the CERES case)

AtlasRb::Compilation.update("c-123",
                            permissions: { read: ["public"], edit: [], edit_users: [] },
                            nuid: "000000002")

Parameters:

  • id (String)

    the Compilation ID.

  • title (String, nil) (defaults to: nil)

    new title.

  • description (String, nil) (defaults to: nil)

    new description.

  • permissions (Hash, nil) (defaults to: nil)

    ACL replacement, e.g. { read: ["public"], edit: [], edit_users: ["000000003"] }.

  • nuid (String, nil) (defaults to: nil)

    optional acting user's NUID. On the relay-signing path it is signed into the assertion sub; on the BYO-JWT (ATLAS_JWT) path it is ignored (identity lives in the token).

  • on_behalf_of (String, nil) (defaults to: nil)

    optional NUID for the On-Behalf-Of header. Falls through to AtlasRb.config.default_on_behalf_of when omitted.

Returns:

  • (Hash)

    the updated "compilation" object, already unwrapped.

Raises:



182
183
184
185
186
187
188
189
190
# File 'lib/atlas_rb/compilation.rb', line 182

def self.update(id, title: nil, description: nil, permissions: nil, nuid: nil, on_behalf_of: nil)
  params = {}
  params[:title]       = title       if title
  params[:description] = description if description
  params[:permissions] = permissions if permissions
  AtlasRb::Mash.new(JSON.parse(
    connection(params, nuid, on_behalf_of: on_behalf_of).patch(ROUTE + id)&.body
  ))["compilation"]
end