fix: /documentation NameError from sinatra-contrib 4.2.1 namespace bug#213
Merged
Merged
Conversation
Member
Author
|
Pre-fix review flagged that Addressed in 65e4379: extracted a Verified against the 4store backend: Two small stylistic choices worth noting:
|
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.
alexskr
force-pushed
the
fix/documentation-route-namespace-issue-212
branch
from
April 23, 2026 05:13
619c1f1 to
2b105ef
Compare
This was referenced Apr 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
namespace "/"into a newhelpers/home_helper.rb(Sinatra::Helpers::HomeHelper). Fixes the primary bug:/documentationand/metadata/:classraisingNameError: undefined local variable or method 'metadata_all'undersinatra-contrib4.2.1 +mustermann3.0, because the namespace's before-filter compiles for"/"to a pattern that only matches the root path, so the module that carries the helpers is never mixed into the request instance for sub-paths. Same structural fix agroportal used.cardinalitycolumn fromHomeHelper#metadata_all. The producer (goo'scls.cardinality(attr)) was removed in Refactor: clean model settings module code agroportal/goo#52 and synced into ncbo/goo via Sync: align ncbo/goo with the AgroPortal codebase goo#176 (April 2024); the consumer views never readvalues[:cardinality]anyway. See issue comment for details.pandoc-rubyforkramdownin theGemfile. The:markdownfilter inviews/documentation/documentation.hamlwas resolving to Tilt'sPandocTemplate, which shells out to apandocbinary that isn't present in ourontoportal/testkit-baseimage (agroportal installs it explicitly in their Dockerfile; we'd rather avoid a system-binary dep for four prose blocks).kramdownis pure Ruby, already known to Tilt, no compile step, no cross-arch churn.test_documentation_route_renderstotest/controllers/test_home_controller.rbso this doesn't silently regress again.Fixes #212
Bonus: also restores long-broken
/metadata/:classendpointsThe same namespace fix revives
GET /metadata/:classfor every validLinkedData::Models::*class, e.g.LinkedData::Models::Metric/metadata/Slice,/metadata/ProvisionalClass,/metadata/OntologyType,/metadata/Reply, etc.These URLs are listed in the long-standing #37 ("broken metadata resources", open since 2017). They've been returning 500s for years — under the prior Sinatra 1.4 stack the specific failure was different, but the user-visible symptom was the same, and the namespace-helper-resolution bug in Sinatra 4.2.1 kept them broken post-migration too. This PR closes out the class-name cases from that issue.
Note: issue #37 also lists property-name paths like
/metadata/prefixIRI,/metadata/name,/metadata/omv*. Those aren't valid model class names and previously 500'd with aNameError; this PR makes them return a clean 404 with the message'X' is not a documented media type. See /documentation or try /metadata/Ontology.(commitd2e5b64). Making/metadata/:nameactually render documentation for those attribute URIs — which are legitimately emitted by the JSON-LD serializer in@context— is a separate design question tracked in ncbo/bioportal-project#388.Related: #37, ncbo/bioportal-project#388
Commits
b85ee2bCLASS_MAPintohelpers/home_helper.rb; add regression testtest_documentation_route_renders6303343cardinalitycolumn fromHomeHelper#metadata_all(producer removed in goo 2024)99d0df1pandoc-ruby→kramdowninGemfile/Gemfile.lock5ca5f8d/metadata/:class(Metrics,Reply)c37e214HomeHelper#metadata(sub-module walk forNotes::Replyetc.)d2e5b64/metadata/:classfor non-class names, worded with the BioPortal docs "media type" vocabulary and routed through theerrorhelper2b105efOntology,Metrics,Class); narrow 404 test to guaranteed-bogus names so future attribute-documentation work (ncbo/bioportal-project#388) doesn't read as a regressionTest plan
ruby -con all changed Ruby files.namespace "/"+helpers Moduleshape) confirms the NameError is gone —GET /documentationreturns 200 where it was 500.Tilt[:md]resolves toTilt::KramdownTemplate(notTilt::PandocTemplate).bundle exec ruby -Itest test/controllers/test_home_controller.rb --verboseagainst the 4store dev backend (viarake 'test:docker:up[fs]') — all 6 tests pass (32 assertions, 0 failures, 0 errors):test_documentation_route_renderstest_metadata_route_renders_for_model_class(GET /metadata/Ontology, /metadata/Metrics, /metadata/Class)test_metadata_route_resolves_submodule_class(GET /metadata/Reply →LinkedData::Models::Notes::Replyvia the sub-module walk added in c37e214)test_metadata_route_returns_404_for_bogus_names(guaranteed-nonexistent identifiers return 404 with themedia typemessage)test_home_index_returns_links_hashtest_home_index_handles_type_uri_failuresbundle exec rackupand curl/,/documentation,/metadata/Class,/metadata/Metrics,/metadata/Slice— all 200.Notes on deviations from agroportal
We adopted agroportal's structural fix (helper module extraction) verbatim but deviated on three secondary points:
metadata_all→get_metadata_allmetadata_allroutes_clslist inget_metadata_allObjectSpacediscovery viaroutes_by_classGET /and/metadata/:classroutes still rely on dynamic route→class mapping; agroportal removed those routes (MOD API catalog serving)pandocin Dockerfilekramdown