Inference
Apiwork detects configuration from the database schema and ActiveRecord models. This reduces repetition and keeps the API in sync with the data.
Apiwork pulls structural details that are safe to reuse: column types, enums, associations, nullability. Application-specific behavior is not detected.
Everything detected can be overridden when needed.
Overview
| What | Detected From | Override |
|---|---|---|
| Model class | Representation class name | model YourModel |
| Attribute type | Database column type | type: :string |
| Nullable | Column NULL constraint | nullable: true |
| Optional | NULL allowed or has default | optional: true |
| Enum values | Rails enum definition | enum: [:a, :b] |
| Association representation | Association name | representation: CommentRepresentation |
| Association nullable | Foreign key constraint | nullable: true |
| Foreign key column | Rails reflection | (automatic) |
| Polymorphic discriminator | Rails reflection | discriminator: :type |
| STI column | inheritance_column | (automatic) |
| STI variant tag | sti_name | type_name 'custom' |
| Allow destroy | nested_attributes_options | (automatic) |
| Root key | model_name.element | root :item, :items |
Model Detection
Representation class names map to model classes automatically:
class UserRepresentation < Apiwork::Representation::Base
# Auto-detects: User model
end
class Api::V1::PostRepresentation < Apiwork::Representation::Base
# Tries: Api::V1::Post, then Post
endWhen names do not match, the model is set explicitly:
class AccountRepresentation < Apiwork::Representation::Base
model Organization # Use Organization model instead
endAttribute Inference
Type Detection
Apiwork reads column types from the database:
| Database Type | Detected Type |
|---|---|
varchar, text | :string |
integer, bigint | :integer |
boolean | :boolean |
datetime, timestamp | :datetime |
date | :date |
time | :time |
decimal, numeric | :decimal |
float, real | :number |
uuid | :uuid |
binary, blob, bytea | :binary |
json, jsonb | :unknown |
# Database: name VARCHAR(255), age INTEGER, active BOOLEAN
class UserRepresentation < Apiwork::Representation::Base
attribute :name # type: :string (auto)
attribute :age # type: :integer (auto)
attribute :active # type: :boolean (auto)
endJSON and JSONB Columns
JSON/JSONB columns auto-detect as :unknown:
# Database: settings JSONB, tags JSONB
class UserRepresentation < Apiwork::Representation::Base
attribute :settings # type: :unknown (auto)
attribute :tags # type: :unknown (auto)
endWhy :unknown instead of :object or :array?
A JSONB column only means "here lies JSON." It could be an object, array, string, number, or null.
Typed exports require an explicitly defined shape:
# Object shape
attribute :settings do
object do
string :theme
boolean :notifications
end
end
# Array shape
attribute :tags do
array do
string
end
endSee Inline Types for complete syntax.
Nullable Detection
Detected from column NULL constraints:
# Database: bio TEXT NULL, email VARCHAR(255) NOT NULL
class UserRepresentation < Apiwork::Representation::Base
attribute :bio # nullable: true (auto)
attribute :email # nullable: false (auto)
endThe detected value can be overridden:
attribute :bio, nullable: false # Reject null even if DB allows itOptional Detection
An attribute is optional when:
- Column allows NULL, or
- Column has a default value
# Database: title VARCHAR(255) NOT NULL, body TEXT NULL, status INTEGER DEFAULT 0
class PostRepresentation < Apiwork::Representation::Base
attribute :title # required (NOT NULL, no default)
attribute :body # optional (allows NULL)
attribute :status # optional (has default)
endEnum Detection
Rails enum definitions are detected automatically:
# Model
class Account < ApplicationRecord
enum :status, { active: 0, inactive: 1, archived: 2 }
end
# Representation
class AccountRepresentation < Apiwork::Representation::Base
attribute :status # enum: [:active, :inactive, :archived] (auto)
endGenerated TypeScript:
type AccountStatus = "active" | "inactive" | "archived";The detected enum values can be overridden:
attribute :status, enum: [:pending, :approved] # Custom valuesAssociation Inference
Representation Detection
Association representations are resolved by name:
class PostRepresentation < Apiwork::Representation::Base
has_many :comments # representation: CommentRepresentation (auto)
belongs_to :author # representation: AuthorRepresentation (auto)
endNon-standard names require an explicit representation:
has_many :recent_posts, representation: PostRepresentationNullable Detection
For belongs_to, nullable is detected from the foreign key column:
# Database: author_id INTEGER NOT NULL, reviewer_id INTEGER NULL
class PostRepresentation < Apiwork::Representation::Base
belongs_to :author # nullable: false (FK is NOT NULL)
belongs_to :reviewer # nullable: true (FK allows NULL)
endFor has_one, nullable defaults to false. The default can be overridden if needed:
has_one :profile, nullable: trueThe detected value can be overridden:
belongs_to :author, nullable: true # Allow null even if FK is requiredForeign Key Detection
For belongs_to associations, Apiwork detects the foreign key column from Rails reflection:
# Model
class Post < ApplicationRecord
belongs_to :author, class_name: 'User', foreign_key: :writer_id
end
# Representation - foreign key auto-detected as :writer_id
class PostRepresentation < Apiwork::Representation::Base
belongs_to :author # Uses writer_id column for nullable detection
endWhen no explicit foreign key is defined, Apiwork falls back to #{association_name}_id.
Polymorphic Discriminator
For polymorphic associations, the discriminator column is detected from Rails:
# Model
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
# Representation - discriminator auto-detected as :commentable_type
class CommentRepresentation < Apiwork::Representation::Base
belongs_to :commentable, polymorphic: [
PostRepresentation,
ImageRepresentation,
]
endNested Attributes Detection
When an association is writable, Apiwork validates that the model has accepts_nested_attributes_for:
# Model - required for writable associations
class Post < ApplicationRecord
has_many :comments
accepts_nested_attributes_for :comments, allow_destroy: true
end
# Representation
class PostRepresentation < Apiwork::Representation::Base
has_many :comments, writable: true
endSingle Table Inheritance
Apiwork automatically detects STI from Rails models:
- Inheritance column from
inheritance_column(default::type) - Subclass representations auto-register when they inherit from a base representation
The type_name method overrides the API discriminator value (default: model.sti_name).
See Single Table Inheritance for details.
Root Key Detection
The root key for JSON responses is detected from model_class.model_name.element:
class Invoice < ApplicationRecord; end
class InvoiceRepresentation < Apiwork::Representation::Base
# Root key auto-detected:
# - Singular: 'invoice'
# - Plural: 'invoices'
endResponse shape:
{ "invoice": { ... } }
{ "invoices": [{ ... }] }The default can be overridden:
class InvoiceRepresentation < Apiwork::Representation::Base
root :bill, :bills # Custom root keys
endWhen to Override
Auto-detection should be overridden when:
- Names don't match - Representation/model/association names differ from convention
- Virtual attributes - Attribute doesn't exist in database
- Stricter validation - API should be stricter than database allows
- Looser validation - API should accept values database rejects
- Custom types - Need specific serialization behavior
class UserRepresentation < Apiwork::Representation::Base
model Account # Different model name
attribute :full_name, type: :string # Virtual attribute
attribute :email, nullable: false # Stricter than DB
attribute :age, type: :integer # Explicit type
endNot Detected
The following are not automatically detected and must be specified manually:
| Option | Why Not Detected |
|---|---|
min / max | Column length limits don't always match API needs |
filterable | Query capability is a design decision (adapter interprets) |
sortable | Query capability is a design decision (adapter interprets) |
writable | Write permissions are security-sensitive |
include | Eager loading strategy is a performance decision (adapter interprets) |
description | Documentation requires human context |
example | Examples require domain knowledge |
format | Semantic formats (email, url) need explicit intent |
deprecated | Deprecation is a lifecycle decision |
These options affect API behavior, security, or documentation in ways that need explicit choices, not automatic detection.
INFO
JSON/JSONB columns only tell Apiwork "this is JSON" — not what is inside. Unlike most attributes where the type is detected from the database, JSON columns require an explicit object or array block for typed exports. See Inline Types.
See also
- Representation::Base reference — all representation methods and options