关于 inertia-rails-controllers
面向使用 Rails 和 Inertia.js 的开发者,整理控制器构造页面响应及传递属性的服务端模式。指南说明共享数据、延迟或按需属性、验证错误和外部重定向的处理方法。
主要功能
- 01控制器属性传递与渲染规则
- 02共享数据及闪消息配置
- 03延迟、可选和缓存属性
- 04验证错误映射到表单字段
- 05外部重定向与历史记录加密
适用场景
- 01为 Inertia.js 页面传递控制器数据
- 02处理耗时查询或局部刷新属性
- 03将验证错误对应到输入字段
- 04将用户重定向到支付或 OAuth 服务
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ inertia-rails/ skills --skill inertia-rails-controllers安装后在 Claude Code 中输入/inertia-rails-controllers确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/authorization.mdconfiguration.mdprop-types.md详细介绍
Inertia Rails 控制器
用于 Rails 控制器响应 Inertia 请求的服务器端模式。
添加 prop 前,请先确认:
- 每个页面都需要? → 在基础控制器(
InertiaController)中使用inertia_share,而不是按操作传递 prop - 计算开销大? → 使用
InertiaRails.defer— 页面会快速加载,数据随后流式传入 - 仅部分重新加载时需要? → 使用
InertiaRails.optional— 初次加载时跳过 - 很少变化的参考数据? → 使用
InertiaRails.once— 在多次导航间缓存
切勿:
- 对外部 URL(Stripe、OAuth、SSO)使用
redirect_to— 它会返回 302,但 Inertia 客户端会尝试将响应解析为 JSON,导致重定向失败。请使用inertia_location(返回 409 +X-Inertia-Location标头)。 - 将
errors.full_messages用于验证错误 — 它会生成不含字段键的扁平字符串,因此无法在前端将错误映射到对应的输入字段。请使用errors.to_hash(true)。 - 使用
inertia.defer、Inertia.defer或inertia_rails.defer— 正确的语法是InertiaRails.defer { ... }。所有 prop 辅助方法都是InertiaRails常量上的模块方法。 - 假设实例变量会自动作为 prop 传递 — 实际上不会(除非配置了
alba-inertiagem)。每个向前端传递 prop 的操作都必须调用render inertia: { key: data }。 - 在未更新
config.flash_keys的情况下,将success/error用作 flash 键 — Rails 默认使用notice/alert。自定义键必须同时添加到 initializer 配置和FlashDataTypeScript 类型中。
渲染语法
default_render: true陷阱: 此设置只会根据控制器/操作自动推断组件名称, 不会自动将实例变量作为 prop 传递。在启用了default_render: true的操作中 写入@posts = Post.all,会渲染正确的组件,但不会向前端传递任何数据。 只有配置了alba-inertiagem 时,实例变量才会自动序列化为 prop — 请先检查Gemfile,再依赖此行为。未配置时,必须使用render inertia: { posts: data }向页面传递数据。空操作(
def index; end)仅适用于不需要数据的页面(例如静态仪表板页面或登录表单)。 如果操作会查询数据库,就必须调用render inertia:并传入数据。
| 情况 | 语法 | 组件路径 |
|---|---|---|
| 操作加载数据 | render inertia: { users: data } | 根据控制器/操作推断 |
| 操作不加载数据(静态页面) | 空操作或 render inertia: {} | 根据控制器/操作推断 |
| 渲染其他页面 | render inertia: 'errors/show', props: { error: e } | 显式路径 |
经验法则: 如果操作涉及数据库,就必须调用 render inertia: 并传入数据。
如果操作体为空,页面只会收到共享 prop(来自 inertia_share)。
# CORRECT — data passed as props
def index
render inertia: { users: users_data, stats: InertiaRails.defer { ExpensiveQuery.run } }
end
# CORRECT — static page, no data needed
def index; end
# WRONG — @posts is NEVER sent to the frontend (without alba-inertia)
def index
@posts = Post.all
end
注意: 如果项目使用
alba-inertiagem(请检查Gemfile),实例变量会自动 序列化为 prop,因此无需显式调用render inertia:。 关于此约定,请参阅alba-inertia技能。
Prop 类型
InertiaRails.defer — 不是 inertia.defer,也不是 Inertia.defer。所有 prop 助手都是 InertiaRails 的模块方法。
| 类型 | 语法 | 行为 |
|---|---|---|
| 常规 | { key: value } | 始终求值,始终包含 |
| 延迟求值 | -> { expensive_value } | 在初始页面渲染时包含,在部分重新加载时延迟求值 |
| 可选 | InertiaRails.optional { ... } | 仅在部分重新加载请求该 prop 时求值 |
| 延后加载 | InertiaRails.defer { ... } | 在初始页面渲染后加载 |
| 延后加载(分组) | InertiaRails.defer(group: 'name') { ... } | 分组延后加载 — 并行获取 |
| 一次性 | InertiaRails.once { ... } | 求值一次,并在页面导航间记住 |
| 合并 | InertiaRails.merge { ... } | 附加到现有数组(无限滚动) |
| 深度合并 | InertiaRails.deep_merge { ... } | 深度合并到现有对象中 |
| 始终包含 | InertiaRails.always { ... } | 即使在部分重新加载时也包含 |
| 滚动 | InertiaRails.scroll { ... } | 用于无限滚动的滚动感知 prop |
def index
render inertia: {
filters: filter_params,
messages: -> { messages_scope.as_json },
stats: InertiaRails.defer { Dashboard.stats },
chart: InertiaRails.defer(group: 'analytics') { Dashboard.chart },
countries: InertiaRails.once { Country.pluck(:name, :code) },
posts: InertiaRails.merge { @posts.as_json },
csrf: InertiaRails.always { form_authenticity_token },
}
end
延后加载的 Props — 全栈示例
服务端延后加载缓慢的数据,客户端先显示回退内容,然后替换为实际内容:
# Controller
def show
render inertia: {
basic_stats: Stats.quick_summary,
analytics: InertiaRails.defer { Analytics.compute_slow },
}
end
// Page component — child reads deferred prop from page props
import { Deferred, usePage } from '@inertiajs/react'
export default function Dashboard({ basic_stats }: Props) {
return (
<>
<QuickStats data={basic_stats} />
<Deferred data="analytics" fallback={<div>Loading analytics...</div>}>
<AnalyticsPanel />
</Deferred>
</>
)
}
function AnalyticsPanel() {
const { analytics } = usePage<{ analytics: Analytics }>().props
return <div>{analytics.revenue}</div>
}
共享数据
在控制器中使用 inertia_share — 它需要控制器上下文(current_user、
请求)。初始化器仅处理 config.* 设置(version、flash_keys)。
class ApplicationController < ActionController::Base
# Static
inertia_share app_name: 'MyApp'
# Using lambdas (most common)
inertia_share auth: -> { { user: current_user&.as_json(only: [:id, :name, :email, :role]) } }
# Conditional
inertia_share if: :user_signed_in? do
{ notifications: -> { current_user.unread_notifications_count } }
end
end
Lambda 和按操作限定的变体见 references/configuration.md。
求值顺序: 多次调用 inertia_share 时,会从上到下合并。如果子控制器
共享的键与父控制器相同,则采用子控制器的值。代码块和 lambda 共享数据会在每次请求时延迟求值 — 对非 Inertia 请求不会运行。
Flash 消息
Flash 会自动处理。如有需要,可配置要公开的键:
# config/initializers/inertia_rails.rb
InertiaRails.configure do |config|
config.flash_keys = %i[notice alert toast] # default: %i[notice alert]
end
在控制器中使用标准 Rails flash:
redirect_to users_path, notice: "User created!"
# or
flash.alert = "Something went wrong"
redirect_to users_path
重定向与验证错误
创建/更新/删除后,始终进行重定向(Post-Redirect-Get)。标准 Rails
redirect_to 可用。Inertia 特有的部分是验证错误处理:
def create
@user = User.new(user_params)
if @user.save
redirect_to users_path, notice: "Created!"
else
redirect_back_or_to new_user_path, inertia: { errors: @user.errors.to_hash(true) }
end
end
to_hash 与 to_hash(true): to_hash 返回 { name: ["can't be blank"] },
to_hash(true) 返回 { name: ["Name can't be blank"] }。键必须与输入项的
name 属性匹配 — 键不匹配意味着错误不会显示在正确的字段旁边。
绝不要使用 errors.full_messages — 它会生成不带字段键的扁平字符串,
因此无法将错误映射到前端对应的输入字段。
将授权信息作为 Props
按资源传递权限 can 哈希 — 前端控制可见性,服务端强制执行访问权限。参见 inertia-rails-controllers + inertia-rails-pages 技能。
必须 — 实现授权 props 时读取整个文件:
references/authorization.md(约 40 行)— 包含 Action Policy/Pundit/CanCanCan 示例的全栈
can 模式。
如果不向前端传递权限数据,则不要加载。
外部重定向(inertia_location)
重要: 对外部 URL 使用 redirect_to 会破坏 Inertia — 客户端
收到 302 后会尝试将其作为 Inertia 响应(JSON)处理,而不是执行
整页重定向。inertia_location 返回带有 X-Inertia-Location
标头的 409,告知客户端执行 window.location = url。
# Stripe checkout — MUST use inertia_location, not redirect_to
def create
checkout_session = Current.user.payment_processor.checkout(
mode: "payment",
line_items: "price_xxx",
success_url: enrollments_url,
cancel_url: course_url(@course),
)
inertia_location checkout_session.url
end
对于 Inertia 应用之外的任何 URL,都使用 inertia_location:支付
提供商、OAuth、外部服务。
历史记录加密
加密浏览器历史记录状态中的页面数据 — config.encrypt_history = Rails.env.production?。
在注销/角色变更时使用 redirect_to path, inertia: { clear_history: true }。
完整设置及服务端和客户端示例见
references/configuration.md。
配置
所有 InertiaRails.configure 选项(version、encrypt_history、flash_keys 等)见 references/configuration.md。
故障排查
| 症状 | 原因 | 修复方法 |
|---|---|---|
| Stripe/OAuth 重定向出现 302 循环 | 外部 URL 使用了 redirect_to | 使用 inertia_location — 它会返回 409 和 X-Inertia-Location 标头 |
| 错误没有显示在字段旁边 | 错误键与输入项的 name 不匹配 | to_hash 的键必须与输入项的 name 属性完全匹配 |
TS2305:在 @/routes 中找不到 postsPath | 添加路由后未重新生成 js-routes | 修改 config/routes.rb 后运行 rails js_routes:generate |
相关技能
- 表单错误显示 →
inertia-rails-forms - Flash 提示 UI →
inertia-rails-pages(访问)+shadcn-inertia(Sonner) - 在客户端延迟加载 →
inertia-rails-pages(<Deferred>组件) - 类型安全的 props →
inertia-rails-typescript或alba-inertia(序列化器) - 测试 →
inertia-rails-testing
参考资料
必须 — 阅读整个文件:使用高级 prop 类型(merge、
scroll、deep_merge)或组合多个 prop 选项时:
references/prop-types.md(约 180 行)— 详细说明
所有 prop 类型的行为、边缘情况和组合规则。
不要加载 prop-types.md 来了解基本的 defer、optional、once 或 always
用法 — 上表已足够。
仅在首次设置 InertiaRails.configure 或排查配置
问题时,才加载 references/configuration.md(约 180 行)。常规控制器工作时不要加载。