atlas_rb
Ruby client for the Atlas API — Northeastern University's institutional digital repository.
The gem wraps Atlas's REST endpoints in a small set of class-method-only modules, one per resource type. There is no client object to instantiate; calls are made directly on the resource class:
AtlasRb::Work.find("w-789")
Installation
Add to your Gemfile:
gem "atlas_rb"
Then bundle install, or install standalone with gem install atlas_rb.
Configuration
Environment variables
Every regular-path request reads two environment variables:
| Variable | Purpose |
|---|---|
ATLAS_URL |
Base URL of the Atlas API (e.g. https://atlas.example.edu). |
ATLAS_TOKEN |
Bearer token used in the Authorization header. |
ENV["ATLAS_URL"] = "https://atlas.example.edu"
ENV["ATLAS_TOKEN"] = "..."
Ambient identity (default_nuid / default_on_behalf_of)
Every resource method that talks to Atlas accepts a nuid: kwarg (the
acting user) and an on_behalf_of: kwarg (the user the call is being
made for, used by acting-as / view-as flows). Both are forwarded as
User: NUID <nuid> and On-Behalf-Of: NUID <nuid> headers
respectively.
Rather than threading them at every call site, register callables once on app boot and let the gem read them as defaults:
# config/initializers/atlas_rb.rb (Rails)
AtlasRb.configure do |config|
config.default_nuid = -> { Current.nuid }
config.default_on_behalf_of = -> { Current.on_behalf_of }
end
The lambdas run at request time in whatever thread / fiber is
making the call, so they pick up per-request Current.* values that
ApplicationController set up via Devise + Rails 7's
ActiveSupport::CurrentAttributes. Background jobs work the same way
(ActiveJob ↔ CurrentAttributes integration restores the values on
perform).
Caller-passed kwargs always win over the configured defaults:
# Uses Current.nuid:
AtlasRb::Work.find("w-789")
# Uses "X" — explicit kwarg overrides the default:
AtlasRb::Work.find("w-789", nuid: "X")
If neither the call site nor the registered default supplies a value, no header is sent (legacy bearer-only path preserved).
System-path credentials
Calls under AtlasRb::System::* (currently just SSO user provisioning)
authenticate as the seeded Atlas :system fixture, not as a real user.
They use a separate bearer token, looked up from
Rails.application.credentials.atlas_system_token. Storing it in
encrypted credentials rather than ENV halves the blast radius of a
.env leak — the user token and the system token can't both leak
through the same channel.
# config/credentials.yml.enc (Cerberus side)
atlas_system_token: <token-Atlas-side-recognises-as-:system>
The system NUID itself is hardcoded as AtlasRb::System::NUID =
"000000000", matching Atlas's seeded :system fixture row.
Resource hierarchy
Community → Collection → Work
↓
FileSet
↓
Blob
| Class | Represents |
|---|---|
AtlasRb::Community |
Top-level org unit; may nest sub-Communities. |
AtlasRb::Collection |
Holds Works; lives directly under a Community. |
AtlasRb::Work |
Bibliographic unit (article, thesis, dataset…); MODS metadata lives here. |
AtlasRb::FileSet |
Classified slot under a Work (e.g. "primary", "supplemental"). |
AtlasRb::Blob |
The binary bytes; supports streaming downloads. |
AtlasRb::Authentication |
NUID → user record / group lookup. |
AtlasRb::Resource |
Generic resolver and permissions lookup. |
AtlasRb::Reset |
Test-only — wipes Atlas state via GET /reset. |
Namespace gradient: regular / Admin / System
Operations are split across three namespaces, calibrated to the blast radius and the kind of authentication they need:
| Namespace | What it does | Auth | Friction |
|---|---|---|---|
AtlasRb::* |
Regular CRUD (find / list / create / update / tombstone / metadata, etc.) | User token (ATLAS_TOKEN) + acting user's NUID |
None — these are the daily-use paths. |
AtlasRb::Admin::* |
Hard delete (destroy) and un-tombstone (restore) for Work / Collection / Community. |
Same as regular — a real operator is acting. | destroy requires confirm: :i_understand. |
AtlasRb::System::* |
System-context provisioning (currently just SSO user find-or-create). | System token (Rails.application.credentials.atlas_system_token) + User: NUID 000000000. |
The namespace itself is the marker — there is no way to call these as a non-system principal. |
# Regular daily use — picks up Current.nuid via the configured default:
AtlasRb::Work.find("w-789")
AtlasRb::Work.tombstone("w-789") # withdrawal (reversible)
# Operator-only, with a friction marker:
AtlasRb::Admin::Work.destroy("w-789", confirm: :i_understand)
AtlasRb::Admin::Work.restore("w-789")
# System-only — authenticates as Atlas's :system fixture:
AtlasRb::System::User.find_or_create(
nuid: "001234567",
groups: ["northeastern:staff", "drs:editors"]
)
The AtlasRb::System::* path never consults AtlasRb.config.default_nuid
or default_on_behalf_of — there is no ambient user context on system
calls.
A note on create argument shapes
The CRUD-twin classes look the same but pass different parent IDs:
AtlasRb::Community.create(nil) # top-level community (parent_id: nil)
AtlasRb::Community.create("c-123") # sub-community of c-123
AtlasRb::Collection.create("c-123") # collection under community c-123
AtlasRb::Work.create("col-456") # work under collection col-456 (collection_id, not parent_id)
AtlasRb::FileSet.create("w-789", "primary") # file_set under work w-789, classification "primary"
AtlasRb::Blob.create("w-789", path, name) # blob under work w-789 with original filename preserved
Work.create, FileSet.create, and Blob.create each accept an optional
idempotency_key: kwarg for retry-safe bulk-deposit jobs. The caller
generates the UUID; the Atlas server enforces uniqueness scoped to the
acting user. A repeat call with the same key returns the originally-created
resource (or 410 if it has since been tombstoned). The gem does not
generate keys, cache responses, or retry — those concerns belong to the
calling job runner (e.g. Cerberus's Solid Queue).
key = SecureRandom.uuid
AtlasRb::Work.create("col-456", idempotency_key: key)
AtlasRb::FileSet.create("w-789", "primary", idempotency_key: key)
AtlasRb::Blob.create("w-789", path, name, idempotency_key: key)
Listing and monitoring Works
Work.list exposes the paginated GET /works index, with an optional
in_progress: filter for finding deposits that haven't yet been marked
complete. Work.complete flips a Work's in_progress flag to false
once a bulk-deposit job confirms all expected children have been deposited.
AtlasRb::Work.list(in_progress: true) # stuck deposits
AtlasRb::Work.list(in_progress: false, page: 2) # completed deposits, page 2
AtlasRb::Work.complete("w-789") # mark w-789 done
End-to-end example
JSON responses come back as AtlasRb::Mash (a Hashie::Mash subclass), so
you can use dot access — community.id — or string-keyed access —
community["id"] — interchangeably. Both return the same value, so existing
string-keyed callers keep working.
require "atlas_rb"
ENV["ATLAS_URL"] = "https://atlas.example.edu"
ENV["ATLAS_TOKEN"] = "..."
# 1. Build the org structure (each create can optionally seed MODS metadata).
community = AtlasRb::Community.create(nil, "/tmp/community-mods.xml")
collection = AtlasRb::Collection.create(community.id, "/tmp/coll-mods.xml")
work = AtlasRb::Work.create(collection.id, "/tmp/work-mods.xml")
# 2. Upload a binary attached to the work, preserving the user-facing filename.
blob = AtlasRb::Blob.create(work.id, "/tmp/upload.tmp", "thesis.pdf")
# 3. List everything attached to the work.
AtlasRb::Work.assets(work.id)
# 4. Stream the binary back without buffering it in memory.
File.open("out.pdf", "wb") do |f|
headers = AtlasRb::Blob.content(blob.id) { |chunk| f.write(chunk) }
puts headers["content-type"]
end
# 5. Look up the acting user and their groups.
AtlasRb::Authentication.login("001234567")
AtlasRb::Authentication.groups("001234567")
Generated documentation
Full API reference, including @param / @return / @example for every
method, is generated with YARD:
bundle exec yard doc
open doc/index.html
yard stats --list-undoc should report 100% coverage.
Development
bin/setup # install dependencies
bin/console # IRB with atlas_rb loaded
bundle exec rspec # run tests
bundle exec rubocop # lint
To cut a release, bump the version in .version (which lib/atlas_rb/version.rb
reads at load time) and run bundle exec rake release.
License
MIT — see LICENSE.txt.