diff --git a/lib/anchor/config.rb b/lib/anchor/config.rb index b42092e..ab459cb 100644 --- a/lib/anchor/config.rb +++ b/lib/anchor/config.rb @@ -9,7 +9,8 @@ class Config :use_type_as_schema_name, :maybe_as_union, :array_bracket_notation, - :infer_default_as_non_null + :infer_default_as_non_null, + :ar_comment_to_string def initialize @ar_column_to_type = nil @@ -22,6 +23,7 @@ def initialize @maybe_as_union = nil @array_bracket_notation = nil @infer_default_as_non_null = nil + @ar_comment_to_string = nil end end end diff --git a/lib/anchor/resource.rb b/lib/anchor/resource.rb index b5aa139..925c324 100644 --- a/lib/anchor/resource.rb +++ b/lib/anchor/resource.rb @@ -79,7 +79,12 @@ def anchor_attributes_properties(included_fields:) column = !method_defined && _model_class.try(:columns_hash).try(:[], model_method.to_s) if column type = Anchor::Types::Inference::ActiveRecord::SQL.from(column) - description ||= column.comment if Anchor.config.use_active_record_comment + unless description + description = column.comment if Anchor.config.use_active_record_comment + if description && !Anchor.config.ar_comment_to_string.nil? + description = Anchor.config.ar_comment_to_string.call(description) + end + end check_presence = type.is_a?(Anchor::Types::Maybe) && Anchor.config.use_active_record_validations if check_presence && _model_class.validators_on(model_method).any? do |v| if v.is_a?(ActiveRecord::Validations::NumericalityValidator) diff --git a/spec/example/app/resources/exhaustive_resource.rb b/spec/example/app/resources/exhaustive_resource.rb index 1571b0e..4b40721 100644 --- a/spec/example/app/resources/exhaustive_resource.rb +++ b/spec/example/app/resources/exhaustive_resource.rb @@ -43,6 +43,7 @@ class AssertedObject < Types::Object attribute :model_overridden attribute :resource_overridden attribute :with_comment + attribute :with_parsed_comment attribute :defaulted_boolean attribute :defaulted_at diff --git a/spec/example/config/initializers/anchor.rb b/spec/example/config/initializers/anchor.rb index 369a0f5..68f20ac 100644 --- a/spec/example/config/initializers/anchor.rb +++ b/spec/example/config/initializers/anchor.rb @@ -10,6 +10,14 @@ module Anchor return Types::Literal.new("never") if column.name == "loljk" Types::Inference::ActiveRecord::SQL.default_ar_column_to_type(column) } + c.ar_comment_to_string = lambda { |comment| + begin + res = JSON.parse(comment) + res["description"] + rescue JSON::ParserError + comment + end + } c.empty_relationship_type = -> { Anchor::Types::Object } end diff --git a/spec/example/db/migrate/20250920024446_add_with_parsed_comment_to_exhaustives.rb b/spec/example/db/migrate/20250920024446_add_with_parsed_comment_to_exhaustives.rb new file mode 100644 index 0000000..5ed370f --- /dev/null +++ b/spec/example/db/migrate/20250920024446_add_with_parsed_comment_to_exhaustives.rb @@ -0,0 +1,11 @@ +class AddWithParsedCommentToExhaustives < ActiveRecord::Migration[8.0] + def change + comment = <<~JSON + { + "description": "This is a parsed JSON comment.", + "test": 2 + } + JSON + add_column :exhaustives, :with_parsed_comment, :string, comment: + end +end diff --git a/spec/example/db/schema.rb b/spec/example/db/schema.rb index 522f826..ca52436 100644 --- a/spec/example/db/schema.rb +++ b/spec/example/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_07_30_180611) do +ActiveRecord::Schema[8.0].define(version: 2025_09_20_024446) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -60,6 +60,7 @@ t.string "with_comment", comment: "This is a comment." t.boolean "defaulted_boolean", default: false t.datetime "defaulted_at", default: -> { "now()" } + t.string "with_parsed_comment", comment: "{\n \"description\": \"This is a parsed JSON comment.\",\n \"test\": 2\n}\n" end create_table "posts", force: :cascade do |t| diff --git a/spec/example/test/files/all_fields_false_schema.ts b/spec/example/test/files/all_fields_false_schema.ts index baac37c..575753e 100644 --- a/spec/example/test/files/all_fields_false_schema.ts +++ b/spec/example/test/files/all_fields_false_schema.ts @@ -89,6 +89,8 @@ export type Exhaustive = { resourceOverridden: unknown; /** This is a comment. */ withComment: Maybe; + /** This is a parsed JSON comment. */ + withParsedComment: Maybe; defaultedBoolean: boolean; defaultedAt: string; relationships: {}; diff --git a/spec/example/test/files/excluded_fields_schema.ts b/spec/example/test/files/excluded_fields_schema.ts index 1c7d891..56626a6 100644 --- a/spec/example/test/files/excluded_fields_schema.ts +++ b/spec/example/test/files/excluded_fields_schema.ts @@ -87,6 +87,8 @@ export type Exhaustive = { resourceOverridden: unknown; /** This is a comment. */ withComment: Maybe; + /** This is a parsed JSON comment. */ + withParsedComment: Maybe; defaultedBoolean: boolean; defaultedAt: string; relationships: {}; diff --git a/spec/example/test/files/json_schema.json b/spec/example/test/files/json_schema.json index f34888f..7c16d48 100644 --- a/spec/example/test/files/json_schema.json +++ b/spec/example/test/files/json_schema.json @@ -1 +1 @@ -{"$schema":"https://json-schema.org/draft-07/schema","title":"Schema","type":"object","properties":{"comment":{"$ref":"#/$defs/Comment"},"user":{"$ref":"#/$defs/User"},"post":{"$ref":"#/$defs/Post"},"exhaustive":{"$ref":"#/$defs/Exhaustive"}},"required":["comment","user","post","exhaustive"],"additionalProperties":false,"$defs":{"Comment":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["comments"]},"text":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"relationships":{"type":"object","properties":{"user":{"$ref":"#/$defs/User"},"deletedBy":{"$ref":"#/$defs/User"},"commentable":{"oneOf":[{"$ref":"#/$defs/Post"}]}},"required":["user"],"additionalProperties":false}},"required":["id","type","text","createdAt","updatedAt","relationships"],"additionalProperties":false},"User":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["users"]},"name":{"type":"string"},"role":{"enum":[{"value":"admin"},{"value":"content_creator"},{"value":"external"},{"value":"guest"},{"value":"system"}]},"relationships":{"type":"object","properties":{"comments":{"type":"array","items":{"$ref":"#/$defs/Comment"}},"posts":{"type":"array","items":{"$ref":"#/$defs/Post"}}},"required":["comments","posts"],"additionalProperties":false}},"required":["id","type","name","role","relationships"],"additionalProperties":false},"Post":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["posts"]},"description":{"type":"string"},"relationships":{"type":"object","properties":{"user":{"$ref":"#/$defs/User"},"comments":{"type":"array","items":{"$ref":"#/$defs/Comment"}},"participants":{"type":"array","items":{"$ref":"#/$defs/User"}}},"required":["user","comments","participants"],"additionalProperties":false}},"required":["id","type","description","relationships"],"additionalProperties":false},"Exhaustive":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["exhaustives"]},"assertedString":{"type":"string"},"assertedNumber":{"type":"number"},"assertedBoolean":{"type":"boolean"},"assertedNull":{"type":"null"},"assertedUnknown":{},"assertedObject":{"type":"object","properties":{"a":{"enum":["a"]},"b-dash":{"enum":[1]},"c":{"oneOf":[{"type":"string"},{"type":"null"}]},"d_optional":{"oneOf":[{"type":"string"},{"type":"null"}]}},"required":["a","b-dash","c"],"additionalProperties":false},"assertedMaybeObject":{"oneOf":[{"type":"object","properties":{"a":{"enum":["a"]},"b-dash":{"enum":[1]},"c":{"oneOf":[{"type":"string"},{"type":"null"}]},"d_optional":{"oneOf":[{"type":"string"},{"type":"null"}]}},"required":["a","b-dash","c"],"additionalProperties":false},{"type":"null"}]},"assertedArrayRecord":{"type":"array","items":{"type":"object","additionalProperties":"true"}},"assertedUnion":{"oneOf":[{"type":"string"},{"type":"number"}]},"assertedUnionArray":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]}},"withDescription":{"type":"string"},"inferredUnknown":{},"uuid":{"type":"string"},"string":{"type":"string"},"maybeString":{"type":"string"},"text":{"type":"string"},"integer":{"type":"number"},"float":{"type":"number"},"decimal":{"type":"string"},"datetime":{"type":"string"},"timestamp":{"type":"string"},"time":{"type":"string"},"date":{"type":"string"},"boolean":{"type":"boolean"},"arrayString":{"type":"array","items":{"type":"string"}},"maybeArrayString":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"json":{"type":"object","additionalProperties":"true"},"jsonb":{"type":"object","additionalProperties":"true"},"daterange":{},"enum":{},"virtualUpcasedString":{"oneOf":[{"type":"string"},{"type":"null"}]},"loljk":{"enum":["never"]},"delegatedMaybeString":{"type":"string"},"modelOverridden":{},"resourceOverridden":{},"withComment":{"oneOf":[{"type":"string"},{"type":"null"}]},"defaultedBoolean":{"type":"boolean"},"defaultedAt":{"type":"string"}},"required":["id","type","assertedString","assertedNumber","assertedBoolean","assertedNull","assertedUnknown","assertedObject","assertedMaybeObject","assertedArrayRecord","assertedUnion","assertedUnionArray","withDescription","inferredUnknown","uuid","string","maybeString","text","integer","float","decimal","datetime","timestamp","time","date","boolean","arrayString","maybeArrayString","json","jsonb","daterange","enum","virtualUpcasedString","loljk","delegatedMaybeString","modelOverridden","resourceOverridden","withComment","defaultedBoolean","defaultedAt"],"additionalProperties":false}}} \ No newline at end of file +{"$schema":"https://json-schema.org/draft-07/schema","title":"Schema","type":"object","properties":{"comment":{"$ref":"#/$defs/Comment"},"user":{"$ref":"#/$defs/User"},"post":{"$ref":"#/$defs/Post"},"exhaustive":{"$ref":"#/$defs/Exhaustive"}},"required":["comment","user","post","exhaustive"],"additionalProperties":false,"$defs":{"Comment":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["comments"]},"text":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"},"relationships":{"type":"object","properties":{"user":{"$ref":"#/$defs/User"},"deletedBy":{"$ref":"#/$defs/User"},"commentable":{"oneOf":[{"$ref":"#/$defs/Post"}]}},"required":["user"],"additionalProperties":false}},"required":["id","type","text","createdAt","updatedAt","relationships"],"additionalProperties":false},"User":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["users"]},"name":{"type":"string"},"role":{"enum":[{"value":"admin"},{"value":"content_creator"},{"value":"external"},{"value":"guest"},{"value":"system"}]},"relationships":{"type":"object","properties":{"comments":{"type":"array","items":{"$ref":"#/$defs/Comment"}},"posts":{"type":"array","items":{"$ref":"#/$defs/Post"}}},"required":["comments","posts"],"additionalProperties":false}},"required":["id","type","name","role","relationships"],"additionalProperties":false},"Post":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["posts"]},"description":{"type":"string"},"relationships":{"type":"object","properties":{"user":{"$ref":"#/$defs/User"},"comments":{"type":"array","items":{"$ref":"#/$defs/Comment"}},"participants":{"type":"array","items":{"$ref":"#/$defs/User"}}},"required":["user","comments","participants"],"additionalProperties":false}},"required":["id","type","description","relationships"],"additionalProperties":false},"Exhaustive":{"type":"object","properties":{"id":{"type":"number"},"type":{"enum":["exhaustives"]},"assertedString":{"type":"string"},"assertedNumber":{"type":"number"},"assertedBoolean":{"type":"boolean"},"assertedNull":{"type":"null"},"assertedUnknown":{},"assertedObject":{"type":"object","properties":{"a":{"enum":["a"]},"b-dash":{"enum":[1]},"c":{"oneOf":[{"type":"string"},{"type":"null"}]},"d_optional":{"oneOf":[{"type":"string"},{"type":"null"}]}},"required":["a","b-dash","c"],"additionalProperties":false},"assertedMaybeObject":{"oneOf":[{"type":"object","properties":{"a":{"enum":["a"]},"b-dash":{"enum":[1]},"c":{"oneOf":[{"type":"string"},{"type":"null"}]},"d_optional":{"oneOf":[{"type":"string"},{"type":"null"}]}},"required":["a","b-dash","c"],"additionalProperties":false},{"type":"null"}]},"assertedArrayRecord":{"type":"array","items":{"type":"object","additionalProperties":"true"}},"assertedUnion":{"oneOf":[{"type":"string"},{"type":"number"}]},"assertedUnionArray":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"number"}]}},"withDescription":{"type":"string"},"inferredUnknown":{},"uuid":{"type":"string"},"string":{"type":"string"},"maybeString":{"type":"string"},"text":{"type":"string"},"integer":{"type":"number"},"float":{"type":"number"},"decimal":{"type":"string"},"datetime":{"type":"string"},"timestamp":{"type":"string"},"time":{"type":"string"},"date":{"type":"string"},"boolean":{"type":"boolean"},"arrayString":{"type":"array","items":{"type":"string"}},"maybeArrayString":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"json":{"type":"object","additionalProperties":"true"},"jsonb":{"type":"object","additionalProperties":"true"},"daterange":{},"enum":{},"virtualUpcasedString":{"oneOf":[{"type":"string"},{"type":"null"}]},"loljk":{"enum":["never"]},"delegatedMaybeString":{"type":"string"},"modelOverridden":{},"resourceOverridden":{},"withComment":{"oneOf":[{"type":"string"},{"type":"null"}]},"withParsedComment":{"oneOf":[{"type":"string"},{"type":"null"}]},"defaultedBoolean":{"type":"boolean"},"defaultedAt":{"type":"string"}},"required":["id","type","assertedString","assertedNumber","assertedBoolean","assertedNull","assertedUnknown","assertedObject","assertedMaybeObject","assertedArrayRecord","assertedUnion","assertedUnionArray","withDescription","inferredUnknown","uuid","string","maybeString","text","integer","float","decimal","datetime","timestamp","time","date","boolean","arrayString","maybeArrayString","json","jsonb","daterange","enum","virtualUpcasedString","loljk","delegatedMaybeString","modelOverridden","resourceOverridden","withComment","withParsedComment","defaultedBoolean","defaultedAt"],"additionalProperties":false}}} \ No newline at end of file diff --git a/spec/example/test/files/multifile/Exhaustive.ts b/spec/example/test/files/multifile/Exhaustive.ts index c82278f..dc48bde 100644 --- a/spec/example/test/files/multifile/Exhaustive.ts +++ b/spec/example/test/files/multifile/Exhaustive.ts @@ -54,6 +54,8 @@ type Model = { resourceOverridden: unknown; /** This is a comment. */ withComment: Maybe; + /** This is a parsed JSON comment. */ + withParsedComment: Maybe; defaultedBoolean: boolean; defaultedAt: string; relationships: {}; diff --git a/spec/example/test/files/schema.ts b/spec/example/test/files/schema.ts index 1606bd6..ae16658 100644 --- a/spec/example/test/files/schema.ts +++ b/spec/example/test/files/schema.ts @@ -96,6 +96,8 @@ export type Exhaustive = { resourceOverridden: unknown; /** This is a comment. */ withComment: Maybe; + /** This is a parsed JSON comment. */ + withParsedComment: Maybe; defaultedBoolean: boolean; defaultedAt: string; relationships: {}; diff --git a/spec/example/test/files/test_schema.ts b/spec/example/test/files/test_schema.ts index 5783098..b228c01 100644 --- a/spec/example/test/files/test_schema.ts +++ b/spec/example/test/files/test_schema.ts @@ -93,6 +93,8 @@ export type Exhaustive = { resourceOverridden: unknown; /** This is a comment. */ withComment: Maybe; + /** This is a parsed JSON comment. */ + withParsedComment: Maybe; defaultedBoolean: boolean; defaultedAt: string; relationships: {};