mcpField
I
inertia-rails-controllers

inertia-rails-controllers

github.com/inertia-rails开发70493 次安装37 周前更新
代码仓库

关于 inertia-rails-controllers

面向使用 Rails 和 Inertia.js 的开发者,整理控制器构造页面响应及传递属性的服务端模式。指南说明共享数据、延迟或按需属性、验证错误和外部重定向的处理方法。

主要功能

  1. 01控制器属性传递与渲染规则
  2. 02共享数据及闪消息配置
  3. 03延迟、可选和缓存属性
  4. 04验证错误映射到表单字段
  5. 05外部重定向与历史记录加密

适用场景

  1. 01为 Inertia.js 页面传递控制器数据
  2. 02处理耗时查询或局部刷新属性
  3. 03将验证错误对应到输入字段
  4. 04将用户重定向到支付或 OAuth 服务

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/inertia-rails/skills --skill inertia-rails-controllers

安装后在 Claude Code 中输入/inertia-rails-controllers确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
authorization.md
configuration.md
prop-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-inertia gem)。每个向前端传递 prop 的操作都必须调用 render inertia: { key: data }。
  • 在未更新 config.flash_keys 的情况下,将 success/error 用作 flash 键 — Rails 默认使用 notice/alert。自定义键必须同时添加到 initializer 配置和 FlashData TypeScript 类型中。

渲染语法

default_render: true 陷阱: 此设置只会根据控制器/操作自动推断组件名称, 不会自动将实例变量作为 prop 传递。在启用了 default_render: true 的操作中 写入 @posts = Post.all,会渲染正确的组件,但不会向前端传递任何数据。 只有配置了 alba-inertia gem 时,实例变量才会自动序列化为 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-inertia gem(请检查 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 行)。常规控制器工作时不要加载。

评论

支持 Markdown