[BAC-1482] Add read_only attribute support - #25
Merged
Conversation
Adds a `read_only: true` option on `attribute`, emitted as `readOnly: true`
on that property in the generated JSON schema:
attribute :balance, :entity, class_name: "Entities::Businesses::Balance", read_only: true
The option is stripped before `super`, since ActiveModel forwards `**options`
into `Type.lookup` and every symbolic type raises ArgumentError on an unknown
keyword.
`readOnly` on a `$ref` property is wrapped in `allOf`, mirroring the existing
`make_schema_nullable!` -- an OpenAPI 3.0 sibling of `$ref` is ignored, which is
exactly the motivating case. The two are idempotent together.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnpMsUXxckwovSfTfX37KK
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.
Closes BAC-1482. Blocks BAC-1481.
Supersedes #24, which was closed unmerged. That attempt built request/response schema variants, subtree walking and cycle handling; the ticket has since been rewritten to rule all of that out — "the gem does not need to generate request/response schema variants, strip properties, walk nested entities or handle reference cycles — the OpenAPI consumer does all of that from the annotation alone." This PR is only the annotation. Fresh branch so the diff reads clean; #24's history stays intact for reference.
What
emits
readOnly: trueon that property. 19 lines in the existingschemas/json.rb— no new lib file.Two things that are load-bearing and not obvious
read_onlyis stripped beforesuper.ActiveModel::Attributes#attributeforwards**optionsintoType.lookup, soActiveModel::Type::String.new(read_only: true)raisesArgumentErrorat class-definition time. Without the strip the option cannot be declared at all. The interception mirrors the existing one inMeta::Descriptions.readOnlyon a$refproperty is wrapped inallOf. In OpenAPI 3.0 a sibling of$refis ignored, so a barereadOnly: truenext to$refis silently dropped — which is exactly the motivating case (balanceis an:entityattribute).make_schema_read_only!mirrors the existingmake_schema_nullable!, and the two are idempotent together: nullable + read_only on one attribute yields oneallOfcarrying both flags.The duplicated unwrap line between the two methods is deliberate — extracting a shared helper would mean editing
make_schema_nullable!, and the copy reads as a mirror of the method directly above it.Registry
class_attribute :read_only_attributes, written by reassignment (self.read_only_attributes += [...]) rather than mutation. Reassignment creates a subclass-local override, so a subclass declaring a read-only attribute cannot leak it into its parent or a sibling. Mutating the shared default array can — and a mutation test confirmed the inheritance spec is what catches it (swapping+=for<<fails that example and nothing else).Coverage
Primitive,
:entity,:array-of-entity and:array-of-primitive each verified with and without the flag; plus the$refwrapping, read_only combined with nullable, and the inheritance guard. Also checked by hand: interaction withenum,desc,default:, untyped attributes, string attribute names, and theinline: truepath including nested entities.Verification
git diff --statis2 files changed, 82 insertions(+)— zero deletions. That is the mechanical proof for acceptance criterion 2: no pre-existing example, expectation or lib line was edited, including the big equality assertion inschemas/json_spec.rb.No
rubocop:disable, no.rubocop.ymledit, no new dependency, no README change. No version bump — karen pins this gem by git rev.Not done here
karen-side verification (bump the pinned rev, regenerate
swagger/v1/swagger.yamlunderRAILS_ENV=test,npm run oapi, diff to nothing with no markers adopted). It was blocked on this branch existing to pin. Worth running before merge.Unrelated, flagged only: the suite does not load on Ruby 4.0.x — activesupport 7.1.3.4 assumes stdlib gems Ruby 4 ejected. CI is on 3.3.0 and unaffected; run locally with
ASDF_RUBY_VERSION=3.3.0 bundle exec rspec.🤖 Generated with Claude Code
https://claude.ai/code/session_01WnpMsUXxckwovSfTfX37KK