├── .github └── workflows │ ├── docs.yml │ ├── generators.yml │ └── push.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── app ├── controllers │ └── inertia_rails │ │ └── static_controller.rb └── views │ └── inertia.html.erb ├── bin ├── console ├── generate_scaffold_example └── setup ├── docs ├── .gitignore ├── .prettierignore ├── .vitepress │ ├── config.mts │ ├── theme │ │ ├── frameworksTabs.ts │ │ ├── index.ts │ │ └── style.css │ └── vitepress-plugin-tabs │ │ ├── ruleBlockTab.ts │ │ └── tabsMarkdownPlugin.ts ├── awesome.md ├── cookbook │ ├── handling-validation-error-types.md │ └── integrating-shadcn-ui.md ├── guide │ ├── asset-versioning.md │ ├── authentication.md │ ├── authorization.md │ ├── client-side-setup.md │ ├── code-splitting.md │ ├── configuration.md │ ├── csrf-protection.md │ ├── deferred-props.md │ ├── demo-application.md │ ├── error-handling.md │ ├── events.md │ ├── file-uploads.md │ ├── forms.md │ ├── history-encryption.md │ ├── how-it-works.md │ ├── index.md │ ├── links.md │ ├── load-when-visible.md │ ├── manual-visits.md │ ├── merging-props.md │ ├── pages.md │ ├── partial-reloads.md │ ├── polling.md │ ├── prefetching.md │ ├── progress-indicators.md │ ├── redirects.md │ ├── remembering-state.md │ ├── responses.md │ ├── routing.md │ ├── scroll-management.md │ ├── server-side-rendering.md │ ├── server-side-setup.md │ ├── shared-data.md │ ├── testing.md │ ├── the-protocol.md │ ├── title-and-meta.md │ ├── upgrade-guide.md │ ├── validation.md │ └── who-is-it-for.md ├── index.md ├── package-lock.json ├── package.json ├── prettier.config.js └── public │ ├── favicon.ico │ ├── logo.jpg │ ├── logo.svg │ ├── og_image.jpg │ └── pingcrm.png ├── inertia_rails.gemspec ├── lib ├── generators │ ├── inertia │ │ ├── controller │ │ │ ├── controller_generator.rb │ │ │ └── templates │ │ │ │ └── controller.rb.tt │ │ ├── install │ │ │ ├── frameworks.yml │ │ │ ├── helpers.rb │ │ │ ├── install_generator.rb │ │ │ ├── js_package_manager.rb │ │ │ └── templates │ │ │ │ ├── assets │ │ │ │ ├── inertia.svg │ │ │ │ ├── react.svg │ │ │ │ ├── svelte.svg │ │ │ │ ├── vite_ruby.svg │ │ │ │ └── vue.svg │ │ │ │ ├── controller.rb │ │ │ │ ├── dev │ │ │ │ ├── initializer.rb │ │ │ │ ├── react │ │ │ │ ├── InertiaExample.jsx │ │ │ │ ├── InertiaExample.module.css │ │ │ │ ├── InertiaExample.tsx │ │ │ │ ├── inertia.js │ │ │ │ ├── inertia.ts │ │ │ │ ├── tsconfig.app.json │ │ │ │ ├── tsconfig.json │ │ │ │ ├── tsconfig.node.json │ │ │ │ └── vite-env.d.ts │ │ │ │ ├── svelte │ │ │ │ ├── InertiaExample.svelte │ │ │ │ ├── InertiaExample.ts.svelte │ │ │ │ ├── inertia.js │ │ │ │ ├── inertia.ts.tt │ │ │ │ ├── svelte.config.js │ │ │ │ ├── tsconfig.json │ │ │ │ ├── tsconfig.node.json │ │ │ │ └── vite-env.d.ts │ │ │ │ ├── svelte4 │ │ │ │ ├── InertiaExample.svelte │ │ │ │ ├── InertiaExample.ts.svelte │ │ │ │ ├── inertia.js │ │ │ │ ├── inertia.ts.tt │ │ │ │ ├── svelte.config.js │ │ │ │ ├── tsconfig.json │ │ │ │ ├── tsconfig.node.json │ │ │ │ └── vite-env.d.ts │ │ │ │ ├── tailwind │ │ │ │ └── application.css │ │ │ │ └── vue │ │ │ │ ├── InertiaExample.ts.vue │ │ │ │ ├── InertiaExample.vue │ │ │ │ ├── inertia.js │ │ │ │ ├── inertia.ts │ │ │ │ ├── tsconfig.app.json │ │ │ │ ├── tsconfig.json │ │ │ │ ├── tsconfig.node.json │ │ │ │ └── vite-env.d.ts │ │ ├── scaffold │ │ │ └── scaffold_generator.rb │ │ └── scaffold_controller │ │ │ ├── scaffold_controller_generator.rb │ │ │ └── templates │ │ │ └── controller.rb.tt │ ├── inertia_templates │ │ ├── controller │ │ │ ├── controller_generator.rb │ │ │ └── templates │ │ │ │ ├── react │ │ │ │ ├── view.jsx.tt │ │ │ │ └── view.tsx.tt │ │ │ │ ├── svelte │ │ │ │ └── view.svelte.tt │ │ │ │ ├── svelte4 │ │ │ │ └── view.svelte.tt │ │ │ │ └── vue │ │ │ │ └── view.vue.tt │ │ └── scaffold │ │ │ ├── scaffold_generator.rb │ │ │ └── templates │ │ │ ├── react │ │ │ ├── Edit.jsx.tt │ │ │ ├── Edit.tsx.tt │ │ │ ├── Form.jsx.tt │ │ │ ├── Form.tsx.tt │ │ │ ├── Index.jsx.tt │ │ │ ├── Index.tsx.tt │ │ │ ├── New.jsx.tt │ │ │ ├── New.tsx.tt │ │ │ ├── One.jsx.tt │ │ │ ├── One.tsx.tt │ │ │ ├── Show.jsx.tt │ │ │ ├── Show.tsx.tt │ │ │ └── types.ts.tt │ │ │ ├── svelte │ │ │ ├── Edit.svelte.tt │ │ │ ├── Edit.ts.svelte.tt │ │ │ ├── Form.svelte.tt │ │ │ ├── Form.ts.svelte.tt │ │ │ ├── Index.svelte.tt │ │ │ ├── Index.ts.svelte.tt │ │ │ ├── New.svelte.tt │ │ │ ├── New.ts.svelte.tt │ │ │ ├── One.svelte.tt │ │ │ ├── One.ts.svelte.tt │ │ │ ├── Show.svelte.tt │ │ │ ├── Show.ts.svelte.tt │ │ │ └── types.ts.tt │ │ │ ├── svelte4 │ │ │ ├── Edit.svelte.tt │ │ │ ├── Edit.ts.svelte.tt │ │ │ ├── Form.svelte.tt │ │ │ ├── Form.ts.svelte.tt │ │ │ ├── Index.svelte.tt │ │ │ ├── Index.ts.svelte.tt │ │ │ ├── New.svelte.tt │ │ │ ├── New.ts.svelte.tt │ │ │ ├── One.svelte.tt │ │ │ ├── One.ts.svelte.tt │ │ │ ├── Show.svelte.tt │ │ │ ├── Show.ts.svelte.tt │ │ │ └── types.ts.tt │ │ │ └── vue │ │ │ ├── Edit.ts.vue.tt │ │ │ ├── Edit.vue.tt │ │ │ ├── Form.ts.vue.tt │ │ │ ├── Form.vue.tt │ │ │ ├── Index.ts.vue.tt │ │ │ ├── Index.vue.tt │ │ │ ├── New.ts.vue.tt │ │ │ ├── New.vue.tt │ │ │ ├── One.ts.vue.tt │ │ │ ├── One.vue.tt │ │ │ ├── Show.ts.vue.tt │ │ │ ├── Show.vue.tt │ │ │ └── types.ts.tt │ └── inertia_tw_templates │ │ ├── controller │ │ ├── controller_generator.rb │ │ └── templates │ │ │ ├── react │ │ │ ├── view.jsx.tt │ │ │ └── view.tsx.tt │ │ │ ├── svelte │ │ │ └── view.svelte.tt │ │ │ ├── svelte4 │ │ │ └── view.svelte.tt │ │ │ └── vue │ │ │ └── view.vue.tt │ │ └── scaffold │ │ ├── scaffold_generator.rb │ │ └── templates │ │ ├── react │ │ ├── Edit.jsx.tt │ │ ├── Edit.tsx.tt │ │ ├── Form.jsx.tt │ │ ├── Form.tsx.tt │ │ ├── Index.jsx.tt │ │ ├── Index.tsx.tt │ │ ├── New.jsx.tt │ │ ├── New.tsx.tt │ │ ├── One.jsx.tt │ │ ├── One.tsx.tt │ │ ├── Show.jsx.tt │ │ ├── Show.tsx.tt │ │ └── types.ts.tt │ │ ├── svelte │ │ ├── Edit.svelte.tt │ │ ├── Edit.ts.svelte.tt │ │ ├── Form.svelte.tt │ │ ├── Form.ts.svelte.tt │ │ ├── Index.svelte.tt │ │ ├── Index.ts.svelte.tt │ │ ├── New.svelte.tt │ │ ├── New.ts.svelte.tt │ │ ├── One.svelte.tt │ │ ├── One.ts.svelte.tt │ │ ├── Show.svelte.tt │ │ ├── Show.ts.svelte.tt │ │ └── types.ts.tt │ │ ├── svelte4 │ │ ├── Edit.svelte.tt │ │ ├── Edit.ts.svelte.tt │ │ ├── Form.svelte.tt │ │ ├── Form.ts.svelte.tt │ │ ├── Index.svelte.tt │ │ ├── Index.ts.svelte.tt │ │ ├── New.svelte.tt │ │ ├── New.ts.svelte.tt │ │ ├── One.svelte.tt │ │ ├── One.ts.svelte.tt │ │ ├── Show.svelte.tt │ │ ├── Show.ts.svelte.tt │ │ └── types.ts.tt │ │ └── vue │ │ ├── Edit.ts.vue.tt │ │ ├── Edit.vue.tt │ │ ├── Form.ts.vue.tt │ │ ├── Form.vue.tt │ │ ├── Index.ts.vue.tt │ │ ├── Index.vue.tt │ │ ├── New.ts.vue.tt │ │ ├── New.vue.tt │ │ ├── One.ts.vue.tt │ │ ├── One.vue.tt │ │ ├── Show.ts.vue.tt │ │ ├── Show.vue.tt │ │ └── types.ts.tt ├── inertia_rails.rb ├── inertia_rails │ ├── action_filter.rb │ ├── always_prop.rb │ ├── base_prop.rb │ ├── configuration.rb │ ├── controller.rb │ ├── defer_prop.rb │ ├── engine.rb │ ├── generators │ │ ├── controller_template_base.rb │ │ ├── helper.rb │ │ └── scaffold_template_base.rb │ ├── helper.rb │ ├── ignore_on_first_load_prop.rb │ ├── inertia_rails.rb │ ├── lazy_prop.rb │ ├── merge_prop.rb │ ├── middleware.rb │ ├── optional_prop.rb │ ├── renderer.rb │ ├── rspec.rb │ └── version.rb ├── patches │ ├── better_errors.rb │ ├── debug_exceptions.rb │ ├── debug_exceptions │ │ ├── patch-5-0.rb │ │ └── patch-5-1.rb │ ├── mapper.rb │ └── request.rb └── tasks │ └── inertia_rails.rake └── spec ├── dummy ├── .ruby-version ├── Rakefile ├── app │ ├── controllers │ │ ├── application_controller.rb │ │ ├── concerns │ │ │ └── .keep │ │ ├── inertia_child_share_test_controller.rb │ │ ├── inertia_conditional_sharing_controller.rb │ │ ├── inertia_config_test_controller.rb │ │ ├── inertia_encrypt_history_controller.rb │ │ ├── inertia_lambda_shared_props_controller.rb │ │ ├── inertia_merge_instance_props_controller.rb │ │ ├── inertia_merge_shared_controller.rb │ │ ├── inertia_multithreaded_share_controller.rb │ │ ├── inertia_rails_mimic_controller.rb │ │ ├── inertia_render_test_controller.rb │ │ ├── inertia_responders_test_controller.rb │ │ ├── inertia_session_continuity_test_controller.rb │ │ ├── inertia_share_test_controller.rb │ │ ├── inertia_test_controller.rb │ │ └── transformed_inertia_rails_mimic_controller.rb │ ├── helpers │ │ └── application_helper.rb │ ├── javascript │ │ └── packs │ │ │ └── application.js │ └── views │ │ └── layouts │ │ ├── application.html.erb │ │ ├── conditional.html.erb │ │ └── testing.html.erb ├── bin │ ├── rails │ ├── rake │ └── setup ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── application_controller_renderer.rb │ │ ├── backtrace_silencers.rb │ │ ├── content_security_policy.rb │ │ ├── cookies_serializer.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ ├── puma.rb │ ├── routes.rb │ └── spring.rb ├── log │ ├── .keep │ └── production.log └── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── apple-touch-icon-precomposed.png │ ├── apple-touch-icon.png │ └── favicon.ico ├── fixtures ├── install_generator │ ├── dummy │ │ ├── Gemfile │ │ ├── app │ │ │ └── views │ │ │ │ └── layouts │ │ │ │ └── application.html.erb │ │ └── config │ │ │ └── routes.rb │ └── with_vite │ │ ├── app │ │ └── views │ │ │ └── layouts │ │ │ └── application.html.erb │ │ ├── config │ │ └── vite.json │ │ ├── package.json │ │ └── vite.config.ts └── package_json_files │ ├── empty_package.json │ ├── react_package.json │ ├── svelte4_package.json │ ├── svelte5_caret_package.json │ ├── svelte5_exact_package.json │ ├── svelte5_tilde_package.json │ ├── tailwind_package.json │ └── vue_package.json ├── generators ├── generators_helper_spec.rb └── install │ └── install_generator_spec.rb ├── inertia ├── action_filter_spec.rb ├── always_prop_spec.rb ├── base_prop_spec.rb ├── conditional_sharing_spec.rb ├── configuration_spec.rb ├── defer_prop_spec.rb ├── encrypt_history_spec.rb ├── error_sharing_spec.rb ├── helper_spec.rb ├── lazy_prop_spec.rb ├── merge_prop_spec.rb ├── middleware_spec.rb ├── rails_mimic_spec.rb ├── rendering_spec.rb ├── request_spec.rb ├── response_spec.rb ├── rspec_helper_spec.rb ├── sharing_spec.rb └── ssr_spec.rb ├── rails_helper.rb ├── spec_helper.rb └── support ├── helper_module.rb └── shared_examples.rb /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'docs/**' 9 | - '.github/workflows/docs.yml' 10 | pull_request: 11 | paths: 12 | - 'docs/**' 13 | - '.github/workflows/docs.yml' 14 | 15 | jobs: 16 | check_formatting: 17 | runs-on: ubuntu-latest 18 | name: Check Documentation Formatting 19 | 20 | concurrency: 21 | group: ${{ github.workflow }}-${{ github.ref }} 22 | cancel-in-progress: true 23 | 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v4 27 | 28 | - name: Setup Node.js 29 | uses: actions/setup-node@v4 30 | with: 31 | node-version: '22' 32 | cache: 'npm' 33 | cache-dependency-path: './docs/package-lock.json' 34 | 35 | - name: Install dependencies 36 | working-directory: ./docs 37 | run: npm ci 38 | 39 | - name: Check formatting 40 | working-directory: ./docs 41 | run: npm run format:check 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | /.cache/ 10 | /Gemfile.lock 11 | 12 | /spec/dummy/db/*.sqlite3 13 | /spec/dummy/db/*.sqlite3-journal 14 | /spec/dummy/db/log/*.log 15 | /spec/dummy/tmp/ 16 | /spec/dummy/.sass-cache 17 | /spec/dummy/log/ 18 | 19 | # rspec failure tracking 20 | .rspec_status 21 | 22 | # Appraisal 23 | gemfiles/*.gemfile.lock 24 | 25 | # Local files, such as .env.development.local 26 | *.local 27 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require rails_helper 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | AllCops: 4 | TargetRubyVersion: 3.0 5 | NewCops: enable 6 | SuggestExtensions: false 7 | Exclude: 8 | - 'node_modules/**/*' 9 | - 'tmp/**/*' 10 | - 'vendor/**/*' 11 | - '.git/**/*' 12 | - 'docs/**/*' 13 | 14 | Metrics: 15 | Enabled: false 16 | 17 | Style/Documentation: 18 | Enabled: false 19 | 20 | Style/TrailingCommaInHashLiteral: 21 | EnforcedStyleForMultiline: consistent_comma 22 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | # Specify your gem's dependencies in inertia_rails.gemspec 6 | gemspec 7 | 8 | version = ENV['RAILS_VERSION'] || '8.0' 9 | gem 'rails', "~> #{version}.0" 10 | 11 | gem 'bundler', '~> 2.0' 12 | gem 'debug' 13 | gem 'generator_spec', '~> 0.10' 14 | gem 'rails-controller-testing' 15 | gem 'rake', '~> 13.0' 16 | gem 'responders' 17 | gem 'rspec-rails', '~> 6.0' 18 | gem 'rubocop', '~> 1.21' 19 | gem 'sqlite3' 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2025 Bellawatt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rspec/core/rake_task' 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | require 'rubocop/rake_task' 9 | 10 | RuboCop::RakeTask.new 11 | 12 | task default: %i[spec rubocop] 13 | -------------------------------------------------------------------------------- /app/controllers/inertia_rails/static_controller.rb: -------------------------------------------------------------------------------- 1 | module InertiaRails 2 | class StaticController < ::ApplicationController 3 | def static 4 | render inertia: params[:component] 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/views/inertia.html.erb: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "pathname" 4 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 5 | Pathname.new(__FILE__).realpath) 6 | 7 | require "rubygems" 8 | require "bundler/setup" 9 | require "rails/all" 10 | require "inertia_rails" 11 | 12 | require "irb" 13 | IRB.start(__FILE__) 14 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vitepress/dist 3 | .vitepress/cache 4 | -------------------------------------------------------------------------------- /docs/.prettierignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/dist 3 | **/cache 4 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/frameworksTabs.ts: -------------------------------------------------------------------------------- 1 | const localStorageKey = 'vitepress:tabsSharedState' 2 | const ls = typeof localStorage !== 'undefined' ? localStorage : null 3 | 4 | const getLocalStorageValue = (): Record => { 5 | const rawValue = ls?.getItem(localStorageKey) 6 | if (rawValue) { 7 | try { 8 | return JSON.parse(rawValue) 9 | } catch {} 10 | } 11 | return {} 12 | } 13 | 14 | const setLocalStorageValue = (v: Record) => { 15 | if (!ls) return 16 | ls.setItem(localStorageKey, JSON.stringify(v)) 17 | } 18 | 19 | export const setupFrameworksTabs = () => { 20 | const v = getLocalStorageValue() 21 | if (!v.frameworks) { 22 | setLocalStorageValue({ frameworks: 'React' }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | // https://vitepress.dev/guide/custom-theme 2 | import type { Theme } from 'vitepress' 3 | import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client' 4 | import DefaultTheme from 'vitepress/theme' 5 | import { h } from 'vue' 6 | import { setupFrameworksTabs } from './frameworksTabs' 7 | import './style.css' 8 | 9 | export default { 10 | extends: DefaultTheme, 11 | Layout: () => { 12 | return h(DefaultTheme.Layout, null, { 13 | // https://vitepress.dev/guide/extending-default-theme#layout-slots 14 | }) 15 | }, 16 | enhanceApp({ app, router, siteData }) { 17 | enhanceAppWithTabs(app) 18 | }, 19 | setup() { 20 | setupFrameworksTabs() 21 | }, 22 | } satisfies Theme 23 | -------------------------------------------------------------------------------- /docs/.vitepress/vitepress-plugin-tabs/tabsMarkdownPlugin.ts: -------------------------------------------------------------------------------- 1 | import type MarkdownIt from 'markdown-it' 2 | import container from 'markdown-it-container' 3 | import type Renderer from 'markdown-it/lib/renderer' 4 | import type Token from 'markdown-it/lib/token' 5 | import { ruleBlockTab } from './ruleBlockTab' 6 | 7 | type Params = { 8 | shareStateKey: string | undefined 9 | } 10 | 11 | const parseTabsParams = (input: string): Params => { 12 | const match = input.match(/key:(\S+)/) 13 | return { 14 | shareStateKey: match?.[1], 15 | } 16 | } 17 | 18 | export const tabsMarkdownPlugin = (md: MarkdownIt) => { 19 | md.use(container, 'tabs', { 20 | render(tokens: Token[], index: number) { 21 | const token = tokens[index] 22 | if (token.nesting === 1) { 23 | const params = parseTabsParams(token.info) 24 | const shareStateKeyProp = params.shareStateKey 25 | ? `sharedStateKey="${md.utils.escapeHtml(params.shareStateKey)}"` 26 | : '' 27 | return `\n` 28 | } else { 29 | return `\n` 30 | } 31 | }, 32 | }) 33 | 34 | md.block.ruler.after('container_tabs', 'tab', ruleBlockTab) 35 | const renderTab: Renderer.RenderRule = (tokens, index) => { 36 | const token = tokens[index] 37 | if (token.nesting === 1) { 38 | const label = token.info 39 | const labelProp = `label="${md.utils.escapeHtml(label)}"` 40 | return `\n` 41 | } else { 42 | return `\n` 43 | } 44 | } 45 | md.renderer.rules['tab_open'] = renderTab 46 | md.renderer.rules['tab_close'] = renderTab 47 | } 48 | -------------------------------------------------------------------------------- /docs/guide/asset-versioning.md: -------------------------------------------------------------------------------- 1 | # Asset versioning 2 | 3 | One common challenge when building single-page apps is refreshing site assets when they've been changed. Thankfully, Inertia makes this easy by optionally tracking the current version of your site assets. When an asset changes, Inertia will automatically make a full page visit instead of a XHR visit on the next request. 4 | 5 | ## Configuration 6 | 7 | To enable automatic asset refreshing, you need to tell Inertia the current version of your assets. This can be any arbitrary string (letters, numbers, or a file hash), as long as it changes when your assets have been updated. 8 | 9 | ```ruby 10 | InertiaRails.configure do |config| 11 | config.version = ViteRuby.digest # or any other versioning method 12 | end 13 | 14 | # You can also use lazy evaluation 15 | InertiaRails.configure do |config| 16 | config.version = lambda { ViteRuby.digest } 17 | end 18 | ``` 19 | 20 | ## Cache busting 21 | 22 | Asset refreshing in Inertia works on the assumption that a hard page visit will trigger your assets to reload. However, Inertia doesn't actually do anything to force this. Typically this is done with some form of cache busting. For example, appending a version query parameter to the end of your asset URLs. 23 | -------------------------------------------------------------------------------- /docs/guide/authentication.md: -------------------------------------------------------------------------------- 1 | # Authentication 2 | 3 | One of the benefits of using Inertia is that you don't need a special authentication system such as OAuth to connect to your data provider (API). Also, since your data is provided via your controllers, and housed on the same domain as your JavaScript components, you don't have to worry about setting up CORS. 4 | 5 | Rather, when using Inertia, you can simply use whatever authentication system you like, such as solutions based on Rails' built-in `has_secure_password` method, or gems like [Devise](https://github.com/heartcombo/devise), [Sorcery](https://github.com/Sorcery/sorcery), [Authentication Zero](https://github.com/lazaronixon/authentication-zero), etc. 6 | -------------------------------------------------------------------------------- /docs/guide/authorization.md: -------------------------------------------------------------------------------- 1 | # Authorization 2 | 3 | When using Inertia, authorization is best handled server-side in your application's authorization policies. However, you may be wondering how to perform checks against your authorization policies from within your Inertia page components since you won't have access to your framework's server-side helpers. 4 | 5 | The simplest approach to solving this problem is to pass the results of your authorization checks as props to your page components. 6 | 7 | Here's an example of how you might do this in a Rails controller using the [Action Policy](https://github.com/palkan/action_policy) gem: 8 | 9 | ```ruby 10 | class UsersController < ApplicationController 11 | def index 12 | render inertia: 'Users/Index', props: { 13 | can: { 14 | create_user: allowed_to?(:create, User) 15 | }, 16 | users: User.all.map do |user| 17 | user.as_json( 18 | only: [:id, :first_name, :last_name, :email] 19 | ).merge( 20 | can: { 21 | edit_user: allowed_to?(:edit, user) 22 | } 23 | ) 24 | end 25 | } 26 | end 27 | end 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/guide/demo-application.md: -------------------------------------------------------------------------------- 1 | # Demo application 2 | 3 | We've setup a demo app for Inertia.js called [Ping CRM](https://demo.inertiajs.com/login). This application is built using Laravel and Vue. You can find the source code on [GitHub](https://github.com/inertiajs/pingcrm). 4 | 5 | > [!NOTE] 6 | > The Ping CRM demo is hosted on Heroku and the database is reset every hour. Please be respectful when editing data. 7 | 8 | [![Ping CRM demo](/pingcrm.png)](https://demo.inertiajs.com/login) 9 | 10 | In addition to the Vue version of Ping CRM, we also maintain a Svelte version of the application, which you can find [on GitHub](https://github.com/inertiajs/pingcrm-svelte). 11 | 12 | ## Third party 13 | 14 | Beyond our official demo app, Ping CRM has also been translated into numerous different languages and frameworks. 15 | 16 | - [Ruby on Rails/Vue](https://github.com/ledermann/pingcrm) by Georg Ledermann 17 | - [Ruby on Rails/Vue SSR/Vite](https://github.com/ElMassimo/pingcrm-vite) by Máximo Mussini 18 | - [Laravel/React](https://github.com/Landish/pingcrm-react) by Lado Lomidze 19 | - [Laravel/Svelte](https://github.com/zgabievi/pingcrm-svelte) by Zura G]abievi 20 | - [Laravel/Mithril.js](https://github.com/tbreuss/pingcrm-mithril) by Thomas Breuss 21 | - [Yii 2/Vue](https://github.com/tbreuss/pingcrm-yii2) by Thomas Breuss 22 | - [Symfony/Vue](https://github.com/aleksblendwerk/pingcrm-symfony) by Aleks Seltenreich 23 | - [Clojure/React](https://github.com/prestancedesign/pingcrm-clojure) by Michaël Salihi 24 | -------------------------------------------------------------------------------- /docs/guide/routing.md: -------------------------------------------------------------------------------- 1 | # Routing 2 | 3 | ## Defining routes 4 | 5 | When using Inertia, all of your application's routes are defined server-side. This means that you don't need Vue Router or React Router. Instead, you can simply define Rails routes and return Inertia responses from those routes. 6 | 7 | ## Shorthand routes 8 | 9 | If you have a page that doesn't need a corresponding controller method, like an "FAQ" or "about" page, you can route directly to a component via the `inertia` method. 10 | 11 | ```ruby 12 | inertia 'about' => 'AboutComponent' 13 | ``` 14 | 15 | ## Generating URLs 16 | 17 | Some server-side frameworks allow you to generate URLs from named routes. However, you will not have access to those helpers client-side. Here are a couple ways to still use named routes with Inertia. 18 | 19 | The first option is to generate URLs server-side and include them as props. Notice in this example how we're passing the `edit_url` and `create_url` to the `Users/Index` component. 20 | 21 | ```ruby 22 | class UsersController < ApplicationController 23 | def index 24 | render inertia: 'Users/Index', props: { 25 | users: User.all.map do |user| 26 | user.as_json( 27 | only: [ :id, :name, :email ] 28 | ).merge( 29 | edit_url: edit_user_path(user) 30 | ) 31 | end, 32 | create_url: new_user_path 33 | } 34 | end 35 | end 36 | ``` 37 | 38 | Another option is to use [JsRoutes](https://github.com/railsware/js-routes) or [JS From Routes](https://js-from-routes.netlify.app) gems that make named server-side routes available on the client via autogenerated helpers. 39 | -------------------------------------------------------------------------------- /docs/guide/who-is-it-for.md: -------------------------------------------------------------------------------- 1 | # Who is Inertia.js for? 2 | 3 | Inertia was crafted for development teams and solo hackers who typically build server-side rendered applications using frameworks like Laravel, Ruby on Rails, or Django. You're used to creating controllers, retrieving data from the database (via an ORM), and rendering views. 4 | 5 | But what happens when you want to replace your server-side rendered views with a modern, JavaScript-based single-page application frontend? The answer is always "you need to build an API". Because that's how modern SPAs are built. 6 | 7 | This means building a REST or GraphQL API. It means figuring out authentication and authorization for that API. It means client-side state management. It means setting up a new Git repository. It means a more complicated deployment strategy. And this list goes on. It's a complete paradigm shift, and often a complete mess. We think there is a better way. 8 | 9 | **Inertia empowers you to build a modern, JavaScript-based single-page application without the tiresome complexity.** 10 | 11 | Inertia works just like a classic server-side rendered application. You create controllers, you get data from the database (via your ORM), and you render views. But, Inertia views are JavaScript page components written in React, Vue, or Svelte. 12 | 13 | This means you get all the power of a client-side application and modern SPA experience, but you don't need to build an API. We think it's a breath of fresh air that will supercharge your productivity. 14 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: 'Inertia.js Rails' 7 | text: 'Build single-page apps, without building an API.' 8 | tagline: |- 9 | Create modern single-page React, Vue, and Svelte apps using classic server-side routing. 10 | actions: 11 | - theme: brand 12 | text: Get Started 13 | link: /guide 14 | - theme: alt 15 | text: Install 16 | link: /guide/server-side-setup 17 | image: 18 | src: /logo.svg 19 | alt: Inertia.js Logo 20 | --- 21 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "docs:dev": "vitepress dev", 4 | "docs:build": "vitepress build", 5 | "docs:preview": "vitepress preview", 6 | "format:check": "prettier --check .", 7 | "format": "prettier --write ." 8 | }, 9 | "devDependencies": { 10 | "markdown-it-container": "^4.0.0", 11 | "prettier": "^3.3.3", 12 | "prettier-plugin-organize-imports": "^4.1.0", 13 | "prettier-plugin-svelte": "^3.2.7", 14 | "prettier-plugin-tailwindcss": "^0.6.8", 15 | "prettier-plugin-vue": "^1.1.6", 16 | "vitepress": "^1.2.2", 17 | "vitepress-plugin-tabs": "^0.6.0", 18 | "vue": "^3.4.27" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | semi: false, 4 | singleQuote: true, 5 | tabWidth: 2, 6 | trailingComma: 'all', 7 | plugins: [ 8 | 'prettier-plugin-svelte', 9 | 'prettier-plugin-organize-imports', 10 | 'prettier-plugin-tailwindcss', 11 | ], 12 | } 13 | -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inertiajs/inertia-rails/291d0d2f09b8085881b93474879d40d413fa55fc/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/public/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inertiajs/inertia-rails/291d0d2f09b8085881b93474879d40d413fa55fc/docs/public/logo.jpg -------------------------------------------------------------------------------- /docs/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/public/og_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inertiajs/inertia-rails/291d0d2f09b8085881b93474879d40d413fa55fc/docs/public/og_image.jpg -------------------------------------------------------------------------------- /docs/public/pingcrm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inertiajs/inertia-rails/291d0d2f09b8085881b93474879d40d413fa55fc/docs/public/pingcrm.png -------------------------------------------------------------------------------- /inertia_rails.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'lib/inertia_rails/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'inertia_rails' 7 | spec.version = InertiaRails::VERSION 8 | spec.authors = ['Brian Knoles', 'Brandon Shar', 'Eugene Granovsky'] 9 | spec.email = ['brian@bellawatt.com', 'brandon@bellawatt.com', 'eugene@bellawatt.com'] 10 | 11 | spec.summary = 'Inertia.js adapter for Rails' 12 | spec.description = 'Quickly build modern single-page React, Vue and Svelte apps ' \ 13 | 'using classic server-side routing and controllers.' 14 | spec.homepage = 'https://github.com/inertiajs/inertia-rails' 15 | spec.license = 'MIT' 16 | 17 | spec.required_ruby_version = '>= 3.0' 18 | 19 | spec.metadata = { 20 | 'bug_tracker_uri' => "#{spec.homepage}/issues", 21 | 'changelog_uri' => "#{spec.homepage}/blob/master/CHANGELOG.md", 22 | 'documentation_uri' => "#{spec.homepage}/blob/master/README.md", 23 | 'homepage_uri' => spec.homepage, 24 | 'source_code_uri' => spec.homepage, 25 | 'rubygems_mfa_required' => 'true', 26 | } 27 | 28 | spec.files = Dir['{app,lib}/**/*', 'CHANGELOG.md', 'LICENSE.txt', 'README.md'] 29 | spec.require_paths = ['lib'] 30 | 31 | spec.add_dependency 'railties', '>= 6' 32 | end 33 | -------------------------------------------------------------------------------- /lib/generators/inertia/controller/controller_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails/generators/rails/controller/controller_generator' 4 | require 'inertia_rails/generators/helper' 5 | 6 | module Inertia 7 | module Generators 8 | class ControllerGenerator < Rails::Generators::ControllerGenerator 9 | include InertiaRails::Generators::Helper 10 | 11 | source_root File.expand_path('./templates', __dir__) 12 | 13 | remove_hook_for :template_engine 14 | 15 | hook_for :inertia_templates, required: true, default: InertiaRails::Generators::Helper.guess_inertia_template 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/generators/inertia/controller/templates/controller.rb.tt: -------------------------------------------------------------------------------- 1 | <% module_namespacing do -%> 2 | class <%= class_name %>Controller < <%= parent_class_name.classify %> 3 | <% actions.each do |action| -%> 4 | def <%= action %> 5 | render inertia: '<%= "#{inertia_base_path}/#{action.camelize}" %>' 6 | end 7 | <%= "\n" unless action == actions.last -%> 8 | <% end -%> 9 | end 10 | <% end -%> 11 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/helpers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Inertia 4 | module Generators 5 | module Helpers 6 | ### FS Helpers 7 | def js_destination_path 8 | return ViteRuby.config.source_code_dir if defined?(ViteRuby) 9 | 10 | if file?('config/vite.json') 11 | source_code_dir = JSON.parse(File.read(file_path('config/vite.json'))).dig('all', 'sourceCodeDir') 12 | return source_code_dir if source_code_dir 13 | end 14 | 15 | 'app/frontend' 16 | end 17 | 18 | def js_destination_root 19 | file_path(js_destination_path) 20 | end 21 | 22 | def js_file_path(*relative_path) 23 | File.join(js_destination_root, *relative_path) 24 | end 25 | 26 | def file?(*relative_path) 27 | File.file?(file_path(*relative_path)) 28 | end 29 | 30 | def file_path(*relative_path) 31 | File.join(destination_root, *relative_path) 32 | end 33 | 34 | # Interactivity Helpers 35 | def ask(*) 36 | unless options[:interactive] 37 | say_error 'Specify all options when running the generator non-interactively.', :red 38 | exit(1) 39 | end 40 | 41 | super 42 | end 43 | 44 | def yes?(*) 45 | return false unless options[:interactive] 46 | 47 | super 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/js_package_manager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Inertia 4 | module Generators 5 | class JSPackageManager 6 | def self.package_managers 7 | %w[npm yarn bun pnpm] 8 | end 9 | 10 | def initialize(generator) 11 | @generator = generator 12 | end 13 | 14 | def present? 15 | package_manager.present? 16 | end 17 | 18 | def add_dependencies(*dependencies) 19 | options = @generator.options[:verbose] ? '' : ' --silent' 20 | @generator.in_root do 21 | @generator.run "#{package_manager} add #{dependencies.join(' ')}#{options}" 22 | end 23 | end 24 | 25 | private 26 | 27 | def package_manager 28 | @package_manager ||= @generator.options[:package_manager] || detect_package_manager 29 | end 30 | 31 | def detect_package_manager 32 | return nil unless file?('package.json') 33 | 34 | if file?('package-lock.json') 35 | 'npm' 36 | elsif file?('bun.lockb') 37 | 'bun' 38 | elsif file?('pnpm-lock.yaml') 39 | 'pnpm' 40 | else 41 | 'yarn' 42 | end 43 | end 44 | 45 | def file?(*relative_path) 46 | @generator.file?(*relative_path) 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/assets/inertia.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/assets/vite_ruby.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class InertiaExampleController < ApplicationController 4 | def index 5 | render inertia: 'InertiaExample', props: { 6 | name: params.fetch(:name, 'World'), 7 | } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | export PORT="${PORT:-3000}" 4 | 5 | if command -v overmind 1> /dev/null 2>&1 6 | then 7 | overmind start -f Procfile.dev "$@" 8 | exit $? 9 | fi 10 | 11 | if command -v hivemind 1> /dev/null 2>&1 12 | then 13 | echo "Hivemind is installed. Running the application with Hivemind..." 14 | exec hivemind Procfile.dev "$@" 15 | exit $? 16 | fi 17 | 18 | if gem list --no-installed --exact --silent foreman; then 19 | echo "Installing foreman..." 20 | gem install foreman 21 | fi 22 | 23 | foreman start -f Procfile.dev "$@" 24 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/initializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | InertiaRails.configure do |config| 4 | config.ssr_enabled = ViteRuby.config.ssr_build_enabled 5 | config.version = ViteRuby.digest 6 | end 7 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/react/InertiaExample.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | color: #213547; 6 | background-color: #ffffff; 7 | max-width: 1280px; 8 | margin: 0 auto; 9 | padding: 2rem; 10 | text-align: center; 11 | } 12 | 13 | .h1 { 14 | font-size: 3.2em; 15 | line-height: 1.1; 16 | } 17 | 18 | .h2 { 19 | font-size: 2.6em; 20 | line-height: 1.1; 21 | } 22 | 23 | .button { 24 | border-radius: 8px; 25 | border: 1px solid transparent; 26 | padding: 0.6em 1.2em; 27 | font-size: 1em; 28 | font-weight: 500; 29 | font-family: inherit; 30 | background-color: #f9f9f9; 31 | cursor: pointer; 32 | transition: border-color 0.25s; 33 | } 34 | .button:hover { 35 | border-color: #646cff; 36 | } 37 | .button:focus, 38 | .button:focus-visible { 39 | outline: 4px auto -webkit-focus-ring-color; 40 | } 41 | 42 | .logo { 43 | display: inline-block; 44 | height: 6em; 45 | padding: 1.5em; 46 | will-change: filter; 47 | transition: filter 300ms; 48 | } 49 | .logo:hover { 50 | filter: drop-shadow(0 0 2em #646cffaa); 51 | } 52 | .logo.vite:hover { 53 | filter: drop-shadow(0 0 2em #e4023baa); 54 | } 55 | .logo.react:hover { 56 | filter: drop-shadow(0 0 2em #61dafbaa); 57 | } 58 | 59 | @keyframes logo-spin { 60 | from { 61 | transform: rotate(0deg); 62 | } 63 | to { 64 | transform: rotate(360deg); 65 | } 66 | } 67 | 68 | @media (prefers-reduced-motion: no-preference) { 69 | .logo.react { 70 | animation: logo-spin infinite 20s linear; 71 | } 72 | } 73 | 74 | .card { 75 | padding: 2em; 76 | } 77 | 78 | .read-the-docs { 79 | color: #888; 80 | } 81 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/react/inertia.js: -------------------------------------------------------------------------------- 1 | import { createInertiaApp } from '@inertiajs/react' 2 | import { createElement } from 'react' 3 | import { createRoot } from 'react-dom/client' 4 | 5 | createInertiaApp({ 6 | // Set default page title 7 | // see https://inertia-rails.dev/guide/title-and-meta 8 | // 9 | // title: title => title ? `${title} - App` : 'App', 10 | 11 | // Disable progress bar 12 | // 13 | // see https://inertia-rails.dev/guide/progress-indicators 14 | // progress: false, 15 | 16 | resolve: (name) => { 17 | const pages = import.meta.glob('../pages/**/*.jsx', { 18 | eager: true, 19 | }) 20 | const page = pages[`../pages/${name}.jsx`] 21 | if (!page) { 22 | console.error(`Missing Inertia page component: '${name}.jsx'`) 23 | } 24 | 25 | // To use a default layout, import the Layout component 26 | // and use the following lines. 27 | // see https://inertia-rails.dev/guide/pages#default-layouts 28 | // 29 | // page.default.layout ||= (page) => createElement(Layout, null, page) 30 | 31 | return page 32 | }, 33 | 34 | setup({ el, App, props }) { 35 | if (el) { 36 | createRoot(el).render(createElement(App, props)) 37 | } else { 38 | console.error( 39 | 'Missing root element.\n\n' + 40 | 'If you see this error, it probably means you load Inertia.js on non-Inertia pages.\n' + 41 | 'Consider moving <%%= vite_javascript_tag "inertia" %> to the Inertia-specific layout instead.', 42 | ) 43 | } 44 | }, 45 | }) 46 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/react/inertia.ts: -------------------------------------------------------------------------------- 1 | import { createInertiaApp } from '@inertiajs/react' 2 | import { createElement, ReactNode } from 'react' 3 | import { createRoot } from 'react-dom/client' 4 | 5 | // Temporary type definition, until @inertiajs/react provides one 6 | type ResolvedComponent = { 7 | default: ReactNode 8 | layout?: (page: ReactNode) => ReactNode 9 | } 10 | 11 | createInertiaApp({ 12 | // Set default page title 13 | // see https://inertia-rails.dev/guide/title-and-meta 14 | // 15 | // title: title => title ? `${title} - App` : 'App', 16 | 17 | // Disable progress bar 18 | // 19 | // see https://inertia-rails.dev/guide/progress-indicators 20 | // progress: false, 21 | 22 | resolve: (name) => { 23 | const pages = import.meta.glob('../pages/**/*.tsx', { 24 | eager: true, 25 | }) 26 | const page = pages[`../pages/${name}.tsx`] 27 | if (!page) { 28 | console.error(`Missing Inertia page component: '${name}.tsx'`) 29 | } 30 | 31 | // To use a default layout, import the Layout component 32 | // and use the following line. 33 | // see https://inertia-rails.dev/guide/pages#default-layouts 34 | // 35 | // page.default.layout ||= (page) => createElement(Layout, null, page) 36 | 37 | return page 38 | }, 39 | 40 | setup({ el, App, props }) { 41 | if (el) { 42 | createRoot(el).render(createElement(App, props)) 43 | } else { 44 | console.error( 45 | 'Missing root element.\n\n' + 46 | 'If you see this error, it probably means you load Inertia.js on non-Inertia pages.\n' + 47 | 'Consider moving <%%= vite_typescript_tag "inertia" %> to the Inertia-specific layout instead.', 48 | ) 49 | } 50 | }, 51 | }) 52 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/react/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 5 | "target": "ES2020", 6 | "useDefineForClassFields": true, 7 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 8 | "module": "ESNext", 9 | "skipLibCheck": true, 10 | 11 | /* Bundler mode */ 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "moduleDetection": "force", 17 | "noEmit": true, 18 | "jsx": "react-jsx", 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true, 25 | }, 26 | "include": ["<%= js_destination_path %>"] 27 | } 28 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.app.json" 6 | }, 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/react/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 | "skipLibCheck": true, 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "noEmit": true 11 | }, 12 | "include": ["vite.config.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/react/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/svelte/inertia.js: -------------------------------------------------------------------------------- 1 | import { createInertiaApp } from '@inertiajs/svelte' 2 | import { mount } from 'svelte'; 3 | 4 | createInertiaApp({ 5 | // Set default page title 6 | // see https://inertia-rails.dev/guide/title-and-meta 7 | // 8 | // title: title => title ? `${title} - App` : 'App', 9 | 10 | // Disable progress bar 11 | // 12 | // see https://inertia-rails.dev/guide/progress-indicators 13 | // progress: false, 14 | 15 | resolve: (name) => { 16 | const pages = import.meta.glob('../pages/**/*.svelte', { 17 | eager: true, 18 | }) 19 | const page = pages[`../pages/${name}.svelte`] 20 | if (!page) { 21 | console.error(`Missing Inertia page component: '${name}.svelte'`) 22 | } 23 | 24 | // To use a default layout, import the Layout component 25 | // and use the following line. 26 | // see https://inertia-rails.dev/guide/pages#default-layouts 27 | // 28 | // return { default: page.default, layout: page.layout || Layout } 29 | 30 | return page 31 | }, 32 | 33 | setup({ el, App, props }) { 34 | if (el) { 35 | mount(App, { target: el, props }) 36 | } else { 37 | console.error( 38 | 'Missing root element.\n\n' + 39 | 'If you see this error, it probably means you load Inertia.js on non-Inertia pages.\n' + 40 | 'Consider moving <%%= vite_javascript_tag "inertia" %> to the Inertia-specific layout instead.', 41 | ) 42 | } 43 | }, 44 | }) 45 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/svelte/inertia.ts.tt: -------------------------------------------------------------------------------- 1 | import { createInertiaApp, type ResolvedComponent } from '@inertiajs/svelte' 2 | import { mount } from 'svelte' 3 | 4 | createInertiaApp({ 5 | // Set default page title 6 | // see https://inertia-rails.dev/guide/title-and-meta 7 | // 8 | // title: title => title ? `${title} - App` : 'App', 9 | 10 | // Disable progress bar 11 | // 12 | // see https://inertia-rails.dev/guide/progress-indicators 13 | // progress: false, 14 | 15 | resolve: (name) => { 16 | const pages = import.meta.glob('../pages/**/*.svelte', { 17 | eager: true, 18 | }) 19 | const page = pages[`../pages/${name}.svelte`] 20 | if (!page) { 21 | console.error(`Missing Inertia page component: '${name}.svelte'`) 22 | } 23 | 24 | // To use a default layout, import the Layout component 25 | // and use the following line. 26 | // see https://inertia-rails.dev/guide/pages#default-layouts 27 | // 28 | // return { default: page.default, layout: page.layout || Layout } 29 | 30 | return page 31 | }, 32 | 33 | setup({ el, App, props }) { 34 | if (el) { 35 | <%= " // @ts-expect-error 1.3.0 contains types mismatch\n" if inertia_resolved_version.release == Gem::Version.new('1.3.0') -%> 36 | mount(App, { target: el, props }) 37 | } else { 38 | console.error( 39 | 'Missing root element.\n\n' + 40 | 'If you see this error, it probably means you load Inertia.js on non-Inertia pages.\n' + 41 | 'Consider moving <%%= vite_typescript_tag "inertia" %> to the Inertia-specific layout instead.', 42 | ) 43 | } 44 | }, 45 | }) 46 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/svelte/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | } 8 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/svelte/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "resolveJsonModule": true, 8 | /** 9 | * Typecheck JS in `.svelte` and `.js` files by default. 10 | * Disable checkJs if you'd like to use dynamic types in JS. 11 | * Note that setting allowJs false does not prevent the use 12 | * of JS in `.svelte` files. 13 | */ 14 | "allowJs": true, 15 | "checkJs": true, 16 | "isolatedModules": true, 17 | "moduleDetection": "force", 18 | }, 19 | "include": ["<%= js_destination_path %>/**/*.ts", "<%= js_destination_path %>/**/*.js", "<%= js_destination_path %>/**/*.svelte"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/svelte/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 | "skipLibCheck": true, 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "strict": true, 9 | "noEmit": true, 10 | }, 11 | "include": ["vite.config.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/svelte/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/svelte4/inertia.js: -------------------------------------------------------------------------------- 1 | import { createInertiaApp } from '@inertiajs/svelte' 2 | 3 | createInertiaApp({ 4 | // Set default page title 5 | // see https://inertia-rails.dev/guide/title-and-meta 6 | // 7 | // title: title => title ? `${title} - App` : 'App', 8 | 9 | // Disable progress bar 10 | // 11 | // see https://inertia-rails.dev/guide/progress-indicators 12 | // progress: false, 13 | 14 | resolve: (name) => { 15 | const pages = import.meta.glob('../pages/**/*.svelte', { 16 | eager: true, 17 | }) 18 | const page = pages[`../pages/${name}.svelte`] 19 | if (!page) { 20 | console.error(`Missing Inertia page component: '${name}.svelte'`) 21 | } 22 | 23 | // To use a default layout, import the Layout component 24 | // and use the following lines. 25 | // see https://inertia-rails.dev/guide/pages#default-layouts 26 | // 27 | // return { default: page.default, layout: page.layout || Layout } 28 | 29 | return page 30 | }, 31 | 32 | setup({ el, App, props }) { 33 | if (el) { 34 | new App({ target: el, props }) 35 | } else { 36 | console.error( 37 | 'Missing root element.\n\n' + 38 | 'If you see this error, it probably means you load Inertia.js on non-Inertia pages.\n' + 39 | 'Consider moving <%%= vite_javascript_tag "inertia" %> to the Inertia-specific layout instead.', 40 | ) 41 | } 42 | }, 43 | }) 44 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/svelte4/inertia.ts.tt: -------------------------------------------------------------------------------- 1 | import { createInertiaApp, type ResolvedComponent } from '@inertiajs/svelte' 2 | 3 | createInertiaApp({ 4 | // Set default page title 5 | // see https://inertia-rails.dev/guide/title-and-meta 6 | // 7 | // title: title => title ? `${title} - App` : 'App', 8 | 9 | // Disable progress bar 10 | // 11 | // see https://inertia-rails.dev/guide/progress-indicators 12 | // progress: false, 13 | 14 | resolve: (name) => { 15 | const pages = import.meta.glob('../pages/**/*.svelte', { 16 | eager: true, 17 | }) 18 | const page = pages[`../pages/${name}.svelte`] 19 | if (!page) { 20 | console.error(`Missing Inertia page component: '${name}.svelte'`) 21 | } 22 | 23 | // To use a default layout, import the Layout component 24 | // and use the following line. 25 | // see https://inertia-rails.dev/guide/pages#default-layouts 26 | // 27 | // return { default: page.default, layout: page.layout || Layout } 28 | 29 | return page 30 | }, 31 | 32 | setup({ el, App, props }) { 33 | if (el) { 34 | <%= "// @ts-expect-error 1.3.0 beta contains types mismatch\n" if inertia_resolved_version.release == Gem::Version.new('1.3.0') -%> 35 | new App({ target: el, props }) 36 | } else { 37 | console.error( 38 | 'Missing root element.\n\n' + 39 | 'If you see this error, it probably means you load Inertia.js on non-Inertia pages.\n' + 40 | 'Consider moving <%%= vite_javascript_tag "inertia" %> to the Inertia-specific layout instead.', 41 | ) 42 | } 43 | }, 44 | }) 45 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/svelte4/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | } 8 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/svelte4/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "resolveJsonModule": true, 8 | /** 9 | * Typecheck JS in `.svelte` and `.js` files by default. 10 | * Disable checkJs if you'd like to use dynamic types in JS. 11 | * Note that setting allowJs false does not prevent the use 12 | * of JS in `.svelte` files. 13 | */ 14 | "allowJs": true, 15 | "checkJs": true, 16 | "isolatedModules": true, 17 | "moduleDetection": "force", 18 | }, 19 | "include": ["<%= js_destination_path %>/**/*.ts", "<%= js_destination_path %>/**/*.js", "<%= js_destination_path %>/**/*.svelte"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/svelte4/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 | "skipLibCheck": true, 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "strict": true, 9 | "noEmit": true, 10 | }, 11 | "include": ["vite.config.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/svelte4/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/tailwind/application.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | @plugin "@tailwindcss/typography"; 4 | @plugin "@tailwindcss/forms"; 5 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/vue/inertia.js: -------------------------------------------------------------------------------- 1 | import { createInertiaApp } from '@inertiajs/vue3' 2 | import { createApp, h } from 'vue' 3 | 4 | createInertiaApp({ 5 | // Set default page title 6 | // see https://inertia-rails.dev/guide/title-and-meta 7 | // 8 | // title: title => title ? `${title} - App` : 'App', 9 | 10 | // Disable progress bar 11 | // 12 | // see https://inertia-rails.dev/guide/progress-indicators 13 | // progress: false, 14 | 15 | resolve: (name) => { 16 | const pages = import.meta.glob('../pages/**/*.vue', { 17 | eager: true, 18 | }) 19 | return pages[`../pages/${name}.vue`] 20 | 21 | // To use a default layout, import the Layout component 22 | // and use the following lines. 23 | // see https://inertia-rails.dev/guide/pages#default-layouts 24 | // 25 | // const page = pages[`../pages/${name}.vue`] 26 | // page.default.layout = page.default.layout || Layout 27 | // return page 28 | }, 29 | 30 | setup({ el, App, props, plugin }) { 31 | createApp({ render: () => h(App, props) }) 32 | .use(plugin) 33 | .mount(el) 34 | }, 35 | }) 36 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/vue/inertia.ts: -------------------------------------------------------------------------------- 1 | import { createInertiaApp } from '@inertiajs/vue3' 2 | import { createApp, DefineComponent, h } from 'vue' 3 | 4 | createInertiaApp({ 5 | // Set default page title 6 | // see https://inertia-rails.dev/guide/title-and-meta 7 | // 8 | // title: title => title ? `${title} - App` : 'App', 9 | 10 | // Disable progress bar 11 | // 12 | // see https://inertia-rails.dev/guide/progress-indicators 13 | // progress: false, 14 | 15 | resolve: (name) => { 16 | const pages = import.meta.glob('../pages/**/*.vue', { 17 | eager: true, 18 | }) 19 | return pages[`../pages/${name}.vue`] 20 | 21 | // To use a default layout, import the Layout component 22 | // and use the following lines. 23 | // see https://inertia-rails.dev/guide/pages#default-layouts 24 | // 25 | // const page = pages[`../pages/${name}.vue`] 26 | // page.default.layout = page.default.layout || Layout 27 | // return page 28 | }, 29 | 30 | setup({ el, App, props, plugin }) { 31 | createApp({ render: () => h(App, props) }) 32 | .use(plugin) 33 | .mount(el) 34 | }, 35 | }) 36 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/vue/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | "jsx": "preserve", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["<%= js_destination_path %>/**/*.ts", "<%= js_destination_path %>/**/*.tsx", "<%= js_destination_path %>/**/*.vue"] 24 | } 25 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.app.json" 6 | }, 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/vue/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "lib": ["ES2023"], 5 | "module": "ESNext", 6 | "skipLibCheck": true, 7 | 8 | /* Bundler mode */ 9 | "moduleResolution": "bundler", 10 | "allowImportingTsExtensions": true, 11 | "isolatedModules": true, 12 | "moduleDetection": "force", 13 | "noEmit": true, 14 | 15 | /* Linting */ 16 | "strict": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "noFallthroughCasesInSwitch": true 20 | }, 21 | "include": ["vite.config.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /lib/generators/inertia/install/templates/vue/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /lib/generators/inertia/scaffold/scaffold_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails/generators/rails/resource/resource_generator' 4 | 5 | module Inertia 6 | module Generators 7 | class ScaffoldGenerator < Rails::Generators::ResourceGenerator # :nodoc: 8 | remove_hook_for :resource_controller 9 | remove_class_option :actions 10 | 11 | class_option :resource_route, type: :boolean 12 | 13 | hook_for :scaffold_controller, required: true 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/controller/controller_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'inertia_rails/generators/controller_template_base' 4 | 5 | module InertiaTemplates 6 | module Generators 7 | class ControllerGenerator < InertiaRails::Generators::ControllerTemplateBase 8 | hide! 9 | source_root File.expand_path('./templates', __dir__) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/controller/templates/react/view.jsx.tt: -------------------------------------------------------------------------------- 1 | export default function <%= @action.camelize %>() { 2 | return ( 3 | <> 4 |

<%= class_name %>#<%= @action %>

5 |

Find me in <%= @path %>

6 | 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/controller/templates/react/view.tsx.tt: -------------------------------------------------------------------------------- 1 | export default function <%= @action.camelize %>() { 2 | return ( 3 | <> 4 |

<%= class_name %>#<%= @action %>

5 |

Find me in <%= @path %>

6 | 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/controller/templates/svelte/view.svelte.tt: -------------------------------------------------------------------------------- 1 |

<%= class_name %>#<%= @action %>

2 |

Find me in <%= @path %>

3 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/controller/templates/svelte4/view.svelte.tt: -------------------------------------------------------------------------------- 1 |

<%= class_name %>#<%= @action %>

2 |

Find me in <%= @path %>

3 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/controller/templates/vue/view.vue.tt: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/scaffold_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'inertia_rails/generators/scaffold_template_base' 4 | 5 | module InertiaTemplates 6 | module Generators 7 | class ScaffoldGenerator < InertiaRails::Generators::ScaffoldTemplateBase 8 | hide! 9 | source_root File.expand_path('./templates', __dir__) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/react/Edit.jsx.tt: -------------------------------------------------------------------------------- 1 | import { Head, Link } from '@inertiajs/react' 2 | import Form from './Form' 3 | 4 | export default function Edit({ <%= singular_table_name %> }) { 5 | return ( 6 | <> 7 | 8 | 9 |

Editing <%= human_name.downcase %>

10 | 11 |
={<%= singular_table_name %>} 13 | onSubmit={(form) => { 14 | form.transform((data) => ({ <%= singular_table_name %>: data })) 15 | <% if attributes.any?(&:attachments?) -%> 16 | form.post(`<%= js_resource_path %>`, { 17 | headers: { 'X-HTTP-METHOD-OVERRIDE': 'put' }, 18 | }) 19 | <% else -%> 20 | form.patch(`<%= js_resource_path %>`) 21 | <% end -%> 22 | }} 23 | submitText="Update <%= human_name %>" 24 | /> 25 | 26 |
27 | 28 |
29 | `}>Show this <%= human_name.downcase %> 30 | {' | '} 31 | Back to <%= human_name.pluralize.downcase %> 32 |
33 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/react/Edit.tsx.tt: -------------------------------------------------------------------------------- 1 | import { Head, Link } from '@inertiajs/react' 2 | import Form from './Form' 3 | import { <%= inertia_model_type %> } from './types' 4 | 5 | interface EditProps { 6 | <%= singular_table_name %>: <%= inertia_model_type %> 7 | } 8 | 9 | export default function Edit({ <%= singular_table_name %> }: EditProps) { 10 | return ( 11 | <> 12 | 13 | 14 |

Editing <%= human_name.downcase %>

15 | 16 | ={<%= singular_table_name %>} 18 | onSubmit={(form) => { 19 | form.transform((data) => ({ <%= singular_table_name %>: data })) 20 | <% if attributes.any?(&:attachments?) -%> 21 | form.post(`<%= js_resource_path %>`, { 22 | headers: { 'X-HTTP-METHOD-OVERRIDE': 'put' }, 23 | }) 24 | <% else -%> 25 | form.patch(`<%= js_resource_path %>`) 26 | <% end -%> 27 | }} 28 | submitText="Update <%= human_name %>" 29 | /> 30 | 31 |
32 | 33 |
34 | `}>Show this <%= human_name.downcase %> 35 | {' | '} 36 | Back to <%= human_name.pluralize.downcase %> 37 |
38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/react/Index.jsx.tt: -------------------------------------------------------------------------------- 1 | import { Head, Link } from '@inertiajs/react' 2 | import <%= inertia_component_name %> from './<%= inertia_component_name %>' 3 | 4 | export default function Index({ <%= plural_table_name %>, flash }) { 5 | return ( 6 | <> 7 | 8 | 9 | {flash.notice &&

{flash.notice}

} 10 | 11 |

<%= human_name.pluralize %>

12 |
13 | {<%= plural_table_name %>.map((<%= singular_table_name %>) => ( 14 |
.id}> 15 | <<%= inertia_component_name %> <%= singular_table_name %>={<%= singular_table_name %>} /> 16 |

17 | `}>Show this <%= human_name.downcase %> 18 |

19 |
20 | ))} 21 |
22 | 23 | New <%= human_name.downcase %> 24 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/react/Index.tsx.tt: -------------------------------------------------------------------------------- 1 | import { Head, Link } from '@inertiajs/react' 2 | import <%= inertia_component_name %> from './<%= inertia_component_name %>' 3 | import { <%= inertia_model_type %> } from './types' 4 | 5 | interface IndexProps { 6 | <%= plural_table_name %>: <%= inertia_model_type %>[] 7 | flash: { notice?: string } 8 | } 9 | 10 | export default function Index({ <%= plural_table_name %>, flash }: IndexProps) { 11 | return ( 12 | <> 13 | 14 | 15 | {flash.notice &&

{flash.notice}

} 16 | 17 |

<%= human_name.pluralize %>

18 |
19 | {<%= plural_table_name %>.map((<%= singular_table_name %>) => ( 20 |
.id}> 21 | <<%= inertia_component_name %> <%= singular_table_name %>={<%= singular_table_name %>} /> 22 |

23 | `}>Show this <%= human_name.downcase %> 24 |

25 |
26 | ))} 27 |
28 | 29 | New <%= human_name.downcase %> 30 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/react/New.jsx.tt: -------------------------------------------------------------------------------- 1 | import { Head, Link } from '@inertiajs/react' 2 | import Form from './Form' 3 | 4 | export default function New({ <%= singular_table_name %> }) { 5 | return ( 6 | <> 7 | 8 | 9 |

New <%= human_name.downcase %>

10 | 11 | ={<%= singular_table_name %>} 13 | onSubmit={(form) => { 14 | form.transform((data) => ({ <%= singular_table_name %>: data })) 15 | form.post('<%= js_resources_path %>') 16 | }} 17 | submitText="Create <%= human_name %>" 18 | /> 19 | 20 |
21 | 22 |
23 | Back to <%= human_name.pluralize.downcase %> 24 |
25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/react/New.tsx.tt: -------------------------------------------------------------------------------- 1 | import { Head, Link } from '@inertiajs/react' 2 | import Form from './Form' 3 | import { <%= inertia_model_type %> } from './types' 4 | 5 | interface NewProps { 6 | <%= singular_table_name %>: <%= inertia_model_type %> 7 | } 8 | 9 | export default function New({ <%= singular_table_name %> }: NewProps) { 10 | return ( 11 | <> 12 | 13 | 14 |

New <%= human_name.downcase %>

15 | 16 | ={<%= singular_table_name %>} 18 | onSubmit={(form) => { 19 | form.transform((data) => ({ <%= singular_table_name %>: data })) 20 | form.post('<%= js_resources_path %>') 21 | }} 22 | submitText="Create <%= human_name %>" 23 | /> 24 | 25 |
26 | 27 |
28 | Back to <%= human_name.pluralize.downcase %> 29 |
30 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/react/One.jsx.tt: -------------------------------------------------------------------------------- 1 | export default function <%= inertia_component_name %>({ <%= singular_table_name %> }) { 2 | return ( 3 |
4 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 5 |

6 | <%= attribute.human_name %>: 7 | <% if attribute.attachment? -%> 8 | {<%= singular_table_name %>.<%= attribute.column_name %> && ( 9 | .<%= attribute.column_name %>.url}>{<%= singular_table_name %>.<%= attribute.column_name %>.filename} 10 | )} 11 |

12 | <% elsif attribute.attachments? -%> 13 |

14 | {<%= singular_table_name %>.<%= attribute.column_name %>.map((file, i) => ( 15 |
16 | {file.filename} 17 |
18 | ))} 19 | <% else -%> 20 | {<%= singular_table_name %>.<%= attribute.column_name %>?.toString()} 21 |

22 | <% end -%> 23 | <% end -%> 24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/react/One.tsx.tt: -------------------------------------------------------------------------------- 1 | import { <%= inertia_model_type %> } from './types' 2 | 3 | interface <%= inertia_component_name %>Props { 4 | <%= singular_table_name %>: <%= inertia_model_type %> 5 | } 6 | 7 | export default function <%= inertia_component_name %>({ <%= singular_table_name %> }: <%= inertia_component_name %>Props) { 8 | return ( 9 |
10 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 11 |

12 | <%= attribute.human_name %>: 13 | <% if attribute.attachment? -%> 14 | {<%= singular_table_name %>.<%= attribute.column_name %> && ( 15 | .<%= attribute.column_name %>.url}>{<%= singular_table_name %>.<%= attribute.column_name %>.filename} 16 | )} 17 |

18 | <% elsif attribute.attachments? -%> 19 |

20 | {<%= singular_table_name %>.<%= attribute.column_name %>.map((file, i) => ( 21 |
22 | {file.filename} 23 |
24 | ))} 25 | <% else -%> 26 | {<%= singular_table_name %>.<%= attribute.column_name %>?.toString()} 27 |

28 | <% end -%> 29 | <% end -%> 30 |
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/react/Show.jsx.tt: -------------------------------------------------------------------------------- 1 | import { Head, Link } from '@inertiajs/react' 2 | import <%= inertia_component_name %> from './<%= inertia_component_name %>' 3 | 4 | export default function Show({ <%= singular_table_name %>, flash }) { 5 | return ( 6 | <> 7 | #${<%= singular_table_name %>.id}`} /> 8 | 9 | {flash.notice &&

{flash.notice}

} 10 | 11 |

<%= human_name %> #{<%= singular_table_name %>.id}

12 | 13 | <<%= inertia_component_name %> <%= singular_table_name %>={<%= singular_table_name %>} /> 14 | 15 |
16 | `}>Edit this <%= human_name.downcase %> 17 | {' | '} 18 | Back to <%= human_name.pluralize.downcase %> 19 | 20 |
21 | 22 | `} 24 | as="button" 25 | method="delete" 26 | > 27 | Destroy this <%= human_name.downcase %> 28 | 29 |
30 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/react/Show.tsx.tt: -------------------------------------------------------------------------------- 1 | import { Head, Link } from '@inertiajs/react' 2 | import <%= inertia_component_name %> from './<%= inertia_component_name %>' 3 | import { <%= inertia_model_type %> } from './types' 4 | 5 | interface ShowProps { 6 | <%= singular_table_name %>: <%= inertia_model_type %> 7 | flash: { notice?: string } 8 | } 9 | 10 | export default function Show({ <%= singular_table_name %>, flash }: ShowProps) { 11 | return ( 12 | <> 13 | #${<%= singular_table_name %>.id}`} /> 14 | 15 | {flash.notice &&

{flash.notice}

} 16 | 17 |

<%= human_name %> #{<%= singular_table_name %>.id}

18 | 19 | <<%= inertia_component_name %> <%= singular_table_name %>={<%= singular_table_name %>} /> 20 | 21 |
22 | `}>Edit this <%= human_name.downcase %> 23 | {' | '} 24 | Back to <%= human_name.pluralize.downcase %> 25 | 26 |
27 | 28 | `} 30 | as="button" 31 | method="delete" 32 | > 33 | Destroy this <%= human_name.downcase %> 34 | 35 |
36 | 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/react/types.ts.tt: -------------------------------------------------------------------------------- 1 | export interface <%= inertia_model_type %> { 2 | id: number 3 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 4 | <%= attribute.column_name %>: <%= ts_type(attribute) %> 5 | <% end -%> 6 | } 7 | 8 | export type <%= inertia_model_form_type %> = Omit<<%= inertia_model_type %>, <%= omit_input_attributes.map { |a| "'#{a}'" }.join(' | ') %>><% if custom_form_attributes.any? -%> & { 9 | <% custom_form_attributes.map do |attribute| -%> 10 | <% if attribute.password_digest? -%> 11 | password: string 12 | password_confirmation: string 13 | <% elsif attribute.attachment? -%> 14 | <%= attribute.column_name %>?: File 15 | <% elsif attribute.attachments? -%> 16 | <%= attribute.column_name %>?: File[] 17 | <% end -%> 18 | <% end -%> 19 | }<% end %> 20 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte/Edit.svelte.tt: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | Editing <%= human_name.downcase %> 21 | 22 | 23 |

Editing <%= human_name.downcase %>

24 | 25 | } 27 | submitText="Update <%= human_name %>" 28 | onSubmit={handleSubmit} 29 | /> 30 | 31 |
32 | 33 |
34 | `}>Show this <%= human_name.downcase %> | 35 | Back to <%= human_name.pluralize.downcase %> 36 |
37 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte/Edit.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | Editing <%= human_name.downcase %> 22 | 23 | 24 |

Editing <%= human_name.downcase %>

25 | 26 | } 28 | submitText="Update <%= human_name %>" 29 | onSubmit={handleSubmit} 30 | /> 31 | 32 |
33 | 34 |
35 | `}>Show this <%= human_name.downcase %> | 36 | Back to <%= human_name.pluralize.downcase %> 37 |
38 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte/Index.svelte.tt: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | <%= human_name.pluralize %> 10 | 11 | 12 | {#if flash.notice} 13 |

{flash.notice}

14 | {/if} 15 | 16 |

<%= human_name.pluralize %>

17 | 18 |
19 | {#each <%= plural_table_name %> as <%= singular_table_name %> (<%= singular_table_name %>.id)} 20 |
21 | <<%= inertia_component_name %> {<%= singular_table_name %>} /> 22 |

23 | `}>Show this <%= human_name.downcase %> 24 |

25 |
26 | {/each} 27 |
28 | 29 | New <%= human_name.downcase %> 30 | 31 | 36 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte/Index.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | <%= human_name.pluralize %> 14 | 15 | 16 | {#if flash.notice} 17 |

{flash.notice}

18 | {/if} 19 | 20 |

<%= human_name.pluralize %>

21 | 22 |
23 | {#each <%= plural_table_name %> as <%= singular_table_name %> (<%= singular_table_name %>.id)} 24 |
25 | <<%= inertia_component_name %> {<%= singular_table_name %>} /> 26 |

27 | `}>Show this <%= human_name.downcase %> 28 |

29 |
30 | {/each} 31 |
32 | 33 | New <%= human_name.downcase %> 34 | 35 | 40 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte/New.svelte.tt: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | New <%= human_name.downcase %> 15 | 16 | 17 |

New <%= human_name.downcase %>

18 | 19 | } 21 | submitText="Create <%= human_name %>" 22 | onSubmit={handleSubmit} 23 | /> 24 | 25 |
26 | 27 |
28 | Back to <%= human_name.pluralize.downcase %> 29 |
30 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte/New.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | New <%= human_name.downcase %> 16 | 17 | 18 |

New <%= human_name.downcase %>

19 | 20 | } 22 | submitText="Create <%= human_name %>" 23 | onSubmit={handleSubmit} 24 | /> 25 | 26 |
27 | 28 |
29 | Back to <%= human_name.pluralize.downcase %> 30 |
31 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte/One.svelte.tt: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 7 |

8 | <%= attribute.human_name %>: 9 | <% if attribute.attachment? -%> 10 | {#if <%= singular_table_name %>.<%= attribute.column_name %>} 11 | .<%= attribute.column_name %>.url}> 12 | {<%= singular_table_name %>.<%= attribute.column_name %>.filename} 13 | 14 | {/if} 15 |

16 | <% elsif attribute.attachments? -%> 17 |

18 | {#each <%= singular_table_name %>.<%= attribute.column_name %> as { url, filename }} 19 |
20 | {filename} 21 |
22 | {/each} 23 | <% else -%> 24 | {<%= singular_table_name %>.<%= attribute.column_name %>} 25 |

26 | <% end -%> 27 | <% end -%> 28 |
29 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte/One.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 9 |

10 | <%= attribute.human_name %>: 11 | <% if attribute.attachment? -%> 12 | {#if <%= singular_table_name %>.<%= attribute.column_name %>} 13 | .<%= attribute.column_name %>.url}> 14 | {<%= singular_table_name %>.<%= attribute.column_name %>.filename} 15 | 16 | {/if} 17 |

18 | <% elsif attribute.attachments? -%> 19 |

20 | {#each <%= singular_table_name %>.<%= attribute.column_name %> as { url, filename }} 21 |
22 | {filename} 23 |
24 | {/each} 25 | <% else -%> 26 | {<%= singular_table_name %>.<%= attribute.column_name %>} 27 |

28 | <% end -%> 29 | <% end -%> 30 |
31 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte/Show.svelte.tt: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | <%= human_name %> #{<%= singular_table_name %>.id} 10 | 11 | 12 | {#if flash.notice} 13 |

{flash.notice}

14 | {/if} 15 | 16 |

<%= human_name %> #{<%= singular_table_name %>.id}

17 | 18 | <<%= inertia_component_name %> {<%= singular_table_name %>} /> 19 | 20 |
21 | `}>Edit this <%= human_name.downcase %> | 22 | Back to <%= human_name.pluralize.downcase %> 23 | 24 |
25 | 26 | `} method="delete"> 27 | Destroy this <%= human_name.downcase %> 28 | 29 |
30 | 31 | 36 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte/Show.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | <%= human_name %> #{<%= singular_table_name %>.id} 14 | 15 | 16 | {#if flash.notice} 17 |

{flash.notice}

18 | {/if} 19 | 20 |

<%= human_name %> #{<%= singular_table_name %>.id}

21 | 22 | <<%= inertia_component_name %> {<%= singular_table_name %>} /> 23 | 24 |
25 | `}>Edit this <%= human_name.downcase %> | 26 | Back to <%= human_name.pluralize.downcase %> 27 | 28 |
29 | 30 | `} method="delete"> 31 | Destroy this <%= human_name.downcase %> 32 | 33 |
34 | 35 | 40 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte/types.ts.tt: -------------------------------------------------------------------------------- 1 | export interface <%= inertia_model_type %> { 2 | id: number 3 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 4 | <%= attribute.column_name %>: <%= ts_type(attribute) %> 5 | <% end -%> 6 | } 7 | 8 | export type <%= inertia_model_form_type %> = Omit<<%= inertia_model_type %>, <%= omit_input_attributes.map { |a| "'#{a}'" }.join(' | ') %>><% if custom_form_attributes.any? -%> & { 9 | <% custom_form_attributes.map do |attribute| -%> 10 | <% if attribute.password_digest? -%> 11 | password: string 12 | password_confirmation: string 13 | <% elsif attribute.attachment? -%> 14 | <%= attribute.column_name %>?: File 15 | <% elsif attribute.attachments? -%> 16 | <%= attribute.column_name %>?: File[] 17 | <% end -%> 18 | <% end -%> 19 | }<% end %> 20 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte4/Edit.svelte.tt: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | Editing <%= human_name.downcase %> 22 | 23 | 24 |

Editing <%= human_name.downcase %>

25 | 26 | } 28 | submitText="Update <%= human_name %>" 29 | on:submit={handleSubmit} 30 | /> 31 | 32 |
33 | 34 |
35 | `}>Show this <%= human_name.downcase %> | 36 | Back to <%= human_name.pluralize.downcase %> 37 |
38 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte4/Edit.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | Editing <%= human_name.downcase %> 23 | 24 | 25 |

Editing <%= human_name.downcase %>

26 | 27 | } 29 | submitText="Update <%= human_name %>" 30 | on:submit={handleSubmit} 31 | /> 32 | 33 |
34 | 35 |
36 | `}>Show this <%= human_name.downcase %> | 37 | Back to <%= human_name.pluralize.downcase %> 38 |
39 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte4/Index.svelte.tt: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | <%= human_name.pluralize %> 11 | 12 | 13 | {#if flash.notice} 14 |

{flash.notice}

15 | {/if} 16 | 17 |

<%= human_name.pluralize %>

18 | 19 |
20 | {#each <%= plural_table_name %> as <%= singular_table_name %> (<%= singular_table_name %>.id)} 21 |
22 | <<%= inertia_component_name %> {<%= singular_table_name %>} /> 23 |

24 | `}>Show this <%= human_name.downcase %> 25 |

26 |
27 | {/each} 28 |
29 | 30 | New <%= human_name.downcase %> 31 | 32 | 37 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte4/Index.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | <%= human_name.pluralize %> 12 | 13 | 14 | {#if flash.notice} 15 |

{flash.notice}

16 | {/if} 17 | 18 |

<%= human_name.pluralize %>

19 | 20 |
21 | {#each <%= plural_table_name %> as <%= singular_table_name %> (<%= singular_table_name %>.id)} 22 |
23 | <<%= inertia_component_name %> {<%= singular_table_name %>} /> 24 |

25 | `}>Show this <%= human_name.downcase %> 26 |

27 |
28 | {/each} 29 |
30 | 31 | New <%= human_name.downcase %> 32 | 33 | 38 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte4/New.svelte.tt: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | New <%= human_name.downcase %> 16 | 17 | 18 |

New <%= human_name.downcase %>

19 | 20 | } 22 | submitText="Create <%= human_name %>" 23 | on:submit={handleSubmit} 24 | /> 25 | 26 |
27 | 28 |
29 | Back to <%= human_name.pluralize.downcase %> 30 |
31 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte4/New.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | New <%= human_name.downcase %> 17 | 18 | 19 |

New <%= human_name.downcase %>

20 | 21 | } 23 | submitText="Create <%= human_name %>" 24 | on:submit={handleSubmit} 25 | /> 26 | 27 |
28 | 29 |
30 | Back to <%= human_name.pluralize.downcase %> 31 |
32 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte4/One.svelte.tt: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 7 |

8 | <%= attribute.human_name %>: 9 | <% if attribute.attachment? -%> 10 | {#if <%= singular_table_name %>.<%= attribute.column_name %>} 11 | .<%= attribute.column_name %>.url}> 12 | {<%= singular_table_name %>.<%= attribute.column_name %>.filename} 13 | 14 | {/if} 15 |

16 | <% elsif attribute.attachments? -%> 17 |

18 | {#each <%= singular_table_name %>.<%= attribute.column_name %> as { url, filename }} 19 |
20 | {filename} 21 |
22 | {/each} 23 | <% else -%> 24 | {<%= singular_table_name %>.<%= attribute.column_name %>} 25 |

26 | <% end -%> 27 | <% end -%> 28 |
29 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte4/One.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 9 |

10 | <%= attribute.human_name %>: 11 | <% if attribute.attachment? -%> 12 | {#if <%= singular_table_name %>.<%= attribute.column_name %>} 13 | .<%= attribute.column_name %>.url}> 14 | {<%= singular_table_name %>.<%= attribute.column_name %>.filename} 15 | 16 | {/if} 17 |

18 | <% elsif attribute.attachments? -%> 19 |

20 | {#each <%= singular_table_name %>.<%= attribute.column_name %> as { url, filename }} 21 |
22 | {filename} 23 |
24 | {/each} 25 | <% else -%> 26 | {<%= singular_table_name %>.<%= attribute.column_name %>} 27 |

28 | <% end -%> 29 | <% end -%> 30 |
31 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte4/Show.svelte.tt: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | <%= human_name %> #{<%= singular_table_name %>.id} 11 | 12 | 13 | {#if flash.notice} 14 |

{flash.notice}

15 | {/if} 16 | 17 |

<%= human_name %> #{<%= singular_table_name %>.id}

18 | 19 | <<%= inertia_component_name %> {<%= singular_table_name %>} /> 20 | 21 |
22 | `}>Edit this <%= human_name.downcase %> | 23 | Back to <%= human_name.pluralize.downcase %> 24 | 25 |
26 | 27 | 33 |
34 | 35 | 40 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte4/Show.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | <%= human_name %> #{<%= singular_table_name %>.id} 12 | 13 | 14 | {#if flash.notice} 15 |

{flash.notice}

16 | {/if} 17 | 18 |

<%= human_name %> #{<%= singular_table_name %>.id}

19 | 20 | <<%= inertia_component_name %> {<%= singular_table_name %>} /> 21 | 22 |
23 | `}>Edit this <%= human_name.downcase %> | 24 | Back to <%= human_name.pluralize.downcase %> 25 | 26 |
27 | 28 | 34 |
35 | 36 | 41 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/svelte4/types.ts.tt: -------------------------------------------------------------------------------- 1 | export interface <%= inertia_model_type %> { 2 | id: number 3 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 4 | <%= attribute.column_name %>: <%= ts_type(attribute) %> 5 | <% end -%> 6 | } 7 | 8 | export type <%= inertia_model_form_type %> = Omit<<%= inertia_model_type %>, <%= omit_input_attributes.map { |a| "'#{a}'" }.join(' | ') %>><% if custom_form_attributes.any? -%> & { 9 | <% custom_form_attributes.map do |attribute| -%> 10 | <% if attribute.password_digest? -%> 11 | password: string 12 | password_confirmation: string 13 | <% elsif attribute.attachment? -%> 14 | <%= attribute.column_name %>?: File 15 | <% elsif attribute.attachments? -%> 16 | <%= attribute.column_name %>?: File[] 17 | <% end -%> 18 | <% end -%> 19 | }<% end %> 20 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/vue/Edit.ts.vue.tt: -------------------------------------------------------------------------------- 1 | 19 | 20 | 38 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/vue/Edit.vue.tt: -------------------------------------------------------------------------------- 1 | 19 | 20 | 37 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/vue/Index.ts.vue.tt: -------------------------------------------------------------------------------- 1 | 19 | 20 | 30 | 31 | 36 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/vue/Index.vue.tt: -------------------------------------------------------------------------------- 1 | 19 | 20 | 26 | 27 | 32 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/vue/New.ts.vue.tt: -------------------------------------------------------------------------------- 1 | 18 | 19 | 31 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/vue/New.vue.tt: -------------------------------------------------------------------------------- 1 | 18 | 19 | 30 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/vue/One.ts.vue.tt: -------------------------------------------------------------------------------- 1 | 23 | 24 | 29 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/vue/One.vue.tt: -------------------------------------------------------------------------------- 1 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/vue/Show.ts.vue.tt: -------------------------------------------------------------------------------- 1 | 25 | 26 | 36 | 37 | 42 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/vue/Show.vue.tt: -------------------------------------------------------------------------------- 1 | 25 | 26 | 32 | 33 | 38 | -------------------------------------------------------------------------------- /lib/generators/inertia_templates/scaffold/templates/vue/types.ts.tt: -------------------------------------------------------------------------------- 1 | export interface <%= inertia_model_type %> { 2 | id: number 3 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 4 | <%= attribute.column_name %>: <%= ts_type(attribute) %> 5 | <% end -%> 6 | } 7 | 8 | export type <%= inertia_model_form_type %> = Omit<<%= inertia_model_type %>, <%= omit_input_attributes.map { |a| "'#{a}'" }.join(' | ') %>><% if custom_form_attributes.any? -%> & { 9 | <% custom_form_attributes.map do |attribute| -%> 10 | <% if attribute.password_digest? -%> 11 | password: string 12 | password_confirmation: string 13 | <% elsif attribute.attachment? -%> 14 | <%= attribute.column_name %>?: File 15 | <% elsif attribute.attachments? -%> 16 | <%= attribute.column_name %>?: File[] 17 | <% end -%> 18 | <% end -%> 19 | }<% end %> 20 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/controller/controller_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'inertia_rails/generators/controller_template_base' 4 | 5 | module InertiaTwTemplates 6 | module Generators 7 | class ControllerGenerator < InertiaRails::Generators::ControllerTemplateBase 8 | hide! 9 | source_root File.expand_path('./templates', __dir__) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/controller/templates/react/view.jsx.tt: -------------------------------------------------------------------------------- 1 | export default function <%= @action.camelize %>() { 2 | return ( 3 | <> 4 |

<%= class_name %>#<%= @action %>

5 |

Find me in <%= @path %>

6 | 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/controller/templates/react/view.tsx.tt: -------------------------------------------------------------------------------- 1 | export default function <%= @action.camelize %>() { 2 | return ( 3 | <> 4 |

<%= class_name %>#<%= @action %>

5 |

Find me in <%= @path %>

6 | 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/controller/templates/svelte/view.svelte.tt: -------------------------------------------------------------------------------- 1 |

<%= class_name %>#<%= @action %>

2 |

Find me in <%= @path %>

3 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/controller/templates/svelte4/view.svelte.tt: -------------------------------------------------------------------------------- 1 |

<%= class_name %>#<%= @action %>

2 |

Find me in <%= @path %>

3 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/controller/templates/vue/view.vue.tt: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/scaffold_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'inertia_rails/generators/scaffold_template_base' 4 | 5 | module InertiaTwTemplates 6 | module Generators 7 | class ScaffoldGenerator < InertiaRails::Generators::ScaffoldTemplateBase 8 | hide! 9 | source_root File.expand_path('./templates', __dir__) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/react/Edit.jsx.tt: -------------------------------------------------------------------------------- 1 | import { Head, Link } from '@inertiajs/react' 2 | import Form from './Form' 3 | 4 | export default function Edit({ <%= singular_table_name %> }) { 5 | return ( 6 | <> 7 | 8 | 9 |
10 |

Editing <%= human_name.downcase %>

11 | 12 | ={<%= singular_table_name %>} 14 | onSubmit={(form) => { 15 | form.transform((data) => ({ <%= singular_table_name %>: data })) 16 | <% if attributes.any?(&:attachments?) -%> 17 | form.post(`<%= js_resource_path %>`, { 18 | headers: { 'X-HTTP-METHOD-OVERRIDE': 'put' }, 19 | }) 20 | <% else -%> 21 | form.patch(`<%= js_resource_path %>`) 22 | <% end -%> 23 | }} 24 | submitText="Update <%= human_name %>" 25 | /> 26 | 27 | `} 29 | className="ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" 30 | > 31 | Show this <%= human_name.downcase %> 32 | 33 | 37 | Back to <%= human_name.pluralize.downcase %> 38 | 39 |
40 | 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/react/Edit.tsx.tt: -------------------------------------------------------------------------------- 1 | import { Head, Link } from '@inertiajs/react' 2 | import Form from './Form' 3 | import { <%= inertia_model_type %> } from './types' 4 | 5 | interface EditProps { 6 | <%= singular_table_name %>: <%= inertia_model_type %> 7 | } 8 | 9 | export default function Edit({ <%= singular_table_name %> }: EditProps) { 10 | return ( 11 | <> 12 | 13 | 14 |
15 |

Editing <%= human_name.downcase %>

16 | 17 | ={<%= singular_table_name %>} 19 | onSubmit={(form) => { 20 | form.transform((data) => ({ <%= singular_table_name %>: data })) 21 | <% if attributes.any?(&:attachments?) -%> 22 | form.post(`<%= js_resource_path %>`, { 23 | headers: { 'X-HTTP-METHOD-OVERRIDE': 'put' }, 24 | }) 25 | <% else -%> 26 | form.patch(`<%= js_resource_path %>`) 27 | <% end -%> 28 | }} 29 | submitText="Update <%= human_name %>" 30 | /> 31 | 32 | `} 34 | className="ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" 35 | > 36 | Show this <%= human_name.downcase %> 37 | 38 | 42 | Back to <%= human_name.pluralize.downcase %> 43 | 44 |
45 | 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/react/Index.jsx.tt: -------------------------------------------------------------------------------- 1 | import { Head, Link } from '@inertiajs/react' 2 | import { Fragment } from 'react' 3 | import <%= inertia_component_name %> from './<%= inertia_component_name %>' 4 | 5 | export default function Index({ <%= plural_table_name %>, flash }) { 6 | return ( 7 | <> 8 | 9 |
10 | {flash.notice && ( 11 |

12 | {flash.notice} 13 |

14 | )} 15 |
16 |

<%= human_name.pluralize %>

17 | 21 | New <%= human_name.downcase %> 22 | 23 |
24 | 25 |
26 | {<%= plural_table_name %>.map((<%= singular_table_name %>) => ( 27 | .id}> 28 | <<%= inertia_component_name %> <%= singular_table_name %>={<%= singular_table_name %>} /> 29 |

30 | `} 32 | className="ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" 33 | > 34 | Show this <%= human_name.downcase %> 35 | 36 |

37 |
38 | ))} 39 |
40 |
41 | 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/react/New.jsx.tt: -------------------------------------------------------------------------------- 1 | import { Head, Link } from '@inertiajs/react' 2 | import Form from './Form' 3 | 4 | export default function New({ <%= singular_table_name %> }) { 5 | return ( 6 | <> 7 | 8 | 9 |
10 |

New <%= human_name.downcase %>

11 | 12 | ={<%= singular_table_name %>} 14 | onSubmit={(form) => { 15 | form.transform((data) => ({ <%= singular_table_name %>: data })) 16 | form.post('<%= js_resources_path %>') 17 | }} 18 | submitText="Create <%= human_name %>" 19 | /> 20 | 21 | 25 | Back to <%= human_name.pluralize.downcase %> 26 | 27 |
28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/react/New.tsx.tt: -------------------------------------------------------------------------------- 1 | import { Head, Link } from '@inertiajs/react' 2 | import Form from './Form' 3 | import { <%= inertia_model_type %> } from './types' 4 | 5 | interface NewProps { 6 | <%= singular_table_name %>: <%= inertia_model_type %> 7 | } 8 | 9 | export default function New({ <%= singular_table_name %> }: NewProps) { 10 | return ( 11 | <> 12 | 13 | 14 |
15 |

New <%= human_name.downcase %>

16 | 17 | ={<%= singular_table_name %>} 19 | onSubmit={(form) => { 20 | form.transform((data) => ({ <%= singular_table_name %>: data })) 21 | form.post('<%= js_resources_path %>') 22 | }} 23 | submitText="Create <%= human_name %>" 24 | /> 25 | 26 | 30 | Back to <%= human_name.pluralize.downcase %> 31 | 32 |
33 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/react/One.jsx.tt: -------------------------------------------------------------------------------- 1 | export default function <%= inertia_component_name %>({ <%= singular_table_name %> }) { 2 | return ( 3 |
4 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 5 |

6 | <%= attribute.human_name %>: 7 | <% if attribute.attachment? -%> 8 | {<%= singular_table_name %>.<%= attribute.column_name %> && ( 9 | .<%= attribute.column_name %>.url}>{<%= singular_table_name %>.<%= attribute.column_name %>.filename} 10 | )} 11 |

12 | <% elsif attribute.attachments? -%> 13 |

14 | {<%= singular_table_name %>.<%= attribute.column_name %>.map((file, i) => ( 15 |
16 | {file.filename} 17 |
18 | ))} 19 | <% else -%> 20 | {<%= singular_table_name %>.<%= attribute.column_name %>?.toString()} 21 |

22 | <% end -%> 23 | <% end -%> 24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/react/One.tsx.tt: -------------------------------------------------------------------------------- 1 | import { <%= inertia_model_type %> } from './types' 2 | 3 | interface <%= inertia_component_name %>Props { 4 | <%= singular_table_name %>: <%= inertia_model_type %> 5 | } 6 | 7 | export default function <%= inertia_component_name %>({ <%= singular_table_name %> }: <%= inertia_component_name %>Props) { 8 | return ( 9 |
10 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 11 |

12 | <%= attribute.human_name %>: 13 | <% if attribute.attachment? -%> 14 | {<%= singular_table_name %>.<%= attribute.column_name %> && ( 15 | .<%= attribute.column_name %>.url}>{<%= singular_table_name %>.<%= attribute.column_name %>.filename} 16 | )} 17 |

18 | <% elsif attribute.attachments? -%> 19 |

20 | {<%= singular_table_name %>.<%= attribute.column_name %>.map((file, i) => ( 21 |
22 | {file.filename} 23 |
24 | ))} 25 | <% else -%> 26 | {<%= singular_table_name %>.<%= attribute.column_name %>?.toString()} 27 |

28 | <% end -%> 29 | <% end -%> 30 |
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/react/types.ts.tt: -------------------------------------------------------------------------------- 1 | export interface <%= inertia_model_type %> { 2 | id: number 3 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 4 | <%= attribute.column_name %>: <%= ts_type(attribute) %> 5 | <% end -%> 6 | } 7 | 8 | export type <%= inertia_model_form_type %> = Omit<<%= inertia_model_type %>, <%= omit_input_attributes.map { |a| "'#{a}'" }.join(' | ') %>><% if custom_form_attributes.any? -%> & { 9 | <% custom_form_attributes.map do |attribute| -%> 10 | <% if attribute.password_digest? -%> 11 | password: string 12 | password_confirmation: string 13 | <% elsif attribute.attachment? -%> 14 | <%= attribute.column_name %>?: File 15 | <% elsif attribute.attachments? -%> 16 | <%= attribute.column_name %>?: File[] 17 | <% end -%> 18 | <% end -%> 19 | }<% end %> 20 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte/Edit.svelte.tt: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | Editing <%= human_name.downcase %> 21 | 22 | 23 |
24 |

Editing <%= human_name.downcase %>

25 | 26 | } 28 | submitText="Update <%= human_name %>" 29 | onSubmit={handleSubmit} 30 | /> 31 | 32 | `} 34 | class="mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" 35 | > 36 | Show this <%= human_name.downcase %> 37 | 38 | 42 | Back to <%= human_name.pluralize.downcase %> 43 | 44 |
45 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte/Edit.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | Editing <%= human_name.downcase %> 22 | 23 | 24 |
25 |

Editing <%= human_name.downcase %>

26 | 27 | } 29 | submitText="Update <%= human_name %>" 30 | onSubmit={handleSubmit} 31 | /> 32 | 33 | `} 35 | class="mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" 36 | > 37 | Show this <%= human_name.downcase %> 38 | 39 | 43 | Back to <%= human_name.pluralize.downcase %> 44 | 45 |
46 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte/Index.svelte.tt: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | <%= human_name.pluralize %> 10 | 11 | 12 |
13 | {#if flash.notice} 14 |

15 | {flash.notice} 16 |

17 | {/if} 18 | 19 |
20 |

<%= human_name.pluralize %>

21 | 25 | New <%= human_name.downcase %> 26 | 27 |
28 | 29 |
30 | {#each <%= plural_table_name %> as <%= singular_table_name %> (<%= singular_table_name %>.id)} 31 | <<%= inertia_component_name %> {<%= singular_table_name %>} /> 32 |

33 | `} 35 | class="ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" 36 | > 37 | Show this <%= human_name.downcase %> 38 | 39 |

40 | {/each} 41 |
42 |
43 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte/Index.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | <%= human_name.pluralize %> 14 | 15 | 16 |
17 | {#if flash.notice} 18 |

19 | {flash.notice} 20 |

21 | {/if} 22 | 23 |
24 |

<%= human_name.pluralize %>

25 | 29 | New <%= human_name.downcase %> 30 | 31 |
32 | 33 |
34 | {#each <%= plural_table_name %> as <%= singular_table_name %> (<%= singular_table_name %>.id)} 35 | <<%= inertia_component_name %> {<%= singular_table_name %>} /> 36 |

37 | `} 39 | class="ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" 40 | > 41 | Show this <%= human_name.downcase %> 42 | 43 |

44 | {/each} 45 |
46 |
47 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte/New.svelte.tt: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | New <%= human_name.downcase %> 15 | 16 | 17 |
18 |

New <%= human_name.downcase %>

19 | 20 | } 22 | submitText="Create <%= human_name %>" 23 | onSubmit={handleSubmit} 24 | /> 25 | 26 | 30 | Back to <%= human_name.pluralize.downcase %> 31 | 32 |
33 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte/New.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | New <%= human_name.downcase %> 16 | 17 | 18 |
19 |

New <%= human_name.downcase %>

20 | 21 | } 23 | submitText="Create <%= human_name %>" 24 | onSubmit={handleSubmit} 25 | /> 26 | 27 | 31 | Back to <%= human_name.pluralize.downcase %> 32 | 33 |
34 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte/One.svelte.tt: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 7 |

8 | <%= attribute.human_name %>: 9 | <% if attribute.attachment? -%> 10 | {#if <%= singular_table_name %>.<%= attribute.column_name %>} 11 | .<%= attribute.column_name %>.url}> 12 | {<%= singular_table_name %>.<%= attribute.column_name %>.filename} 13 | 14 | {/if} 15 |

16 | <% elsif attribute.attachments? -%> 17 |

18 | {#each <%= singular_table_name %>.<%= attribute.column_name %> as { url, filename }} 19 |
20 | {filename} 21 |
22 | {/each} 23 | <% else -%> 24 | {<%= singular_table_name %>.<%= attribute.column_name %>} 25 |

26 | <% end -%> 27 | <% end -%> 28 |
29 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte/One.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 9 |

10 | <%= attribute.human_name %>: 11 | <% if attribute.attachment? -%> 12 | {#if <%= singular_table_name %>.<%= attribute.column_name %>} 13 | .<%= attribute.column_name %>.url}> 14 | {<%= singular_table_name %>.<%= attribute.column_name %>.filename} 15 | 16 | {/if} 17 |

18 | <% elsif attribute.attachments? -%> 19 |

20 | {#each <%= singular_table_name %>.<%= attribute.column_name %> as { url, filename }} 21 |
22 | {filename} 23 |
24 | {/each} 25 | <% else -%> 26 | {<%= singular_table_name %>.<%= attribute.column_name %>} 27 |

28 | <% end -%> 29 | <% end -%> 30 |
31 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte/Show.svelte.tt: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | <%= human_name %> #{<%= singular_table_name %>.id} 10 | 11 | 12 |
13 |
14 | {#if flash.notice} 15 |

16 | {flash.notice} 17 |

18 | {/if} 19 | 20 |

<%= human_name %> #{<%= singular_table_name %>.id}

21 | 22 | <<%= inertia_component_name %> {<%= singular_table_name %>} /> 23 | 24 | `} 26 | class="ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" 27 | > 28 | Edit this <%= human_name.downcase %> 29 | 30 | 34 | Back to <%= human_name.pluralize.downcase %> 35 | 36 |
37 | `} 39 | method="delete" 40 | class="mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" 41 | > 42 | Destroy this <%= human_name.downcase %> 43 | 44 |
45 |
46 |
47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte/types.ts.tt: -------------------------------------------------------------------------------- 1 | export interface <%= inertia_model_type %> { 2 | id: number 3 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 4 | <%= attribute.column_name %>: <%= ts_type(attribute) %> 5 | <% end -%> 6 | } 7 | 8 | export type <%= inertia_model_form_type %> = Omit<<%= inertia_model_type %>, <%= omit_input_attributes.map { |a| "'#{a}'" }.join(' | ') %>><% if custom_form_attributes.any? -%> & { 9 | <% custom_form_attributes.map do |attribute| -%> 10 | <% if attribute.password_digest? -%> 11 | password: string 12 | password_confirmation: string 13 | <% elsif attribute.attachment? -%> 14 | <%= attribute.column_name %>?: File 15 | <% elsif attribute.attachments? -%> 16 | <%= attribute.column_name %>?: File[] 17 | <% end -%> 18 | <% end -%> 19 | }<% end %> 20 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte4/Edit.svelte.tt: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | Editing <%= human_name.downcase %> 22 | 23 | 24 |
25 |

Editing <%= human_name.downcase %>

26 | 27 | } 29 | submitText="Update <%= human_name %>" 30 | on:submit={handleSubmit} 31 | /> 32 | 33 | `} 35 | class="mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" 36 | > 37 | Show this <%= human_name.downcase %> 38 | 39 | 43 | Back to <%= human_name.pluralize.downcase %> 44 | 45 |
46 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte4/Edit.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | Editing <%= human_name.downcase %> 23 | 24 | 25 |
26 |

Editing <%= human_name.downcase %>

27 | 28 | } 30 | submitText="Update <%= human_name %>" 31 | on:submit={handleSubmit} 32 | /> 33 | 34 | `} 36 | class="mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" 37 | > 38 | Show this <%= human_name.downcase %> 39 | 40 | 44 | Back to <%= human_name.pluralize.downcase %> 45 | 46 |
47 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte4/Index.svelte.tt: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | <%= human_name.pluralize %> 11 | 12 | 13 |
14 | {#if flash.notice} 15 |

16 | {flash.notice} 17 |

18 | {/if} 19 | 20 |
21 |

<%= human_name.pluralize %>

22 | 26 | New <%= human_name.downcase %> 27 | 28 |
29 | 30 |
31 | {#each <%= plural_table_name %> as <%= singular_table_name %> (<%= singular_table_name %>.id)} 32 | <<%= inertia_component_name %> {<%= singular_table_name %>} /> 33 |

34 | `} 36 | class="ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" 37 | > 38 | Show this <%= human_name.downcase %> 39 | 40 |

41 | {/each} 42 |
43 |
44 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte4/Index.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | <%= human_name.pluralize %> 12 | 13 | 14 |
15 | {#if flash.notice} 16 |

17 | {flash.notice} 18 |

19 | {/if} 20 | 21 |
22 |

<%= human_name.pluralize %>

23 | 27 | New <%= human_name.downcase %> 28 | 29 |
30 | 31 |
32 | {#each <%= plural_table_name %> as <%= singular_table_name %> (<%= singular_table_name %>.id)} 33 | <<%= inertia_component_name %> {<%= singular_table_name %>} /> 34 |

35 | `} 37 | class="ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" 38 | > 39 | Show this <%= human_name.downcase %> 40 | 41 |

42 | {/each} 43 |
44 |
45 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte4/New.svelte.tt: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | New <%= human_name.downcase %> 16 | 17 | 18 |
19 |

New <%= human_name.downcase %>

20 | 21 | } 23 | submitText="Create <%= human_name %>" 24 | on:submit={handleSubmit} 25 | /> 26 | 27 | 31 | Back to <%= human_name.pluralize.downcase %> 32 | 33 |
34 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte4/New.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | New <%= human_name.downcase %> 17 | 18 | 19 |
20 |

New <%= human_name.downcase %>

21 | 22 | } 24 | submitText="Create <%= human_name %>" 25 | on:submit={handleSubmit} 26 | /> 27 | 28 | 32 | Back to <%= human_name.pluralize.downcase %> 33 | 34 |
35 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte4/One.svelte.tt: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 7 |

8 | <%= attribute.human_name %>: 9 | <% if attribute.attachment? -%> 10 | {#if <%= singular_table_name %>.<%= attribute.column_name %>} 11 | .<%= attribute.column_name %>.url}> 12 | {<%= singular_table_name %>.<%= attribute.column_name %>.filename} 13 | 14 | {/if} 15 |

16 | <% elsif attribute.attachments? -%> 17 |

18 | {#each <%= singular_table_name %>.<%= attribute.column_name %> as { url, filename }} 19 |
20 | {filename} 21 |
22 | {/each} 23 | <% else -%> 24 | {<%= singular_table_name %>.<%= attribute.column_name %>} 25 |

26 | <% end -%> 27 | <% end -%> 28 |
29 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte4/One.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 9 |

10 | <%= attribute.human_name %>: 11 | <% if attribute.attachment? -%> 12 | {#if <%= singular_table_name %>.<%= attribute.column_name %>} 13 | .<%= attribute.column_name %>.url}> 14 | {<%= singular_table_name %>.<%= attribute.column_name %>.filename} 15 | 16 | {/if} 17 |

18 | <% elsif attribute.attachments? -%> 19 |

20 | {#each <%= singular_table_name %>.<%= attribute.column_name %> as { url, filename }} 21 |
22 | {filename} 23 |
24 | {/each} 25 | <% else -%> 26 | {<%= singular_table_name %>.<%= attribute.column_name %>} 27 |

28 | <% end -%> 29 | <% end -%> 30 |
31 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte4/Show.svelte.tt: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | <%= human_name %> #{<%= singular_table_name %>.id} 11 | 12 | 13 |
14 |
15 | {#if flash.notice} 16 |

17 | {flash.notice} 18 |

19 | {/if} 20 | 21 |

<%= human_name %> #{<%= singular_table_name %>.id}

22 | 23 | <<%= inertia_component_name %> {<%= singular_table_name %>} /> 24 | 25 | `} 27 | class="ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" 28 | > 29 | Edit this <%= human_name.downcase %> 30 | 31 | 35 | Back to <%= human_name.pluralize.downcase %> 36 | 37 |
38 | 45 |
46 |
47 |
48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte4/Show.ts.svelte.tt: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | <%= human_name %> #{<%= singular_table_name %>.id} 12 | 13 | 14 |
15 |
16 | {#if flash.notice} 17 |

18 | {flash.notice} 19 |

20 | {/if} 21 | 22 |

<%= human_name %> #{<%= singular_table_name %>.id}

23 | 24 | <<%= inertia_component_name %> {<%= singular_table_name %>} /> 25 | 26 | `} 28 | class="ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" 29 | > 30 | Edit this <%= human_name.downcase %> 31 | 32 | 36 | Back to <%= human_name.pluralize.downcase %> 37 | 38 |
39 | 46 |
47 |
48 |
49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/svelte4/types.ts.tt: -------------------------------------------------------------------------------- 1 | export interface <%= inertia_model_type %> { 2 | id: number 3 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 4 | <%= attribute.column_name %>: <%= ts_type(attribute) %> 5 | <% end -%> 6 | } 7 | 8 | export type <%= inertia_model_form_type %> = Omit<<%= inertia_model_type %>, <%= omit_input_attributes.map { |a| "'#{a}'" }.join(' | ') %>><% if custom_form_attributes.any? -%> & { 9 | <% custom_form_attributes.map do |attribute| -%> 10 | <% if attribute.password_digest? -%> 11 | password: string 12 | password_confirmation: string 13 | <% elsif attribute.attachment? -%> 14 | <%= attribute.column_name %>?: File 15 | <% elsif attribute.attachments? -%> 16 | <%= attribute.column_name %>?: File[] 17 | <% end -%> 18 | <% end -%> 19 | }<% end %> 20 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/vue/Edit.ts.vue.tt: -------------------------------------------------------------------------------- 1 | 27 | 28 | 46 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/vue/Edit.vue.tt: -------------------------------------------------------------------------------- 1 | 27 | 28 | 45 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/vue/Index.ts.vue.tt: -------------------------------------------------------------------------------- 1 | 37 | 38 | 48 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/vue/Index.vue.tt: -------------------------------------------------------------------------------- 1 | 37 | 38 | 44 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/vue/New.ts.vue.tt: -------------------------------------------------------------------------------- 1 | 21 | 22 | 34 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/vue/New.vue.tt: -------------------------------------------------------------------------------- 1 | 21 | 22 | 33 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/vue/One.ts.vue.tt: -------------------------------------------------------------------------------- 1 | 23 | 24 | 29 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/vue/One.vue.tt: -------------------------------------------------------------------------------- 1 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/vue/Show.vue.tt: -------------------------------------------------------------------------------- 1 | 43 | 44 | 50 | -------------------------------------------------------------------------------- /lib/generators/inertia_tw_templates/scaffold/templates/vue/types.ts.tt: -------------------------------------------------------------------------------- 1 | export interface <%= inertia_model_type %> { 2 | id: number 3 | <% attributes.reject(&:password_digest?).each do |attribute| -%> 4 | <%= attribute.column_name %>: <%= ts_type(attribute) %> 5 | <% end -%> 6 | } 7 | 8 | export type <%= inertia_model_form_type %> = Omit<<%= inertia_model_type %>, <%= omit_input_attributes.map { |a| "'#{a}'" }.join(' | ') %>><% if custom_form_attributes.any? -%> & { 9 | <% custom_form_attributes.map do |attribute| -%> 10 | <% if attribute.password_digest? -%> 11 | password: string 12 | password_confirmation: string 13 | <% elsif attribute.attachment? -%> 14 | <%= attribute.column_name %>?: File 15 | <% elsif attribute.attachments? -%> 16 | <%= attribute.column_name %>?: File[] 17 | <% end -%> 18 | <% end -%> 19 | }<% end %> 20 | -------------------------------------------------------------------------------- /lib/inertia_rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'inertia_rails/renderer' 4 | require 'inertia_rails/engine' 5 | 6 | require 'patches/debug_exceptions' 7 | require 'patches/better_errors' 8 | require 'patches/request' 9 | require 'patches/mapper' 10 | 11 | ActionController::Renderers.add :inertia do |component, options| 12 | InertiaRails::Renderer.new( 13 | component, 14 | self, 15 | request, 16 | response, 17 | method(:render), 18 | props: options[:props], 19 | view_data: options[:view_data], 20 | deep_merge: options[:deep_merge], 21 | encrypt_history: options[:encrypt_history], 22 | clear_history: options[:clear_history] 23 | ).render 24 | end 25 | 26 | module InertiaRails 27 | class Error < StandardError; end 28 | 29 | def self.deprecator # :nodoc: 30 | @deprecator ||= ActiveSupport::Deprecation.new 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/inertia_rails/action_filter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # 3 | # Based on AbstractController::Callbacks::ActionFilter 4 | # https://github.com/rails/rails/blob/v7.2.0/actionpack/lib/abstract_controller/callbacks.rb#L39 5 | module InertiaRails 6 | class ActionFilter 7 | def initialize(conditional_key, actions) 8 | @conditional_key = conditional_key 9 | @actions = Array(actions).map(&:to_s).to_set 10 | end 11 | 12 | def match?(controller) 13 | missing_action = @actions.find { |action| !controller.available_action?(action) } 14 | if missing_action 15 | message = <<~MSG 16 | The #{missing_action} action could not be found for the :inertia_share 17 | callback on #{controller.class.name}, but it is listed in the controller's 18 | #{@conditional_key.inspect} option. 19 | MSG 20 | 21 | raise AbstractController::ActionNotFound.new(message, controller, missing_action) 22 | end 23 | 24 | @actions.include?(controller.action_name) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/inertia_rails/always_prop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module InertiaRails 4 | class AlwaysProp < BaseProp 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/inertia_rails/base_prop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module InertiaRails 4 | # Base class for all props. 5 | class BaseProp 6 | def initialize(&block) 7 | @block = block 8 | end 9 | 10 | def call(controller) 11 | controller.instance_exec(&@block) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/inertia_rails/defer_prop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module InertiaRails 4 | class DeferProp < IgnoreOnFirstLoadProp 5 | DEFAULT_GROUP = 'default' 6 | 7 | attr_reader :group 8 | 9 | def initialize(group: nil, merge: nil, deep_merge: nil, &block) 10 | raise ArgumentError, 'Cannot set both `deep_merge` and `merge` to true' if deep_merge && merge 11 | 12 | super(&block) 13 | 14 | @group = group || DEFAULT_GROUP 15 | @merge = merge || deep_merge 16 | @deep_merge = deep_merge 17 | end 18 | 19 | def merge? 20 | @merge 21 | end 22 | 23 | def deep_merge? 24 | @deep_merge 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/inertia_rails/engine.rb: -------------------------------------------------------------------------------- 1 | require_relative "middleware" 2 | require_relative "controller" 3 | 4 | module InertiaRails 5 | class Engine < ::Rails::Engine 6 | initializer "inertia_rails.configure_rails_initialization" do |app| 7 | app.middleware.use ::InertiaRails::Middleware 8 | end 9 | 10 | initializer "inertia_rails.action_controller" do 11 | ActiveSupport.on_load(:action_controller_base) do 12 | include ::InertiaRails::Controller 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/inertia_rails/generators/scaffold_template_base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails/generators/resource_helpers' 4 | require_relative 'controller_template_base' 5 | 6 | module InertiaRails 7 | module Generators 8 | class ScaffoldTemplateBase < ControllerTemplateBase 9 | include Rails::Generators::ResourceHelpers 10 | 11 | remove_argument :actions 12 | 13 | argument :attributes, type: :array, default: [], banner: 'field:type field:type' 14 | 15 | def copy_view_files 16 | available_views.each do |view| 17 | template "#{options.frontend_framework}/#{view}.#{template_extension}", 18 | File.join(base_path, "#{view}.#{extension}") 19 | end 20 | 21 | template "#{options.frontend_framework}/#{partial_name}.#{template_extension}", 22 | File.join(base_path, "#{inertia_component_name}.#{extension}") 23 | 24 | template "#{options.frontend_framework}/types.ts", File.join(base_path, 'types.ts') if typescript? 25 | end 26 | 27 | private 28 | 29 | def template_extension 30 | return extension unless typescript? 31 | return 'tsx' if options.frontend_framework == 'react' 32 | 33 | "ts.#{extension}" 34 | end 35 | 36 | def available_views 37 | %w[Index Edit Show New Form] 38 | end 39 | 40 | def partial_name 41 | 'One' 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/inertia_rails/helper.rb: -------------------------------------------------------------------------------- 1 | require_relative 'inertia_rails' 2 | 3 | module InertiaRails::Helper 4 | def inertia_ssr_head 5 | controller.instance_variable_get("@_inertia_ssr_head") 6 | end 7 | 8 | def inertia_headers 9 | InertiaRails.deprecator.warn( 10 | "`inertia_headers` is deprecated and will be removed in InertiaRails 4.0, use `inertia_ssr_head` instead." 11 | ) 12 | inertia_ssr_head 13 | end 14 | 15 | def inertia_rendering? 16 | controller.instance_variable_get("@_inertia_rendering") 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/inertia_rails/ignore_on_first_load_prop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module InertiaRails 4 | class IgnoreOnFirstLoadProp < BaseProp 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/inertia_rails/inertia_rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'inertia_rails/base_prop' 4 | require 'inertia_rails/ignore_on_first_load_prop' 5 | require 'inertia_rails/always_prop' 6 | require 'inertia_rails/lazy_prop' 7 | require 'inertia_rails/optional_prop' 8 | require 'inertia_rails/defer_prop' 9 | require 'inertia_rails/merge_prop' 10 | require 'inertia_rails/configuration' 11 | 12 | module InertiaRails 13 | class << self 14 | CONFIGURATION = Configuration.default 15 | 16 | def configure 17 | yield(CONFIGURATION) 18 | end 19 | 20 | def configuration 21 | CONFIGURATION 22 | end 23 | 24 | def lazy(value = nil, &block) 25 | LazyProp.new(value, &block) 26 | end 27 | 28 | def optional(&block) 29 | OptionalProp.new(&block) 30 | end 31 | 32 | def always(&block) 33 | AlwaysProp.new(&block) 34 | end 35 | 36 | def merge(&block) 37 | MergeProp.new(&block) 38 | end 39 | 40 | def deep_merge(&block) 41 | MergeProp.new(deep_merge: true, &block) 42 | end 43 | 44 | def defer(group: nil, merge: nil, deep_merge: nil, &block) 45 | DeferProp.new(group: group, merge: merge, deep_merge: deep_merge, &block) 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/inertia_rails/lazy_prop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module InertiaRails 4 | class LazyProp < IgnoreOnFirstLoadProp 5 | def initialize(value = nil, &block) 6 | raise ArgumentError, 'You must provide either a value or a block, not both' if value && block 7 | 8 | InertiaRails.deprecator.warn( 9 | '`lazy` is deprecated and will be removed in InertiaRails 4.0, use `optional` instead.' 10 | ) 11 | 12 | @value = value 13 | @block = block 14 | end 15 | 16 | def call(controller) 17 | value.respond_to?(:call) ? controller.instance_exec(&value) : value 18 | end 19 | 20 | def value 21 | @value.nil? ? @block : @value 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/inertia_rails/merge_prop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module InertiaRails 4 | class MergeProp < BaseProp 5 | def initialize(deep_merge: false, &block) 6 | super(&block) 7 | @deep_merge = deep_merge 8 | end 9 | 10 | def merge? 11 | true 12 | end 13 | 14 | def deep_merge? 15 | @deep_merge 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/inertia_rails/optional_prop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module InertiaRails 4 | class OptionalProp < IgnoreOnFirstLoadProp 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/inertia_rails/version.rb: -------------------------------------------------------------------------------- 1 | module InertiaRails 2 | VERSION = "3.8.0" 3 | end 4 | -------------------------------------------------------------------------------- /lib/patches/better_errors.rb: -------------------------------------------------------------------------------- 1 | # Patch BetterErrors::Middleware to render HTML for Inertia requests 2 | # 3 | # Original source: 4 | # https://github.com/BetterErrors/better_errors/blob/v2.5.1/lib/better_errors/middleware.rb 5 | # 6 | 7 | module InertiaRails 8 | module InertiaBetterErrors 9 | def text?(env) 10 | return false if env["HTTP_X_INERTIA"] 11 | 12 | super 13 | end 14 | end 15 | end 16 | 17 | if defined?(BetterErrors) 18 | BetterErrors::Middleware.include InertiaRails::InertiaBetterErrors 19 | end 20 | -------------------------------------------------------------------------------- /lib/patches/debug_exceptions.rb: -------------------------------------------------------------------------------- 1 | # Patch ActionDispatch::DebugExceptions to render HTML for Inertia requests 2 | # 3 | # Rails has introduced text rendering for XHR requests with Rails 4.1 and 4 | # changed the implementation in 4.2, 5.0 and 5.1 (unchanged since then). 5 | # 6 | # The original source needs to be patched, so that Inertia requests are 7 | # NOT responded with plain text, but with HTML. 8 | 9 | if defined?(ActionDispatch::DebugExceptions) 10 | if ActionPack.version.to_s >= '5.1' 11 | require 'patches/debug_exceptions/patch-5-1' 12 | elsif ActionPack.version.to_s >= '5.0' 13 | require 'patches/debug_exceptions/patch-5-0' 14 | else 15 | # This gem supports Rails 5 or later 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/patches/debug_exceptions/patch-5-0.rb: -------------------------------------------------------------------------------- 1 | # Patch ActionDispatch::DebugExceptions to render HTML for Inertia requests 2 | # 3 | # Original source: 4 | # https://github.com/rails/rails/blob/5-0-stable/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb 5 | # 6 | 7 | module InertiaRails 8 | module InertiaDebugExceptions 9 | def render_for_default_application(request, wrapper) 10 | template = create_template(request, wrapper) 11 | file = "rescues/#{wrapper.rescue_template}" 12 | 13 | if request.xhr? && !request.headers['X-Inertia'] # <<<< this line is changed only 14 | body = template.render(template: file, layout: false, formats: [:text]) 15 | format = "text/plain" 16 | else 17 | body = template.render(template: file, layout: 'rescues/layout') 18 | format = "text/html" 19 | end 20 | render(wrapper.status_code, body, format) 21 | end 22 | end 23 | end 24 | 25 | if defined?(ActionDispatch::DebugExceptions) 26 | ActionDispatch::DebugExceptions.prepend InertiaRails::InertiaDebugExceptions 27 | end 28 | -------------------------------------------------------------------------------- /lib/patches/debug_exceptions/patch-5-1.rb: -------------------------------------------------------------------------------- 1 | # Patch ActionDispatch::DebugExceptions to render HTML for Inertia requests 2 | # 3 | # Original source (unchanged since Rails 5.1): 4 | # https://github.com/rails/rails/blob/5-1-stable/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb 5 | # https://github.com/rails/rails/blob/5-2-stable/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb 6 | # https://github.com/rails/rails/blob/6-0-stable/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb 7 | # 8 | 9 | module InertiaRails 10 | module InertiaDebugExceptions 11 | def render_for_browser_request(request, wrapper) 12 | template = create_template(request, wrapper) 13 | file = "rescues/#{wrapper.rescue_template}" 14 | 15 | if request.xhr? && !request.headers['X-Inertia'] # <<<< this line is changed only 16 | body = template.render(template: file, layout: false, formats: [:text]) 17 | format = "text/plain" 18 | else 19 | body = template.render(template: file, layout: "rescues/layout") 20 | format = "text/html" 21 | end 22 | 23 | render(wrapper.status_code, body, format) 24 | end 25 | end 26 | end 27 | 28 | if defined?(ActionDispatch::DebugExceptions) 29 | ActionDispatch::DebugExceptions.prepend InertiaRails::InertiaDebugExceptions 30 | end 31 | -------------------------------------------------------------------------------- /lib/patches/mapper.rb: -------------------------------------------------------------------------------- 1 | module InertiaRails 2 | module InertiaMapper 3 | def inertia(*args, **options) 4 | path = args.any? ? args.first : options 5 | route, component = extract_route_and_component(path) 6 | get(route, to: StaticController.action(:static), defaults: { component: component }, **options) 7 | end 8 | 9 | private 10 | 11 | def extract_route_and_component(path) 12 | if path.is_a?(Hash) 13 | path.first 14 | elsif resource_scope? 15 | [path, InertiaRails.configuration.component_path_resolver(path: [@scope[:module], @scope[:controller]].compact.join('/'), action: path)] 16 | elsif @scope[:module].blank? 17 | [path, path] 18 | else 19 | [path, InertiaRails.configuration.component_path_resolver(path: @scope[:module], action: path)] 20 | end 21 | end 22 | end 23 | end 24 | 25 | ActionDispatch::Routing::Mapper.include InertiaRails::InertiaMapper 26 | -------------------------------------------------------------------------------- /lib/patches/request.rb: -------------------------------------------------------------------------------- 1 | module InertiaRails 2 | module InertiaRequest 3 | def inertia? 4 | key? 'HTTP_X_INERTIA' 5 | end 6 | 7 | def inertia_partial? 8 | key?('HTTP_X_INERTIA_PARTIAL_COMPONENT') 9 | end 10 | end 11 | end 12 | 13 | ActionDispatch::Request.include InertiaRails::InertiaRequest 14 | -------------------------------------------------------------------------------- /lib/tasks/inertia_rails.rake: -------------------------------------------------------------------------------- 1 | namespace :inertia_rails do 2 | namespace :install do 3 | desc "Installs inertia_rails packages and configurations for a React based app" 4 | task :react => :environment do 5 | system 'rails g inertia_rails:install --front_end react' 6 | end 7 | desc "Installs inertia_rails packages and configurations for a Vue based app" 8 | task vue: :environment do 9 | system 'rails g inertia_rails:install --front_end vue' 10 | end 11 | desc "Installs inertia_rails packages and configurations for a Svelte based app" 12 | task svelte: :environment do 13 | system 'rails g inertia_rails:install --front_end svelte' 14 | end 15 | end 16 | end -------------------------------------------------------------------------------- /spec/dummy/.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.3 2 | -------------------------------------------------------------------------------- /spec/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | def controller_method 3 | 'controller_method value' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inertiajs/inertia-rails/291d0d2f09b8085881b93474879d40d413fa55fc/spec/dummy/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /spec/dummy/app/controllers/inertia_child_share_test_controller.rb: -------------------------------------------------------------------------------- 1 | class InertiaChildShareTestController < InertiaShareTestController 2 | inertia_share name: 'No Longer Brandon' 3 | 4 | def share_with_inherited 5 | render inertia: 'ShareTestComponent' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/inertia_config_test_controller.rb: -------------------------------------------------------------------------------- 1 | class InertiaConfigTestController < ApplicationController 2 | inertia_config( 3 | deep_merge_shared_data: true, 4 | ssr_enabled: true, 5 | ssr_url: "http://localhost:7777", 6 | layout: "test", 7 | version: "1.0", 8 | encrypt_history: false, 9 | ) 10 | 11 | # Test that modules included in the same class can also call it. 12 | inertia_config( 13 | version: "2.0", 14 | ) 15 | 16 | def configuration 17 | render json: inertia_configuration.send(:options) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/inertia_encrypt_history_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class InertiaEncryptHistoryController < ApplicationController 4 | inertia_config( 5 | encrypt_history: -> { action_name != 'default_config' } 6 | ) 7 | 8 | def default_config 9 | render inertia: 'TestComponent' 10 | end 11 | 12 | def encrypt_history 13 | render inertia: 'TestComponent' 14 | end 15 | 16 | def override_config 17 | render inertia: 'TestComponent', encrypt_history: false 18 | end 19 | 20 | def clear_history 21 | render inertia: 'TestComponent', clear_history: true 22 | end 23 | 24 | def clear_history_after_redirect 25 | redirect_to :empty_test, inertia: { clear_history: true } 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/inertia_lambda_shared_props_controller.rb: -------------------------------------------------------------------------------- 1 | class InertiaLambdaSharedPropsController < ApplicationController 2 | inertia_share someProperty: -> { 3 | { 4 | property_a: "some value", 5 | property_b: "this value" 6 | } 7 | } 8 | 9 | def lamda_shared_props 10 | render inertia: 'ShareTestComponent', props: { property_c: "some other value" } 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/inertia_merge_instance_props_controller.rb: -------------------------------------------------------------------------------- 1 | class InertiaMergeInstancePropsController < ApplicationController 2 | use_inertia_instance_props 3 | inertia_share do 4 | { 5 | nested: { 6 | points: 55, 7 | rebounds: 10, 8 | } 9 | } 10 | end 11 | 12 | def merge_instance_props 13 | @nested = { 14 | points: 100, 15 | } 16 | 17 | render inertia: 'InertiaTestComponent', deep_merge: true 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/inertia_merge_shared_controller.rb: -------------------------------------------------------------------------------- 1 | class InertiaMergeSharedController < ApplicationController 2 | inertia_share do 3 | { 4 | nested: { 5 | goals: 100, 6 | assists: 100, 7 | } 8 | } 9 | end 10 | 11 | def merge_shared 12 | render inertia: 'ShareTestComponent', props: { 13 | nested: { 14 | assists: 200, 15 | } 16 | } 17 | end 18 | 19 | def deep_merge_shared 20 | render inertia: 'ShareTestComponent', props: { 21 | nested: { 22 | assists: 300, 23 | } 24 | }, deep_merge: true 25 | end 26 | 27 | def shallow_merge_shared 28 | render inertia: 'ShareTestComponent', props: { 29 | nested: { 30 | assists: 200, 31 | } 32 | }, deep_merge: false 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/inertia_multithreaded_share_controller.rb: -------------------------------------------------------------------------------- 1 | class InertiaMultithreadedShareController < ApplicationController 2 | inertia_share name: 'Michael' 3 | inertia_share has_goat_status: true 4 | 5 | def share_multithreaded 6 | sleep 1 7 | render inertia: 'ShareTestComponent' 8 | end 9 | 10 | def share_multithreaded_error 11 | raise Exception 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/inertia_rails_mimic_controller.rb: -------------------------------------------------------------------------------- 1 | class InertiaRailsMimicController < ApplicationController 2 | inertia_config( 3 | default_render: -> { action_name == "default_render_test" }, 4 | ) 5 | use_inertia_instance_props 6 | 7 | def instance_props_test 8 | @name = 'Brandon' 9 | @sport = 'hockey' 10 | 11 | render inertia: 'TestComponent' 12 | end 13 | 14 | def default_render_test 15 | @name = 'Brian' 16 | end 17 | 18 | def provided_props_test 19 | @name = 'Brian' 20 | 21 | render inertia: 'TestComponent', props: { 22 | sport: 'basketball', 23 | } 24 | end 25 | 26 | def default_component_test 27 | render inertia: true 28 | end 29 | 30 | def default_component_with_props_test 31 | render inertia: { my: 'props' } 32 | end 33 | 34 | def default_component_with_duplicated_props_test 35 | # should raise an error 36 | render inertia: { my: 'props' }, props: { another: 'prop' } 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/inertia_responders_test_controller.rb: -------------------------------------------------------------------------------- 1 | require 'responders' 2 | 3 | class Thing 4 | end 5 | 6 | class InertiaRespondersTestController < ApplicationController 7 | self.responder = ActionController::Responder 8 | respond_to :html 9 | 10 | def redirect_test 11 | respond_with Thing.new, location: '/foo' 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/inertia_session_continuity_test_controller.rb: -------------------------------------------------------------------------------- 1 | class InertiaSessionContinuityTestController < ApplicationController 2 | def initialize_session 3 | render inertia: 'TestNewSessionComponent' 4 | end 5 | 6 | def submit_form_to_test_csrf 7 | render inertia: 'TestComponent' 8 | end 9 | 10 | def clear_session 11 | session.clear 12 | 13 | return redirect_to initialize_session_path 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/inertia_share_test_controller.rb: -------------------------------------------------------------------------------- 1 | class InertiaShareTestController < ApplicationController 2 | inertia_share name: 'Brandon' 3 | inertia_share sport: -> { 'hockey' } 4 | inertia_share({a_hash: 'also works'}) 5 | inertia_share do 6 | { 7 | position: 'center', 8 | number: number, 9 | } 10 | end 11 | 12 | def share 13 | render inertia: 'ShareTestComponent' 14 | end 15 | 16 | private 17 | 18 | def number 19 | 29 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/transformed_inertia_rails_mimic_controller.rb: -------------------------------------------------------------------------------- 1 | class TransformedInertiaRailsMimicController < ApplicationController 2 | inertia_config( 3 | default_render: true, 4 | component_path_resolver: ->(path:, action:) do 5 | "#{path.camelize}/#{action.camelize}" 6 | end 7 | ) 8 | 9 | def render_test 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /spec/dummy/app/javascript/packs/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require rails-ujs 14 | //= require_tree . 15 | -------------------------------------------------------------------------------- /spec/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | <%= inertia_ssr_head %> 2 | <%= yield %> 3 | <%= local_assigns.except(:page, :inertia_ssr_head).to_json.html_safe %> 4 | -------------------------------------------------------------------------------- /spec/dummy/app/views/layouts/conditional.html.erb: -------------------------------------------------------------------------------- 1 |

Conditional layout specified by controller

2 | <%= yield %> 3 | -------------------------------------------------------------------------------- /spec/dummy/app/views/layouts/testing.html.erb: -------------------------------------------------------------------------------- 1 |

Testing Layout for configuration_spec

2 | <%= yield %> 3 | -------------------------------------------------------------------------------- /spec/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /spec/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /spec/dummy/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path('..', __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to setup or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at anytime and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?('config/database.yml') 22 | # FileUtils.cp 'config/database.yml.sample', 'config/database.yml' 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! 'bin/rails db:prepare' 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! 'bin/rails log:clear tmp:clear' 30 | 31 | puts "\n== Restarting application server ==" 32 | system! 'bin/rails restart' 33 | end 34 | -------------------------------------------------------------------------------- /spec/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /spec/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | # require "active_model/railtie" 6 | # require "active_job/railtie" 7 | # require "active_record/railtie" 8 | # require "active_storage/engine" 9 | require "action_controller/railtie" 10 | # require "action_mailer/railtie" 11 | # require "action_mailbox/engine" 12 | # require "action_text/engine" 13 | # require "action_view/railtie" 14 | # require "action_cable/engine" 15 | # require "sprockets/railtie" 16 | # require "rails/test_unit/railtie" 17 | 18 | Bundler.require(*Rails.groups) 19 | require "inertia_rails" 20 | 21 | module Dummy 22 | class Application < Rails::Application 23 | if Gem::Version.new(Rails::VERSION::STRING) >= Gem::Version.new('5.1.0') 24 | # Initialize configuration defaults for current Rails version. 25 | config.load_defaults "#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}" 26 | end 27 | 28 | # Settings in config/environments/* take precedence over those specified here. 29 | # Application configuration can go into files in config/initializers 30 | # -- all .rb files in that directory are automatically loaded after loading 31 | # the framework and any gems in your application. 32 | 33 | # Required for Rails 5.0 and 5.1 34 | config.secret_key_base = SecureRandom.hex 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__) 6 | -------------------------------------------------------------------------------- /spec/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /spec/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.config.content_security_policy do |policy| 8 | # policy.default_src :self, :https 9 | # policy.font_src :self, :https, :data 10 | # policy.img_src :self, :https, :data 11 | # policy.object_src :none 12 | # policy.script_src :self, :https 13 | # policy.style_src :self, :https 14 | 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | 19 | # If you are using UJS then enable automatic nonce generation 20 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 21 | 22 | # Set the nonce only to specific directives 23 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src) 24 | 25 | # Report CSP violations to a specified URI 26 | # For further information see the following documentation: 27 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 28 | # Rails.application.config.content_security_policy_report_only = true 29 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /spec/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /spec/dummy/config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 9 | threads min_threads_count, max_threads_count 10 | 11 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 12 | # 13 | port ENV.fetch("PORT") { 3000 } 14 | 15 | # Specifies the `environment` that Puma will run in. 16 | # 17 | environment ENV.fetch("RAILS_ENV") { "development" } 18 | 19 | # Specifies the `pidfile` that Puma will use. 20 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 21 | 22 | # Specifies the number of `workers` to boot in clustered mode. 23 | # Workers are forked web server processes. If using threads and workers together 24 | # the concurrency of the application would be max `threads` * `workers`. 25 | # Workers do not work on JRuby or Windows (both of which do not support 26 | # processes). 27 | # 28 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 29 | 30 | # Use the `preload_app!` method when specifying a `workers` number. 31 | # This directive tells Puma to first boot the application and load code 32 | # before forking the application. This takes advantage of Copy On Write 33 | # process behavior so workers use less memory. 34 | # 35 | # preload_app! 36 | 37 | # Allow puma to be restarted by `rails restart` command. 38 | plugin :tmp_restart 39 | -------------------------------------------------------------------------------- /spec/dummy/config/spring.rb: -------------------------------------------------------------------------------- 1 | Spring.watch( 2 | ".ruby-version", 3 | ".rbenv-vars", 4 | "tmp/restart.txt", 5 | "tmp/caching-dev.txt" 6 | ) 7 | -------------------------------------------------------------------------------- /spec/dummy/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inertiajs/inertia-rails/291d0d2f09b8085881b93474879d40d413fa55fc/spec/dummy/log/.keep -------------------------------------------------------------------------------- /spec/dummy/log/production.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inertiajs/inertia-rails/291d0d2f09b8085881b93474879d40d413fa55fc/spec/dummy/log/production.log -------------------------------------------------------------------------------- /spec/dummy/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inertiajs/inertia-rails/291d0d2f09b8085881b93474879d40d413fa55fc/spec/dummy/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /spec/dummy/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inertiajs/inertia-rails/291d0d2f09b8085881b93474879d40d413fa55fc/spec/dummy/public/apple-touch-icon.png -------------------------------------------------------------------------------- /spec/dummy/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inertiajs/inertia-rails/291d0d2f09b8085881b93474879d40d413fa55fc/spec/dummy/public/favicon.ico -------------------------------------------------------------------------------- /spec/fixtures/install_generator/dummy/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | -------------------------------------------------------------------------------- /spec/fixtures/install_generator/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TestContrib 5 | 6 | <%= csrf_meta_tags %> 7 | <%= csp_meta_tag %> 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /spec/fixtures/install_generator/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.routes.draw do 4 | # root to: 'home#index' 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/install_generator/with_vite/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TestContrib 5 | 6 | <%= csrf_meta_tags %> 7 | <%= csp_meta_tag %> 8 | 9 | <%= vite_client_tag %> 10 | <%= vite_javascript_tag 'application' %> 11 | 12 | 13 | 14 | <%= yield %> 15 | 16 | 17 | -------------------------------------------------------------------------------- /spec/fixtures/install_generator/with_vite/config/vite.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /spec/fixtures/install_generator/with_vite/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /spec/fixtures/install_generator/with_vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import RubyPlugin from 'vite-plugin-ruby' 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | RubyPlugin(), 7 | ], 8 | }) 9 | -------------------------------------------------------------------------------- /spec/fixtures/package_json_files/empty_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": {} 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/package_json_files/react_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@inertiajs/react": "1.0.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /spec/fixtures/package_json_files/svelte4_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@inertiajs/svelte": "1.0.0", 4 | "svelte": "^4.0.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /spec/fixtures/package_json_files/svelte5_caret_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@inertiajs/svelte": "1.0.0", 4 | "svelte": "^5.0.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /spec/fixtures/package_json_files/svelte5_exact_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@inertiajs/svelte": "1.0.0", 4 | "svelte": "5.0.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /spec/fixtures/package_json_files/svelte5_tilde_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@inertiajs/svelte": "1.0.0", 4 | "svelte": "~5.0.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /spec/fixtures/package_json_files/tailwind_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "tailwindcss": "4.0.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /spec/fixtures/package_json_files/vue_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@inertiajs/vue3": "1.0.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /spec/inertia/always_prop_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe InertiaRails::AlwaysProp do 2 | it_behaves_like 'base prop' 3 | end 4 | -------------------------------------------------------------------------------- /spec/inertia/base_prop_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe InertiaRails::BaseProp do 2 | it_behaves_like 'base prop' 3 | end 4 | -------------------------------------------------------------------------------- /spec/inertia/defer_prop_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe InertiaRails::DeferProp do 4 | it_behaves_like 'base prop' 5 | 6 | describe '#merge?' do 7 | subject(:merge?) { prop.merge? } 8 | 9 | let(:prop) { described_class.new { 'block' } } 10 | 11 | it { is_expected.to be_falsy } 12 | 13 | context 'when merge is set' do 14 | let(:prop) { described_class.new(merge: true) { 'block' } } 15 | 16 | it { is_expected.to be true } 17 | end 18 | 19 | context 'when deep_merge is set' do 20 | let(:prop) { described_class.new(deep_merge: true) { 'block' } } 21 | 22 | it { is_expected.to be true } 23 | end 24 | 25 | context 'when both merge and deep_merge are set' do 26 | let(:prop) { described_class.new(merge: true, deep_merge: true) { 'block' } } 27 | 28 | it 'raises an ArgumentError' do 29 | expect { merge? }.to raise_error(ArgumentError, 'Cannot set both `deep_merge` and `merge` to true') 30 | end 31 | end 32 | end 33 | 34 | describe '#deep_merge?' do 35 | subject(:deep_merge?) { prop.deep_merge? } 36 | 37 | let(:prop) { described_class.new { 'block' } } 38 | 39 | it { is_expected.to be_falsy } 40 | 41 | context 'when deep is true' do 42 | let(:prop) { described_class.new(deep_merge: true) { 'block' } } 43 | 44 | it { is_expected.to be true } 45 | end 46 | end 47 | 48 | describe '#group' do 49 | subject(:group) { prop.group } 50 | 51 | let(:prop) { described_class.new { 'block' } } 52 | 53 | it { is_expected.to eq('default') } 54 | 55 | context 'when group is set' do 56 | let(:prop) { described_class.new(group: 'custom') { 'block' } } 57 | 58 | it { is_expected.to eq('custom') } 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/inertia/helper_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe InertiaRails::Helper do 4 | let(:controller) { ApplicationController.new } 5 | 6 | let(:test_helper) do 7 | Class.new do 8 | include InertiaRails::Helper 9 | attr_accessor :controller 10 | end.new 11 | end 12 | 13 | before do 14 | test_helper.controller = controller 15 | end 16 | 17 | describe '#inertia_rendering?' do 18 | context 'when not rendering through Inertia' do 19 | it 'returns nil' do 20 | expect(test_helper.inertia_rendering?).to be_nil 21 | end 22 | end 23 | 24 | context 'when rendering through Inertia' do 25 | before do 26 | controller.instance_variable_set('@_inertia_rendering', true) 27 | end 28 | 29 | it 'returns true' do 30 | expect(test_helper.inertia_rendering?).to be true 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/inertia/lazy_prop_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe InertiaRails::LazyProp do 2 | it_behaves_like 'base prop' 3 | 4 | let(:deprecator) do 5 | double(warn: nil).tap do |deprecator| 6 | allow(InertiaRails).to receive(:deprecator).and_return(deprecator) 7 | end 8 | end 9 | 10 | it 'is deprecated' do 11 | expect(deprecator).to receive(:warn).with('`lazy` is deprecated and will be removed in InertiaRails 4.0, use `optional` instead.') 12 | 13 | described_class.new('value') 14 | end 15 | 16 | describe '#call' do 17 | subject(:call) { prop.call(controller) } 18 | let(:prop) { described_class.new('value') } 19 | let(:controller) { ApplicationController.new } 20 | 21 | it { is_expected.to eq('value') } 22 | 23 | context 'with false as value' do 24 | let(:prop) { described_class.new(false) } 25 | 26 | it { is_expected.to eq(false) } 27 | end 28 | 29 | context 'with nil as value' do 30 | let(:prop) { described_class.new(nil) } 31 | 32 | it { is_expected.to eq(nil) } 33 | end 34 | 35 | context 'with a callable value' do 36 | let(:prop) { described_class.new(-> { 'callable' }) } 37 | 38 | it { is_expected.to eq('callable') } 39 | 40 | context 'with dependency on the context of a controller' do 41 | let(:prop) { described_class.new(-> { controller_method }) } 42 | 43 | it { is_expected.to eq('controller_method value') } 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/inertia/merge_prop_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe InertiaRails::MergeProp do 4 | it_behaves_like 'base prop' 5 | 6 | describe '#merge?' do 7 | subject(:merge?) { prop.merge? } 8 | 9 | let(:prop) { described_class.new { 'block' } } 10 | 11 | it { is_expected.to be true } 12 | end 13 | 14 | describe '#deep_merge?' do 15 | subject(:deep_merge?) { prop.deep_merge? } 16 | 17 | let(:prop) { described_class.new { 'block' } } 18 | 19 | it { is_expected.to be false } 20 | 21 | context 'when deep_merge is true' do 22 | let(:prop) { described_class.new(deep_merge: true) { 'block' } } 23 | 24 | it { is_expected.to be true } 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.configure do |config| 4 | # Enable flags like --only-failures and --next-failure 5 | config.example_status_persistence_file_path = '.rspec_status' 6 | 7 | # Disable RSpec exposing methods globally on `Module` and `main` 8 | config.disable_monkey_patching! 9 | 10 | config.filter_run_when_matching :focus 11 | 12 | config.order = :random 13 | 14 | config.expect_with :rspec do |c| 15 | c.syntax = :expect 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/support/helper_module.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HelperModule 4 | def self.included(base) 5 | base.extend(ClassMethods) 6 | end 7 | 8 | def with_forgery_protection 9 | orig = ActionController::Base.allow_forgery_protection 10 | begin 11 | ActionController::Base.allow_forgery_protection = true 12 | yield if block_given? 13 | ensure 14 | ActionController::Base.allow_forgery_protection = orig 15 | end 16 | end 17 | 18 | def with_env(**env) 19 | orig = ENV.to_h 20 | begin 21 | ENV.replace(env) 22 | yield if block_given? 23 | ensure 24 | ENV.replace(orig) 25 | end 26 | end 27 | 28 | module ClassMethods 29 | def with_inertia_config(**props) 30 | around do |example| 31 | config = InertiaRails.configuration 32 | orig_options = config.send(:options).dup 33 | config.merge!(InertiaRails::Configuration.new(**props)) 34 | example.run 35 | ensure 36 | config.instance_variable_set(:@options, orig_options) 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/support/shared_examples.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples 'base prop' do 2 | describe '#call' do 3 | subject(:call) { prop.call(controller) } 4 | let(:prop) { described_class.new { 'block' } } 5 | let(:controller) { ApplicationController.new } 6 | 7 | it { is_expected.to eq('block') } 8 | 9 | context 'with dependency on the context of a controller' do 10 | let(:prop) { described_class.new { controller_method } } 11 | 12 | it { is_expected.to eq('controller_method value') } 13 | end 14 | end 15 | end 16 | --------------------------------------------------------------------------------