JSONAPI Resources Anchor

Configuration

Quick Reference

Example

config/initializers/anchor.rb
module Anchor
  configure do |c|
    c.field_case = :camel_without_inflection
    c.use_active_record_comment = true
    c.ar_comment_to_string = lambda { |comment|
      begin
        res = JSON.parse(comment)
        res["description"]
      rescue JSON::ParserError
        comment
      end
    }

    c.use_active_record_validations = true
    c.infer_nullable_relationships_as_optional = true

    c.ar_column_to_type = lambda { |column|
      return Types::Literal.new("never") if column.name == "loljk"
      Types::Inference::ActiveRecord::SQL.default_ar_column_to_type(column)
    }

    c.empty_relationship_type = -> { Anchor::Types::Object }
  end
end

Options

field_case

  • Type: :camel | :snake | :kebab | :camel_without_inflection
  • Default: nil

Determines the format for properties of attributes and relationships.

  • nil - no changes to key name
  • camel, snake, and kebab format keys as their names suggest
  • camel_without_inflection - camelize the key without using inflections defined in your Rails app
    • e.g. given inflect.acronym "LOL" and key "lol_what"
      • camel: "lol_what".camellize => "LOLWhat"
      • camel_without_inflection: camel_without_inflection("lol_what") => "LolWhat"

ar_column_to_type

  • Type: Proc
  • Default: nil

Input: ActiveRecord::Base.columns_hash[attribute]

Output: member of Anchor::Types

Example

module Anchor
  configure do |c|
    c.ar_column_to_type = lambda { |column|
      return Types::Literal.new("never") if column.name == "loljk"
      Types::Inference::ActiveRecord::SQL.default_ar_column_to_type(column)
    }
  end
end

class UserResource < ApplicationResource
  attribute :loljk
end
schema.ts
// ...
export type User = {
  id: string;
  type: "users";
  loljk: "never";
};
// ...

use_active_record_comment

  • Type: Boolean
  • Default: nil

Whether to use ActiveRecord comments as the default value of the description option. In TypeScript, this would become a comment.

ar_comment_to_string

  • Type: Proc
  • Default: nil

Input: String

Output: String

Requires use_active_record_comment to be enabled.

Determines how the comment will be serialized in the serializer.

Example

After running this migration to add a JSON string formatted comment to get the schema below:

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

ActiveRecord::Schema[8.0].define(version: 2025_09_20_024446) do
  create_table "exhaustives", force: :cascade do |t|
    t.string "with_comment", comment: "This is a comment."
    t.string "with_parsed_comment", comment: "{\n  \"description\": \"This is a parsed JSON comment.\",\n  \"test\": 2\n}\n"
  end
end
module Anchor
  configure do |c|
    c.field_case = :camel_without_inflection
    c.use_active_record_comment = true
    c.ar_comment_to_string = lambda { |comment|
      begin
        res = JSON.parse(comment)
        res["description"]
      rescue JSON::ParserError
        comment
      end
    }
  end
end

The config above will serialize the comment for with_parsed_comment like:

type Exhaustive = {
  id: number;
  type: "exhaustives";
  /** This is a comment. */
  withComment: Maybe<string>;
  /** This is a parsed JSON comment. */
  withParsedComment: Maybe<string>;
};

use_active_record_validations

  • Type: Boolean
  • Default: true

Use ActiveModel validations on the ActiveRecord model to determine whether an attribute is nullable.

For example, validates :role, presence: true will infer role as non-null irrespective of the presence of the database's non-null constraint on the role column.

infer_nullable_relationships_as_optional

  • Type: Boolean
  • Default: nil

true infers nullable relationships as optional.

For example, in TypeScript, true infers { relation?: Relation } over { relation: Maybe<Relation> }.

empty_relationship_type

  • Type: Proc
  • Default: nil

By default, if there are no relationships for a resource then the relationships property is not included in the TypeScript serializer.

If this config is defined, the relationships property will be included with the returned Anchor type as the type.

Example

Anchor.configure { |c| c.empty_relationship_type = -> { Anchor::Types::Object } }

class UserResource < ApplicationResource
  attribute :name, Anchor::Types::String
end
schema.ts
// ...
export type User = {
  id: string;
  type: "users";
  name: string;
  relationships: {};
};
// ...

use_type_as_schema_name

  • Type: Boolean
  • Default: nil

By default, the demodulized name of the class minus "Resource" is used as the schema name for the resource, e.g. SomeModule::UserResource => User.

When true, the String#classify'd type that JSONAPI::Resource determines from the resource will be used.

infer_default_as_non_null

  • Type: Boolean
  • Default: nil

By default, columns without non-null validations will be considered nullable.

When true, columns with default values will be considered non-null.

maybe_as_union

  • Type: Boolean
  • Default: nil

By default, in the TypeScript serializer, nullable types will be serialized as e.g. Maybe<string>.

When true, nullable types will be serialized as e.g. string | null.

array_bracket_notation

  • Type: Boolean
  • Default: nil

By default, in the TypeScript serializer, array types will be serialized as e.g. Array<User>.

When true, array types will be serialized as e.g. (User)[].

infer_ar_enums

  • Type: Boolean
  • Default: nil

By default, in the TypeScript serializer, enums defined by ActiveRecord::Base.enum will be inferred as unknown.

When true, they will be serialized as a union of literals, e.g. type User = { role: "admin" | "member" }