diff --git a/docs/cookbook/inertia-modal.md b/docs/cookbook/inertia-modal.md index 8e411744..0f69b553 100644 --- a/docs/cookbook/inertia-modal.md +++ b/docs/cookbook/inertia-modal.md @@ -260,8 +260,8 @@ To define the base route for your modal, you need to use the `inertia_modal` ren class UsersController < ApplicationController def edit - render inertia: 'Users/Edit', props: { # [!code --] - render inertia_modal: 'Users/Edit', props: { # [!code ++] + render inertia: { # [!code --] + render inertia_modal: { # [!code ++] user:, roles: -> { Role.all }, } @@ -275,7 +275,7 @@ Then, you can pass the `base_url` parameter to the `inertia_modal` renderer to d class UsersController < ApplicationController def edit - render inertia_modal: 'Users/Edit', props: { + render inertia_modal: { user:, roles: -> { Role.all }, } # [!code --] diff --git a/docs/cookbook/server-managed-meta-tags.md b/docs/cookbook/server-managed-meta-tags.md index 9c01eda8..02a1e87b 100644 --- a/docs/cookbook/server-managed-meta-tags.md +++ b/docs/cookbook/server-managed-meta-tags.md @@ -212,7 +212,7 @@ class EventsController < ApplicationController def show event = Event.find(params[:id]) - render inertia: 'Event/Show', props: { event: event.as_json }, meta: [ + render inertia: { event: event.as_json }, meta: [ { title: "Check out the #{event.name} event!" }, { name: 'description', content: event.description }, { tag_name: 'script', type: 'application/ld+json', inner_content: { '@context': 'https://schema.org', '@type': 'Event', name: 'My Event' } } @@ -232,7 +232,7 @@ class EventsController < ApplicationController before_action :set_meta_tags def show - render inertia: 'Event/Show', props: { event: Event.find(params[:id]) } + render inertia: { event: Event.find(params[:id]) } end private @@ -319,12 +319,12 @@ class StoriesController < ApplicationController # Renders a single article:author meta tag def single_author - render inertia: 'Stories/Show' + render inertia: 'stories/show' end # Renders multiple article:author meta tags def multiple_authors - render inertia: 'Stories/Show', meta: [ + render inertia: 'stories/show', meta: [ { name: 'article:author', content: 'Dan Gilroy', allow_duplicates: true }, ] end diff --git a/docs/guide/authorization.md b/docs/guide/authorization.md index 07021b05..de6f77c8 100644 --- a/docs/guide/authorization.md +++ b/docs/guide/authorization.md @@ -9,7 +9,7 @@ Here's an example of how you might do this in a Rails controller using the [Acti ```ruby class UsersController < ApplicationController def index - render inertia: 'Users/Index', props: { + render inertia: { can: { create_user: allowed_to?(:create, User) }, diff --git a/docs/guide/deferred-props.md b/docs/guide/deferred-props.md index 7ad3ce9b..27f99d5e 100644 --- a/docs/guide/deferred-props.md +++ b/docs/guide/deferred-props.md @@ -11,7 +11,7 @@ To defer a prop, you can use the `InertiaRails.defer` method when returning your ```ruby class UsersController < ApplicationController def index - render inertia: 'Users/Index', props: { + render inertia: { users: -> { User.all }, roles: -> { Role.all }, permissions: InertiaRails.defer { Permission.all }, @@ -27,7 +27,7 @@ By default, all deferred props get fetched in one request after the initial page ```ruby class UsersController < ApplicationController def index - render inertia: 'Users/Index', props: { + render inertia: { users: -> { User.all }, roles: -> { Role.all }, permissions: InertiaRails.defer { Permission.all }, diff --git a/docs/guide/history-encryption.md b/docs/guide/history-encryption.md index 3dd0a4cd..ca1b4a68 100644 --- a/docs/guide/history-encryption.md +++ b/docs/guide/history-encryption.md @@ -22,7 +22,7 @@ If you'd like to enable history encryption globally, set the `encrypt_history` c You are able to opt out of encryption on specific pages by passing `false` to the `encrypt_history` option. ```ruby -render inertia: 'Homepage', props: {}, encrypt_history: false +render inertia: {}, encrypt_history: false ``` ### Per-request encryption @@ -30,7 +30,7 @@ render inertia: 'Homepage', props: {}, encrypt_history: false To encrypt the history of an individual request, simply pass `true` to the `encrypt_history` option. ```ruby -render inertia: 'Dashboard', props: {}, encrypt_history: true +render inertia: {}, encrypt_history: true ``` ### Controller-level encryption @@ -50,7 +50,7 @@ end To clear the history state on the server side, you can pass the `clear_history` option to the `render` method. ```ruby -render inertia: 'Dashboard', props: {}, clear_history: true +render inertia: {}, clear_history: true ``` Once the response has rendered on the client, the encryption key will be rotated, rendering the previous history state unreadable. diff --git a/docs/guide/pages.md b/docs/guide/pages.md index adbec25d..092c5599 100644 --- a/docs/guide/pages.md +++ b/docs/guide/pages.md @@ -85,14 +85,14 @@ export default function Welcome({ user }) { ::: -Given the page above, you can render the page by returning an Inertia response from a controller or route. In this example, let's assume this page is stored at `app/frontend/pages/User/Show.(jsx|vue|svelte)` within a Rails application. +Given the page above, you can render the page by returning an Inertia response from a controller or route. In this example, let's assume this page is stored at `app/frontend/pages/user/show.(jsx|vue|svelte)` within a Rails application. ```ruby class UsersController < ApplicationController def show user = User.find(params[:id]) - render inertia: 'User/Show', props: { user: } + render inertia: { user: } end end ``` diff --git a/docs/guide/partial-reloads.md b/docs/guide/partial-reloads.md index b282b916..e1d75e83 100644 --- a/docs/guide/partial-reloads.md +++ b/docs/guide/partial-reloads.md @@ -210,7 +210,7 @@ For partial reloads to be most effective, be sure to also use lazy data evaluati ```ruby class UsersController < ApplicationController def index - render inertia: 'Users/Index', props: { + render inertia: { users: -> { User.all }, companies: -> { Company.all }, } @@ -225,7 +225,7 @@ Additionally, Inertia provides an `InertiaRails.optional` method to specify that ```ruby class UsersController < ApplicationController def index - render inertia: 'Users/Index', props: { + render inertia: { users: InertiaRails.optional { User.all }, } end @@ -240,7 +240,7 @@ On the inverse, you can use the `InertiaRails.always` method to specify that a p ```ruby class UsersController < ApplicationController def index - render inertia: 'Users/Index', props: { + render inertia: { users: InertiaRails.always { User.all }, } end @@ -252,7 +252,7 @@ Here's a summary of each approach: ```ruby class UsersController < ApplicationController def index - render inertia: 'Users/Index', props: { + render inertia: { # ALWAYS included on standard visits # OPTIONALLY included on partial reloads # ALWAYS evaluated diff --git a/docs/guide/responses.md b/docs/guide/responses.md index 24205995..0963dabe 100644 --- a/docs/guide/responses.md +++ b/docs/guide/responses.md @@ -2,52 +2,61 @@ ## Creating responses -Creating an Inertia response is simple. To get started, just use the `inertia` renderer in your controller methods, providing both the name of the [JavaScript page component](/guide/pages.md) that you wish to render, as well as any props (data) for the page. +Creating an Inertia response is simple. By default, Inertia Rails follows convention over configuration: you simply pass the props (data) you wish to send to the page, and the component name is automatically inferred from the controller and action. ```ruby -class EventsController < ApplicationController +class UsersController < ApplicationController def show - event = Event.find(params[:id]) - - render inertia: 'Event/Show', props: { - event: event.as_json( - only: [:id, :title, :start_date, :description] - ) - } + user = User.find(params[:id]) + render inertia: { user: } # Renders '../users/show.jsx|vue|svelte' end end ``` -Within Rails applications, the `Event/Show` page would typically correspond to the file located at `app/frontend/pages/Event/Show.(jsx|vue|svelte)`. +Within Rails applications, the `UsersController#show` action would typically correspond to the file located at `app/frontend/pages/users/show.(jsx|vue|svelte)`. > [!WARNING] > To ensure that pages load quickly, only return the minimum data required for the page. Also, be aware that **all data returned from the controllers will be visible client-side**, so be sure to omit sensitive information. -### Automatically determine component name +### Customizing the Component Path + +While the default convention works for most cases, you may need to render a specific component or change how component paths are resolved globally. -You can pass props without specifying a component name: +#### Explicit Component Names + +If you wish to render a component that does not match the current controller action, you can explicitly provide the name of the [JavaScript page component](/guide/pages) followed by the props hash. ```ruby -class UsersController < ApplicationController - def show - render inertia: { user: @user } # Will render '../users/show.jsx|vue|svelte' +class EventsController < ApplicationController + def my_event + event = Event.find(params[:id]) + + render inertia: 'events/show', props: { + event: event.as_json( + only: [:id, :title, :start_date, :description] + ) + } end end ``` -If the default component path doesn't match your convention, you can define a custom resolution method via the `component_path_resolver` config value. The value should be callable and will receive the path and action parameters, returning a string component path. +#### Custom Path Resolver + +If the default automatic path resolution does not match your project's conventions, you can define a custom resolution method via the `component_path_resolver` config value. + +The value should be callable and will receive the `path` and `action` parameters, returning a string component path. ```ruby inertia_config( component_path_resolver: ->(path:, action:) do - "Storefront/#{path.camelize}/#{action.camelize}" + "storefront/#{path.camelize}/#{action.camelize}" end ) ``` ### Using instance variables as props -Inertia enables the automatic passing of instance variables as props. This can be achieved by invoking the `use_inertia_instance_props` function in a controller or in a base controller from which other controllers inherit. +For convenience, Inertia can automatically pass your controller's instance variables to the page component as props. To enable this behavior, invoke the `use_inertia_instance_props` method within your controller or a base controller. ```ruby class EventsController < ApplicationController @@ -56,18 +65,25 @@ class EventsController < ApplicationController def index @events = Event.all - render inertia: 'Events/Index' + render inertia: 'events/index' end end ``` -This action automatically passes the `@events` instance variable as the `events` prop to the `Events/Index` page component. +In this example, the `@events` instance variable is automatically included in the response as the `events` prop. -> [!NOTE] -> Manually providing any props for a response disables the instance props feature for that specific response. +Please note that if you manually provide a props hash in your render call, the instance variables feature is disabled for that specific response. -> [!NOTE] -> Instance props are only included if they are defined **after** the `use_inertia_instance_props` call, hence the order of `before_action` callbacks is crucial. +> [!WARNING] +> Security and Performance Risk +> +> When enabled, this feature serializes all instance variables present in the controller at the time of rendering. This includes: +> +> - Variables set by `before_action` filters (e.g., `@current_user`, `@breadcrumbs`) called **after** `use_inertia_instance_props`. +> - Memoized variables (often used for caching internal state, e.g., `@_cached_result`). +> - Variables intended only for server-side logic. +> +> This creates a high risk of accidentally leaking sensitive data or internal implementation details to the client. It can also negatively impact performance by serializing unnecessary heavy objects. We recommend being explicit with your props whenever possible. ## Root template data @@ -89,7 +105,7 @@ Sometimes you may even want to provide data to the root template that will not b def show event = Event.find(params[:id]) - render inertia: 'Event', props: { event: }, view_data: { meta: event.meta } + render inertia: { event: }, view_data: { meta: event.meta } end ``` @@ -134,13 +150,13 @@ $ bin/rails generate inertia:scaffold Post title:string body:text invoke scaffold_controller create app/controllers/posts_controller.rb invoke inertia_templates - create app/frontend/pages/Post - create app/frontend/pages/Post/Index.svelte - create app/frontend/pages/Post/Edit.svelte - create app/frontend/pages/Post/Show.svelte - create app/frontend/pages/Post/New.svelte - create app/frontend/pages/Post/Form.svelte - create app/frontend/pages/Post/Post.svelte + create app/frontend/pages/posts + create app/frontend/pages/posts/index.svelte + create app/frontend/pages/posts/edit.svelte + create app/frontend/pages/posts/show.svelte + create app/frontend/pages/posts/new.svelte + create app/frontend/pages/posts/form.svelte + create app/frontend/pages/posts/post.svelte invoke resource_route invoke test_unit create test/controllers/posts_controller_test.rb @@ -178,9 +194,9 @@ $ bin/rails generate inertia:controller pages welcome next_steps create app/helpers/pages_helper.rb invoke test_unit invoke inertia_templates - create app/frontend/pages/Pages - create app/frontend/pages/Pages/Welcome.jsx - create app/frontend/pages/Pages/NextSteps.jsx + create app/frontend/pages/pages + create app/frontend/pages/pages/welcome.jsx + create app/frontend/pages/pages/next_steps.jsx ``` ### Customizing the generator templates @@ -235,7 +251,7 @@ Inertia responses always operate as a `:html` response type. This means that you def some_action respond_to do |format| format.html do - render inertia: 'Some/Component', props: { data: 'value' } + render inertia: { data: 'value' } end format.json do diff --git a/docs/guide/routing.md b/docs/guide/routing.md index c367a4e6..49aab8b6 100644 --- a/docs/guide/routing.md +++ b/docs/guide/routing.md @@ -34,12 +34,12 @@ end 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. -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. +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. ```ruby class UsersController < ApplicationController def index - render inertia: 'Users/Index', props: { + render inertia: { users: User.all.map do |user| user.as_json( only: [ :id, :name, :email ] diff --git a/docs/guide/server-side-setup.md b/docs/guide/server-side-setup.md index 1fa54fc8..58536543 100644 --- a/docs/guide/server-side-setup.md +++ b/docs/guide/server-side-setup.md @@ -174,7 +174,7 @@ class EventsController < ApplicationController def show event = Event.find(params[:id]) - render inertia: 'Event/Show', props: { + render inertia: { event: event.as_json( only: [:id, :title, :start_date, :description] ) diff --git a/docs/guide/shared-data.md b/docs/guide/shared-data.md index 284703be..c13abb9d 100644 --- a/docs/guide/shared-data.md +++ b/docs/guide/shared-data.md @@ -244,9 +244,9 @@ Let's say we want a particular action to change only part of that data structure ```ruby class CrazyScorersController < ApplicationController def index - render inertia: 'CrazyScorersComponent', - props: { basketball_data: { points: 100 } }, - deep_merge: true + render inertia: { + basketball_data: { points: 100 } + }, deep_merge: true end end @@ -283,9 +283,9 @@ class CrazyScorersController < ApplicationController end def index - render inertia: 'CrazyScorersComponent', - props: { basketball_data: { points: 100 } }, - deep_merge: false + render inertia: { + basketball_data: { points: 100 } + }, deep_merge: false end end