Skip to content

Develop -> Master merge, release v7.1.0#215

Merged
alexskr merged 13 commits intomasterfrom
develop
Apr 23, 2026
Merged

Develop -> Master merge, release v7.1.0#215
alexskr merged 13 commits intomasterfrom
develop

Conversation

@alexskr
Copy link
Copy Markdown
Member

@alexskr alexskr commented Apr 23, 2026

alexskr added 13 commits April 14, 2026 17:21
Previously, slice filtering only applied to collection responses (GET /ontologies)
because filter_for_slice checked obj.is_a?(Enumerable). Single ontology access
(GET /ontologies/:acronym) and all sub-routes (submissions, classes, etc.) bypassed
slice enforcement entirely.

Add a Sinatra before filter on /ontologies/:acronym routes that checks slice
membership and returns 404 for ontologies not in the current slice. Also memoize
slice lookups per request to avoid duplicate triplestore queries.
Canonicalize ontology ID construction in ontology_in_slice? to match
how slice.ontology_id_set entries are generated by Goo, avoiding
silent comparison failures if the acronym ever contains chars that
CGI.escape would encode.
Enforce slice-based access control on individual ontology endpoints
Under sinatra-contrib 4.2.1 + mustermann 3.0, the before-filter that
Sinatra::Namespace uses to mix its namespace module into each request
instance (`before { extend(@namespace = namespace) }`) compiles for
`namespace "/"` to a pattern that matches only `/` and not sub-paths
like `/documentation` or `/metadata/:class`. Methods defined with bare
`def` inside the namespace block were therefore unreachable from those
routes, producing:

  NameError: undefined local variable or method `metadata_all'
             for #<Sinatra::Application:...>

Move the home-controller helpers (metadata_all, metadata,
resource_collection_link, sample_objects, hypermedia_links,
routes_by_class, route_to_class_map, routes_list, parse_route,
safe_type_uri, and CLASS_MAP) out of `namespace "/"` into a proper
`Sinatra::Helpers::HomeHelper` module registered via the top-level
`helpers` DSL — the same shape every other helper in this repo uses.
Helpers now attach to Sinatra::Application at class-load time and no
longer depend on the namespace before-filter firing.

Add a regression test that exercises GET /documentation.

Fixes #212
The producer (`cls.cardinality(attribute)`) was removed from goo in the
AgroPortal fork in agroportal/goo#52 (commit 2206084, merged
2024-01-31) and subsequently synced into ncbo/goo via ncbo/goo#176 on
2024-04-03. The consumer — the `views/documentation/*.haml` tables —
has no reference to `values[:cardinality]` either. The key in the
attributes_info hash was being populated only to be thrown away.

Delete the key from both branches of the conditional. No view change
needed; the earlier `rescue nil` guard added in 1fe44f8 becomes
unnecessary and is removed with it.
`views/documentation/documentation.haml` contains four `:markdown`
filter blocks that HAML delegates to Tilt. In the current bundle the
only markdown-capable gem was `pandoc-ruby`, so Tilt selected its
PandocTemplate, which shells out to an external `pandoc` binary. Our
Docker image inherits from `ontoportal/testkit-base` and does not
install that binary, so `/documentation` failed with
`RuntimeError: sh: pandoc: command not found` in any environment
without pandoc on the PATH.

Replace `pandoc-ruby` with `kramdown`, a pure-Ruby renderer already
known to Tilt (`tilt/kramdown.rb`). This removes the system-binary
dependency entirely rather than matching agroportal's approach of
installing pandoc in their Dockerfile. Performance is indistinguishable
for four short blocks on a rarely-hit developer page; pure-Ruby avoids
cross-architecture compile/install churn across containers.

Verified post-install: `Tilt[:md] → Tilt::KramdownTemplate`.
The home-helper extraction also restores `GET /metadata/:class`, which
had been broken for years (#37) and was broken again
for a different reason under sinatra-contrib 4.2.1
(#212). Lock the fix in with two tests:

- /metadata/Metrics — exercises the common case (singularize +
  top-level LinkedData::Models::Metric lookup).
- /metadata/Reply — exercises HomeHelper#routes_by_class's sub-module
  search branch (LinkedData::Models::Notes::Reply).
`metadata(cls)` did a bare `LinkedData::Models.const_get(name)`, so
requests like `GET /metadata/Reply` blew up with
`NameError: uninitialized constant LinkedData::Models::Reply` —
`Reply` lives under `LinkedData::Models::Notes`, not at the top level.
`routes_by_class` already has sub-module search for the same reason;
`metadata` was missing the mirror.

Extract a small `resolve_model_class(name)` helper that tries the
top-level lookup first and falls back to scanning
`LinkedData::Models.constants` for the name, matching the behavior of
`routes_by_class`. This restores `GET /metadata/Reply` and any other
nested model class (e.g. `Users::Role`, `Users::NotificationType`).

test_metadata_route_resolves_submodule_class now passes.
The previous "No metadata for \`X\` — not a LinkedData model class"
leaked the Ruby gem name and used "model class", terms API users don't
encounter elsewhere. The BioPortal /documentation page uses "media
type" for exactly these /metadata/:X URIs (they surface as JSON-LD
@type / @context identifiers in responses, documented under "Media
Types and Hypermedia Links"), so match that vocabulary and point the
user at a concrete working example.

Also route the response through the repo's `error` helper so the 404
comes back as the standard `{ errors: [...], status: 404 }` shape
through LinkedData's content-negotiating serializer, matching every
other error response in the codebase (no more one-off `halt 404,
"string"`).

Strengthen the regression test to assert the new phrasing appears in
the body, not just the status code.
- Extend `test_metadata_route_renders_for_model_class` to loop over
  `Ontology`, `Metrics`, `Class` — covers plain top-level lookup
  (`Ontology`), singularization (`Metrics` → `Metric`), and a second
  plain case with a distinctive name (`Class`). All three hit the same
  `resolve_model_class` top-level branch, but `Ontology` was the one
  case we previously had no explicit coverage for.

- Rename `test_metadata_route_returns_404_for_non_class_names` →
  `test_metadata_route_returns_404_for_bogus_names` and trim its list
  to `DefinitelyNotAModel`, `nonexistent_thing`, `xyzqux123` —
  guaranteed-nonexistent identifiers that will stay 404 regardless of
  future features.

  The previous list (`created`, `body`, `prefixIRI`, `name`,
  `omvacronym`) was misleading: those *are* attribute URIs the API
  emits in JSON-LD @context, currently 404'd as a stop-gap but tracked
  in ncbo/bioportal-project#388 for actual attribute-documentation
  rendering. Leaving them asserted as 404 would turn into a false
  regression signal when #388 lands.
…ssue-212

fix: /documentation NameError from sinatra-contrib 4.2.1 namespace bug
@alexskr alexskr self-assigned this Apr 23, 2026
@alexskr alexskr merged commit 73f4fea into master Apr 23, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant