alba-inertia

SKILL.md

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_json when Alba is set up — it bypasses type generation and creates untyped props
  • Skip typelize_from when resource name differs from model — Typelizer can't infer column types and generates unknown
  • Put declare module augmentations in serializers/index.ts — Typelizer-generated types go in serializers/index.ts, manual InertiaConfig goes in globals.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 typesinertia-rails-controllers (defer, once, merge, scroll)
  • TypeScript configinertia-rails-typescript (InertiaConfig in globals.d.ts)
Weekly Installs
43
GitHub Stars
34
First Seen
Feb 13, 2026
Installed on
opencode42
gemini-cli42
codex42
github-copilot41
amp40
kimi-cli39