Alba + Typelizer for Inertia Rails
Requires: alba, typelizer, alba-inertia gems in Gemfile.
Alba serializers for Inertia props with auto-generated TypeScript types.
Replaces as_json(only: [...]) with structured, type-safe resources.
Before creating a resource, ask:
- Reusable data shape (user, course)? → Entity resource (
UserResource) — shared across pages - Page-specific props bundle? → Page resource (
UsersIndexResource) — one per controller action - Global data (auth, notifications)? → Shared props resource (
SharedPropsResource)
NEVER:
- Use
as_jsonwhen Alba is set up — it bypasses type generation and creates untyped props - Skip
typelize_fromwhen resource name differs from model — Typelizer can't infer column types and generatesunknown - Put
declare moduleaugmentations inserializers/index.ts— Typelizer-generated types go inserializers/index.ts, manual InertiaConfig goes inglobals.d.ts
Setup
ApplicationResource (all resources inherit from this)
# app/resources/application_resource.rb
class ApplicationResource
include Alba::Resource
helper Typelizer::DSL # enables typelize, typelize_from
helper Alba::Inertia::Resource # enables inertia: option on attributes
include Rails.application.routes.url_helpers
end
Controller Integration
# app/controllers/inertia_controller.rb
class InertiaController < ApplicationController
include Alba::Inertia::Controller
inertia_share { SharedPropsResource.new(self).to_inertia }
end
Resource Types
Entity Resource (reusable data shape)
# app/resources/user_resource.rb
class UserResource < ApplicationResource
typelize_from User # needed when resource name doesn't match model
attributes :id, :name, :email
typelize :string?
attribute :avatar_url do |user|
user.avatar.attached? ? rails_blob_path(user.avatar, only_path: true) : nil
end
end
Page Resource (page-specific props)
# app/resources/users/index_resource.rb
# Naming convention: {Controller}{Action}Resource
class UsersIndexResource < ApplicationResource
has_many :users, resource: UserResource
typelize :string
attribute :search do |obj, _|
obj.params.dig(:filters, :search)
end
end
Shared Props Resource
Requires Rails Current attributes (e.g., Current.user) to be configured —
see CurrentAttributes.
# app/resources/shared_props_resource.rb
class SharedPropsResource < ApplicationResource
one :auth, source: proc { Current }
attribute :unread_messages_count, inertia: { always: true } do
Current.user&.unread_count || 0
end
has_many :live_now, resource: LiveSessionsResource,
inertia: { once: { expires_in: 5.minutes } }
end
Convention-Based Rendering
With Alba::Inertia::Controller, instance variables auto-serialize:
class UsersController < InertiaController
def index
@users = User.all # auto-serialized via UserResource
@filters = filter_params # plain data passed through
# Auto-detects UsersIndexResource
end
def show
@user = User.find(params[:id])
# Auto-detects UsersShowResource
end
end
Typelizer + Type Generation
typelize_from tells Typelizer which model to infer column types from — needed when
resource name doesn't match model. For computed attributes, declare types explicitly:
class AuthorResource < ApplicationResource
typelize_from User
attributes :id, :name, :email
typelize :string? # next attribute is string | undefined
attribute :avatar_url do |user|
rails_storage_proxy_path(user.avatar) if user.avatar.attached?
end
typelize filters: "{category: number}" # inline TS type
end
Types auto-generate when Rails server runs. Manual: bin/rake typelizer:generate.
Inertia Prop Options in Alba
The inertia: option on attributes/associations maps to InertiaRails prop types:
attribute :stats, inertia: :defer # InertiaRails.defer
has_many :users, inertia: :optional # InertiaRails.optional
has_many :countries, inertia: :once # InertiaRails.once
has_many :items, inertia: { merge: true } # InertiaRails.merge
attribute :csrf, inertia: { always: true } # InertiaRails.always
MANDATORY — READ ENTIRE FILE when using grouped defer, merge with match_on,
scroll props, or combining multiple options:
references/prop-options.md (~60 lines) — full syntax
for all inertia: option variants and inertia_prop alternative syntax.
Do NOT load for basic inertia: :defer, inertia: :optional, or
inertia: :once — the shorthand above is sufficient.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
TypeScript type is all unknown | Missing typelize_from | Add typelize_from ModelName when resource name doesn't match model |
inertia: option has no effect | Missing helper | Ensure helper Alba::Inertia::Resource is in ApplicationResource |
| Types not regenerating | Server not running | Typelizer watches files in dev only. Run bin/rake typelizer:generate manually |
NoMethodError for typelize | Missing helper | Ensure helper Typelizer::DSL is in ApplicationResource |
| Convention-based rendering picks wrong resource | Naming mismatch | Resource must be {Controller}{Action}Resource (e.g., UsersIndexResource for UsersController#index) |
to_inertia undefined | Missing include | Controller needs include Alba::Inertia::Controller |
Related Skills
- Prop types →
inertia-rails-controllers(defer, once, merge, scroll) - TypeScript config →
inertia-rails-typescript(InertiaConfig in globals.d.ts)