inertia_jb 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +51 -58
- data/lib/inertia_jb/controller.rb +24 -14
- data/lib/inertia_jb/handler.rb +34 -8
- data/lib/inertia_jb/props.rb +37 -0
- data/lib/inertia_jb/railtie.rb +16 -3
- data/lib/inertia_jb/version.rb +1 -1
- data/lib/inertia_jb.rb +8 -5
- data/test/controller_test.rb +7 -9
- data/test/layout_suppression_test.rb +10 -9
- data/test/layout_test.rb +3 -7
- data/test/rendering_test.rb +238 -0
- metadata +5 -3
- data/lib/inertia_jb/renderer.rb +0 -34
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e28cad09da1cc8c9b7098013dae68a3f87a17c9c770448955459672f4df53d38
|
|
4
|
+
data.tar.gz: f3b9082f2f747bd5b263710652d816a7f750eae331eb2fde9c2a86190e71b003
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b89149beecc268c316a4e91126c33df0b840a44b66a5b536a2b89bdfc21c74a3489ed699f2800185f7834fadd0daeca8b4653e2efcd2309f4b0c9e1c3a15205b
|
|
7
|
+
data.tar.gz: f95ce5ed914aa0477fa63a33af696da8dc921eeb3609f56cd45cda181f9c259820bb8080a2d308f9617c8b729505c25c3574c4c4f546ef3bf6376736b1961805
|
data/README.md
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
InertiaJb lets you declare [Inertia.js](https://inertiajs.com/) props
|
|
4
4
|
for your Rails frontend components inside **view templates**, using plain Ruby
|
|
5
|
-
Hashes
|
|
5
|
+
Hashes.
|
|
6
6
|
|
|
7
7
|
It is built on top of the [inertia-rails](https://github.com/inertiajs/inertia-rails) gem.
|
|
8
8
|
|
|
9
|
-
A `*.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
A `*.inertia` template is just Ruby code whose last expression is a Hash.
|
|
10
|
+
`inertia` is registered as a real Rails template format, so the template renders
|
|
11
|
+
through the ordinary Rails pipeline and its resulting Hash is handed straight to
|
|
12
|
+
`InertiaRails::Renderer` — meaning **every Inertia protocol feature works out of
|
|
13
|
+
the box**: partial reloads (at any nesting depth), optional / always / deferred /
|
|
14
|
+
scroll props, prop merging, shared data, and global key transforms.
|
|
14
15
|
|
|
15
16
|
```ruby
|
|
16
17
|
# app/controllers/messages_controller.rb
|
|
@@ -25,7 +26,7 @@ end
|
|
|
25
26
|
```
|
|
26
27
|
|
|
27
28
|
```ruby
|
|
28
|
-
# app/views/messages/show.
|
|
29
|
+
# app/views/messages/show.inertia
|
|
29
30
|
{
|
|
30
31
|
message: {
|
|
31
32
|
content: @message.content,
|
|
@@ -55,15 +56,6 @@ export default function Message({ user, message }) {
|
|
|
55
56
|
}
|
|
56
57
|
```
|
|
57
58
|
|
|
58
|
-
## Why jb?
|
|
59
|
-
|
|
60
|
-
Inertia's server side is fundamentally about producing **a Hash of props** that
|
|
61
|
-
`inertia-rails` then resolves and serializes. jb templates *are* plain Ruby
|
|
62
|
-
Hashes, so there is zero impedance mismatch: no DSL to learn, no intermediate
|
|
63
|
-
representation, and the full power of Ruby for building collections and
|
|
64
|
-
conditionals.
|
|
65
|
-
|
|
66
|
-
|
|
67
59
|
## Installation
|
|
68
60
|
|
|
69
61
|
Add the gem to your Gemfile:
|
|
@@ -72,8 +64,9 @@ Add the gem to your Gemfile:
|
|
|
72
64
|
gem "inertia_jb"
|
|
73
65
|
```
|
|
74
66
|
|
|
75
|
-
Then, in your inertia-rails initializer,
|
|
76
|
-
normal implicit render falls through to your `.
|
|
67
|
+
Then, in your inertia-rails initializer, keep `default_render` **off** (that is
|
|
68
|
+
its own default) so a normal implicit render falls through to your `.inertia`
|
|
69
|
+
template:
|
|
77
70
|
|
|
78
71
|
```ruby
|
|
79
72
|
# config/initializers/inertia_rails.rb
|
|
@@ -82,8 +75,6 @@ InertiaRails.configure do |config|
|
|
|
82
75
|
end
|
|
83
76
|
```
|
|
84
77
|
|
|
85
|
-
You do **not** need to call `use_inertia_instance_props`.
|
|
86
|
-
|
|
87
78
|
## Layout
|
|
88
79
|
|
|
89
80
|
On an **initial (non-XHR) page load** the `data-page` root element is wrapped in
|
|
@@ -95,13 +86,13 @@ The layout is chosen from inertia-rails' `config.layout`, matching
|
|
|
95
86
|
|
|
96
87
|
## Templates and partials
|
|
97
88
|
|
|
98
|
-
- **Page templates** live at `app/views/<controller>/<action>.
|
|
89
|
+
- **Page templates** live at `app/views/<controller>/<action>.inertia` and
|
|
99
90
|
must return a Hash (your Inertia props).
|
|
100
91
|
- **Partials** are ordinary [jb](https://github.com/amatsuda/jb) partials named
|
|
101
|
-
`_name.
|
|
92
|
+
`_name.jb`. They return a Hash, and compose naturally:
|
|
102
93
|
|
|
103
94
|
```ruby
|
|
104
|
-
# app/views/messages/show.
|
|
95
|
+
# app/views/messages/show.inertia
|
|
105
96
|
{
|
|
106
97
|
author: render(partial: "authors/author", object: @message.author),
|
|
107
98
|
comments: render(partial: "comments/comment", collection: @message.comments)
|
|
@@ -109,20 +100,20 @@ The layout is chosen from inertia-rails' `config.layout`, matching
|
|
|
109
100
|
```
|
|
110
101
|
|
|
111
102
|
```ruby
|
|
112
|
-
# app/views/authors/_author.
|
|
103
|
+
# app/views/authors/_author.jb
|
|
113
104
|
{ id: author.id, name: author.name }
|
|
114
105
|
```
|
|
115
106
|
|
|
116
107
|
`render(partial:, collection:)` returns an **array of Hashes** (thanks to jb),
|
|
117
|
-
which you embed directly. Don't name partials `.
|
|
118
|
-
|
|
108
|
+
which you embed directly. Don't name partials `.inertia` — that format is for
|
|
109
|
+
top-level page templates only, and turns the result into an Inertia response.
|
|
119
110
|
|
|
120
111
|
## Sharing a partial with a plain JSON API
|
|
121
112
|
|
|
122
113
|
An Inertia page and a plain JSON endpoint are both, in the end, just **a Hash**,
|
|
123
114
|
so a single jb partial can back both. Name the partial **without a format**
|
|
124
|
-
(`_message.jb`, not `_message.html.jb`) so it resolves for the
|
|
125
|
-
|
|
115
|
+
(`_message.jb`, not `_message.html.jb`) so it resolves for the formats an Inertia
|
|
116
|
+
page renders under *and* the `json` format a normal API request uses:
|
|
126
117
|
|
|
127
118
|
```ruby
|
|
128
119
|
# app/views/messages/_message.jb
|
|
@@ -134,12 +125,12 @@ Inertia uses *and* the `json` format a normal API request uses:
|
|
|
134
125
|
```
|
|
135
126
|
|
|
136
127
|
```ruby
|
|
137
|
-
# app/views/messages/show.
|
|
138
|
-
|
|
128
|
+
# app/views/messages/show.inertia — the Inertia page
|
|
129
|
+
render(partial: "messages/message", object: @message)
|
|
139
130
|
```
|
|
140
131
|
|
|
141
132
|
```ruby
|
|
142
|
-
# app/views/messages/index.
|
|
133
|
+
# app/views/messages/index.inertia — nested under a key
|
|
143
134
|
{ messages: render(partial: "messages/message", collection: @messages, as: :message) }
|
|
144
135
|
```
|
|
145
136
|
|
|
@@ -150,41 +141,22 @@ render(partial: "messages/message", object: @message)
|
|
|
150
141
|
|
|
151
142
|
|
|
152
143
|
Note the asymmetry: the JSON endpoint can return that Array at the top level,
|
|
153
|
-
but the Inertia page **must** nest it under a key (`{
|
|
154
|
-
props must be an object, never a top-level Array.
|
|
155
|
-
|
|
156
|
-
> **Gotcha — wrap the page template in a Hash literal.** A `.html.inertia` page
|
|
157
|
-
> must **not** be a bare top-level `render(partial: …)`:
|
|
158
|
-
>
|
|
159
|
-
> ```ruby
|
|
160
|
-
> # ❌ props get misread as the component name
|
|
161
|
-
> render(partial: "messages/message", object: @message)
|
|
162
|
-
>
|
|
163
|
-
> # ✅ spread into a real Hash literal
|
|
164
|
-
> { **render(partial: "messages/message", object: @message) }
|
|
165
|
-
> ```
|
|
166
|
-
>
|
|
167
|
-
> jb's `render(partial:)` returns a `Jb::TemplateResult` (a delegator), not a
|
|
168
|
-
> true `Hash`. inertia-rails decides *"is this props or a component name?"* with
|
|
169
|
-
> `component.is_a?(Hash)`, so a bare partial result is taken for a component name
|
|
170
|
-
> and your props end up in the `component` field. Wrapping it in a literal
|
|
171
|
-
> `{ **… }` — or nesting it under a key, e.g. `{ message: render(…) }` — makes
|
|
172
|
-
> the top-level value a genuine `Hash`, which inertia-rails reads as props. A
|
|
173
|
-
> `.json.jb` endpoint never hits this, because jb serializes its top-level result
|
|
174
|
-
> with `to_json` directly.
|
|
144
|
+
but the Inertia page **must** nest it under a key (`{ messages: … }`) — Inertia
|
|
145
|
+
props must be an object, never a top-level Array. An **object** partial is fine
|
|
146
|
+
bare, as in `show.inertia` above.
|
|
175
147
|
|
|
176
148
|
If you'd rather keep a format-specific partial (`_message.json.jb`), borrow the
|
|
177
149
|
`:json` variant from the Inertia side with `formats:`:
|
|
178
150
|
|
|
179
151
|
```ruby
|
|
180
|
-
# app/views/messages/show.
|
|
181
|
-
|
|
152
|
+
# app/views/messages/show.inertia
|
|
153
|
+
render(partial: "messages/message", object: @message, formats: [:json])
|
|
182
154
|
```
|
|
183
155
|
|
|
184
156
|
## Inertia prop types
|
|
185
157
|
|
|
186
158
|
Because props are just a Hash, Inertia's special prop types are plain values you
|
|
187
|
-
drop in. Inside a `.
|
|
159
|
+
drop in. Inside a `.inertia` template you can use the short helpers
|
|
188
160
|
(`optional`, `always`, `defer`, `scroll`, `merge`, `deep_merge`, `once`, `cache`) or the full
|
|
189
161
|
`InertiaRails.*` methods.
|
|
190
162
|
|
|
@@ -246,8 +218,29 @@ Alternatively, just write camelCase keys directly in your templates.
|
|
|
246
218
|
|
|
247
219
|
- Inertia props must be an **object**, so page templates should return a Hash
|
|
248
220
|
(not a top-level Array).
|
|
249
|
-
-
|
|
250
|
-
template
|
|
221
|
+
- The gem registers `inertia` as a `Mime::Type` **alias** of `text/html`. Because
|
|
222
|
+
it is an alias, it is skipped by content negotiation: `inertia` is a template
|
|
223
|
+
format, never a wire format, and `Accept` headers are unaffected.
|
|
224
|
+
- If both `show.html.erb` and `show.inertia` exist for one action, Rails prefers
|
|
225
|
+
the exact format match — the ERB one.
|
|
226
|
+
- If your app pins `config.action_view.default_formats`, you don't need to add
|
|
227
|
+
`inertia` to it: page templates carry no format extension, so they resolve under
|
|
228
|
+
whatever format the request already asked for.
|
|
229
|
+
|
|
230
|
+
## Upgrading from 0.3.x
|
|
231
|
+
|
|
232
|
+
1. **Rename page templates**: `show.html.inertia` → `show.inertia`. A leftover
|
|
233
|
+
`.html.inertia` template raises with the rename instruction, so nothing fails
|
|
234
|
+
silently. Jb partials (`_author.html.jb`, `_author.jb`) don't change.
|
|
235
|
+
2. **Component names now follow the rendered template** rather than
|
|
236
|
+
`controller_path`/`action_name`. Identical for a normal implicit render; it
|
|
237
|
+
differs when you `render :other_action` (you now get `other_action`'s
|
|
238
|
+
component, which is the point) or when a template is inherited from a parent
|
|
239
|
+
controller's view directory (the component follows the template's directory).
|
|
240
|
+
3. `{ **render(partial: …) }` in a page template can lose the wrapper — a bare
|
|
241
|
+
`render(partial: …)` is now read as props.
|
|
242
|
+
|
|
243
|
+
`config.default_render = false` is still required, unchanged from 0.3.x.
|
|
251
244
|
|
|
252
245
|
## Development
|
|
253
246
|
|
|
@@ -1,28 +1,38 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module InertiaJb
|
|
4
|
-
# Controller concern that renders
|
|
5
|
-
# responses.
|
|
4
|
+
# Controller concern that renders `*.inertia` templates as Inertia responses.
|
|
6
5
|
#
|
|
7
|
-
#
|
|
8
|
-
# render
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# resolution, shared data,
|
|
12
|
-
# SSR and the
|
|
13
|
-
#
|
|
6
|
+
# There is no control-flow trickery here: an `.inertia` page goes through
|
|
7
|
+
# Rails' ordinary render pipeline and its *return value* — a {Props} object —
|
|
8
|
+
# comes back out of `render_to_body`. We recognise it there and re-enter with
|
|
9
|
+
# `inertia:`, which inertia-rails registered as a proper
|
|
10
|
+
# +ActionController::Renderers+ renderer. Component resolution, shared data,
|
|
11
|
+
# `PropsResolver`/partial reloads, `config.layout`, SSR and the
|
|
12
|
+
# X-Inertia/Vary headers therefore all stay with inertia-rails.
|
|
14
13
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
14
|
+
# Because the hand-off keys on the rendered result, `default_render` needs no
|
|
15
|
+
# hook of its own: Rails' ordinary implicit render finds the `.inertia`
|
|
16
|
+
# template, and explicit renders work the same way — `render :new`,
|
|
17
|
+
# `render template: "other/page"`, `render :edit, status: :unprocessable_entity`.
|
|
18
|
+
#
|
|
19
|
+
# The one thing that *does* have to stay out of the way is inertia-rails'
|
|
20
|
+
# `config.default_render`, which short-circuits `default_render` into
|
|
21
|
+
# `render inertia: true` before any template is consulted; apps therefore set it
|
|
22
|
+
# to `false` (its own default).
|
|
17
23
|
#
|
|
18
24
|
# +InertiaRails::Controller+ is already mixed into +ActionController::Base+ by
|
|
19
25
|
# inertia-rails' engine, so we don't include it again.
|
|
20
26
|
module Controller
|
|
21
27
|
extend ActiveSupport::Concern
|
|
22
28
|
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
def render_to_body(options = {})
|
|
30
|
+
body = super
|
|
31
|
+
return body unless body.is_a?(InertiaJb::Props)
|
|
32
|
+
|
|
33
|
+
# Second pass: `:inertia` is handled by inertia-rails' renderer, which
|
|
34
|
+
# returns a real String body, so this never recurses further.
|
|
35
|
+
render_to_body(inertia: body.component(inertia_configuration), props: body.props)
|
|
26
36
|
end
|
|
27
37
|
end
|
|
28
38
|
end
|
data/lib/inertia_jb/handler.rb
CHANGED
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module InertiaJb
|
|
4
|
-
# ActionView template handler for `*.
|
|
4
|
+
# ActionView template handler for `*.inertia` page templates.
|
|
5
5
|
#
|
|
6
|
-
# An `.
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
6
|
+
# An `.inertia` template is jb-style Ruby: its last expression is a Hash of
|
|
7
|
+
# Inertia props. The handler compiles it into a {Props} object — the
|
|
8
|
+
# template's *return value*, produced by an otherwise completely ordinary
|
|
9
|
+
# Rails render. {Controller#render_to_body} recognises that value and hands it
|
|
10
|
+
# to inertia-rails' `:inertia` renderer.
|
|
11
|
+
#
|
|
12
|
+
# The `default_format` declaration is what makes `:inertia` a real Rails
|
|
13
|
+
# template format: a file named `show.inertia` carries no format extension, so
|
|
14
|
+
# +TemplateDetails#format_or_default+ falls back to this value and the built
|
|
15
|
+
# +ActionView::Template+ reports `format == :inertia`. Two things follow for
|
|
16
|
+
# free — the template still resolves for an ordinary `html` request (a
|
|
17
|
+
# format-less path matches any requested format), and no layout is ever found
|
|
18
|
+
# for it, because layout lookup runs against `formats.first` (`:inertia`) and
|
|
19
|
+
# `layouts/*.html.erb` simply does not match.
|
|
11
20
|
class Handler
|
|
21
|
+
class_attribute :default_format
|
|
22
|
+
self.default_format = :inertia
|
|
23
|
+
|
|
12
24
|
def self.call(template, source = nil)
|
|
13
25
|
source ||= template.source
|
|
14
26
|
|
|
27
|
+
unless template.format == :inertia
|
|
28
|
+
raise ArgumentError, <<~MESSAGE
|
|
29
|
+
InertiaJb: #{template.identifier} has format #{template.format.inspect}, not :inertia.
|
|
30
|
+
Page templates must be named `<action>.inertia` (drop the `.html.`) so that
|
|
31
|
+
Rails resolves them as the :inertia format.
|
|
32
|
+
MESSAGE
|
|
33
|
+
end
|
|
34
|
+
|
|
15
35
|
# `begin;#{source}` keeps the template's own line numbers aligned in
|
|
16
36
|
# backtraces. The block evaluates to the template's last expression (the
|
|
17
|
-
# props Hash)
|
|
18
|
-
|
|
37
|
+
# props Hash). `@virtual_path` is assigned by Rails in the compiled
|
|
38
|
+
# method's preamble, so it is already set here.
|
|
39
|
+
"::InertiaJb::Props.new(@virtual_path, begin;#{source}\nend)"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Hand the source to Ruby as-is, like Jb::Handler does.
|
|
43
|
+
def self.handles_encoding?
|
|
44
|
+
true
|
|
19
45
|
end
|
|
20
46
|
end
|
|
21
47
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module InertiaJb
|
|
4
|
+
# The result of rendering an `*.inertia` template: the props Hash plus the
|
|
5
|
+
# template's virtual path.
|
|
6
|
+
#
|
|
7
|
+
# It exists so that {Controller#render_to_body} can tell "this is a set of
|
|
8
|
+
# Inertia props" from "this is a response body" without inspecting formats or
|
|
9
|
+
# guessing, and so the Inertia component name can be derived from the template
|
|
10
|
+
# that actually rendered.
|
|
11
|
+
class Props
|
|
12
|
+
attr_reader :virtual_path, :props
|
|
13
|
+
|
|
14
|
+
def initialize(virtual_path, props)
|
|
15
|
+
@virtual_path = virtual_path
|
|
16
|
+
@props = props
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The Inertia component name for this page.
|
|
20
|
+
#
|
|
21
|
+
# Derived from the rendered template rather than from
|
|
22
|
+
# `controller_path`/`action_name`, so an explicit `render :edit` points at
|
|
23
|
+
# the `edit` component instead of the acting `create`.
|
|
24
|
+
#
|
|
25
|
+
# The virtual path is split into the same `path:`/`action:` pair inertia-rails
|
|
26
|
+
# feeds its own resolver, and handed to `config.component_path_resolver`
|
|
27
|
+
# unconditionally — a custom resolver (camelizing, prefixing, ...) owns the
|
|
28
|
+
# result in every case, including a page template that sits at the root of a
|
|
29
|
+
# view path (`path:` is then `""`, which the default resolver renders as
|
|
30
|
+
# `"/name"`).
|
|
31
|
+
def component(configuration)
|
|
32
|
+
prefix, _, name = virtual_path.rpartition("/")
|
|
33
|
+
|
|
34
|
+
configuration.component_path_resolver(path: prefix, action: name)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/inertia_jb/railtie.rb
CHANGED
|
@@ -5,14 +5,27 @@ require "rails/railtie"
|
|
|
5
5
|
module InertiaJb
|
|
6
6
|
class Railtie < ::Rails::Railtie
|
|
7
7
|
initializer :inertia_jb do
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
# Makes `:inertia` a valid Rails template format (`Mime::SET.symbols` is
|
|
9
|
+
# what `ActionView::Template::Types.symbols` reads). Registered here, in
|
|
10
|
+
# the initializer body, so it lands before any template path is parsed —
|
|
11
|
+
# the resolvers memoize a regex built from the format list.
|
|
12
|
+
#
|
|
13
|
+
# `register_alias` rather than `register`: the latter would overwrite
|
|
14
|
+
# `LOOKUP["text/html"]` and break HTML content negotiation app-wide.
|
|
15
|
+
# An alias skips the mime-string lookup, so `:inertia` never takes part in
|
|
16
|
+
# Accept negotiation — it is a template format, not a wire format.
|
|
17
|
+
Mime::Type.register_alias "text/html", :inertia unless Mime[:inertia]
|
|
18
|
+
|
|
19
|
+
# `prepend`, not `include`: `render_to_body` has to sit ahead of
|
|
20
|
+
# +ActionController::Renderers+ in the ancestry so `super` reaches it, and
|
|
21
|
+
# prepending makes that position independent of gem load order.
|
|
22
|
+
ActiveSupport.on_load(:action_controller_base) do
|
|
23
|
+
prepend InertiaJb::Controller
|
|
10
24
|
end
|
|
11
25
|
|
|
12
26
|
ActiveSupport.on_load(:action_view) do
|
|
13
27
|
ActionView::Template.register_template_handler :inertia, InertiaJb::Handler
|
|
14
28
|
ActionView::Base.include InertiaJb::Helper
|
|
15
|
-
ActionView::TemplateRenderer.prepend InertiaJb::Renderer
|
|
16
29
|
end
|
|
17
30
|
end
|
|
18
31
|
end
|
data/lib/inertia_jb/version.rb
CHANGED
data/lib/inertia_jb.rb
CHANGED
|
@@ -4,8 +4,8 @@ require "jb"
|
|
|
4
4
|
require "inertia_rails"
|
|
5
5
|
|
|
6
6
|
require "inertia_jb/version"
|
|
7
|
+
require "inertia_jb/props"
|
|
7
8
|
require "inertia_jb/handler"
|
|
8
|
-
require "inertia_jb/renderer"
|
|
9
9
|
require "inertia_jb/controller"
|
|
10
10
|
require "inertia_jb/helper"
|
|
11
11
|
require "inertia_jb/railtie" if defined?(Rails::Railtie)
|
|
@@ -14,9 +14,12 @@ require "inertia_jb/railtie" if defined?(Rails::Railtie)
|
|
|
14
14
|
# frontend components inside view templates, using plain Ruby Hashes powered by
|
|
15
15
|
# the {https://github.com/amatsuda/jb jb} renderer.
|
|
16
16
|
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
17
|
+
# `:inertia` is registered as a real Rails template format, so a `*.inertia`
|
|
18
|
+
# template is rendered by the ordinary Rails pipeline (and never wrapped in a
|
|
19
|
+
# layout). Its return value is a {Props} object carrying the template's last
|
|
20
|
+
# expression — a Hash of props — which {Controller#render_to_body} hands to
|
|
21
|
+
# inertia-rails' `:inertia` renderer. Every Inertia protocol feature (partial
|
|
22
|
+
# reloads, optional/always/deferred props, merging, shared data, key transforms)
|
|
23
|
+
# therefore works out of the box.
|
|
21
24
|
module InertiaJb
|
|
22
25
|
end
|
data/test/controller_test.rb
CHANGED
|
@@ -5,8 +5,6 @@ require "action_view/testing/resolvers"
|
|
|
5
5
|
require "json"
|
|
6
6
|
|
|
7
7
|
class TestController < ActionController::Base
|
|
8
|
-
include InertiaJb::Controller
|
|
9
|
-
|
|
10
8
|
layout "application"
|
|
11
9
|
|
|
12
10
|
# Shared props are merged in automatically by inertia-rails.
|
|
@@ -49,23 +47,23 @@ class ControllerTest < ActionController::TestCase
|
|
|
49
47
|
|
|
50
48
|
TEMPLATES = {
|
|
51
49
|
"layouts/application.html.erb" => "<html><body><%= yield %></body></html>",
|
|
52
|
-
"test/index.
|
|
53
|
-
"test/nested.
|
|
54
|
-
"test/collection.
|
|
55
|
-
"test/with_partial.
|
|
50
|
+
"test/index.inertia" => "{ content: 'content' }",
|
|
51
|
+
"test/nested.inertia" => "{ product: { id: 10, name: 'Keyboard', price: 29.99 } }",
|
|
52
|
+
"test/collection.inertia" => "{ products: @products.map { |p| { id: p[:id], name: p[:name] } } }",
|
|
53
|
+
"test/with_partial.inertia" =>
|
|
56
54
|
"{ author: render(partial: 'authors/author', object: @author), " \
|
|
57
55
|
"comments: render(partial: 'comments/comment', collection: @comments) }",
|
|
58
56
|
"authors/_author.html.jb" => "{ id: author[:id], name: author[:name] }",
|
|
59
57
|
"comments/_comment.html.jb" => "{ body: comment[:body] }",
|
|
60
|
-
"test/special.
|
|
58
|
+
"test/special.inertia" =>
|
|
61
59
|
"{ id: @id, stats: optional { { visits: 42 } }, feed: defer(group: :feed) { [1, 2, 3] } }",
|
|
62
|
-
"test/cached.
|
|
60
|
+
"test/cached.inertia" =>
|
|
63
61
|
"{ flash: once { 'hello' }, " \
|
|
64
62
|
"report: cache('report', expires_in: 1.minute) { Time.current.to_f } }",
|
|
65
63
|
# A deferred/optional prop whose block renders a jb partial. The block is
|
|
66
64
|
# resolved by inertia-rails in controller context, so a bare `render` there
|
|
67
65
|
# would set response_body and collide with inertia's own render(json:).
|
|
68
|
-
"test/lazy_partial.
|
|
66
|
+
"test/lazy_partial.inertia" =>
|
|
69
67
|
"{ deferredAuthor: defer { render(partial: 'authors/author', object: @author) }, " \
|
|
70
68
|
"optionalComments: optional { render(partial: 'comments/comment', collection: @comments) } }"
|
|
71
69
|
}.freeze
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
require "test_helper"
|
|
4
4
|
require "action_view/testing/resolvers"
|
|
5
5
|
|
|
6
|
-
# Regression test
|
|
6
|
+
# Regression test: an `.inertia` page must never be wrapped in — or even resolve
|
|
7
|
+
# — the controller's layout.
|
|
7
8
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# that
|
|
9
|
+
# This falls out of `:inertia` being a real template format: layout lookup runs
|
|
10
|
+
# against `formats.first` (`:inertia`), which `layouts/*.html.erb` cannot match.
|
|
11
|
+
# Nothing patches Rails to make it happen, so the guarantee is worth pinning:
|
|
12
|
+
# were the layout resolved, request logs and APM traces would show a layout
|
|
13
|
+
# render that contributes nothing to the response, and its timing would absorb
|
|
14
|
+
# the prop-building work.
|
|
13
15
|
#
|
|
14
16
|
# The controller here uses `layout "application"` but points Inertia at a
|
|
15
17
|
# *different* shell (`config.layout = "inertia"`), so the two are easy to tell
|
|
@@ -17,7 +19,6 @@ require "action_view/testing/resolvers"
|
|
|
17
19
|
# * the `.inertia` props template must be rendered with NO layout, and
|
|
18
20
|
# * `layouts/application` must never be rendered at all.
|
|
19
21
|
class SuppressLayoutController < ActionController::Base
|
|
20
|
-
include InertiaJb::Controller
|
|
21
22
|
layout "application"
|
|
22
23
|
inertia_config layout: "inertia"
|
|
23
24
|
def index; end
|
|
@@ -29,7 +30,7 @@ class LayoutSuppressionTest < ActionController::TestCase
|
|
|
29
30
|
TEMPLATES = {
|
|
30
31
|
"layouts/application.html.erb" => "<html><body><%= yield %></body></html>",
|
|
31
32
|
"layouts/inertia.html.erb" => "<main data-inertia-shell=\"1\"><%= yield %></main>",
|
|
32
|
-
"suppress_layout/index.
|
|
33
|
+
"suppress_layout/index.inertia" => "{ ok: true }"
|
|
33
34
|
}.freeze
|
|
34
35
|
|
|
35
36
|
def setup
|
|
@@ -47,7 +48,7 @@ class LayoutSuppressionTest < ActionController::TestCase
|
|
|
47
48
|
def test_inertia_template_is_rendered_without_a_layout
|
|
48
49
|
templates = capture("render_template.action_view") { get :index }
|
|
49
50
|
|
|
50
|
-
page = templates.find { |e| e.payload[:identifier].end_with?("index.
|
|
51
|
+
page = templates.find { |e| e.payload[:identifier].end_with?("index.inertia") }
|
|
51
52
|
assert page, "the .inertia props template should have been rendered"
|
|
52
53
|
assert_nil page.payload[:layout],
|
|
53
54
|
"the .inertia props template must be rendered without a layout, " \
|
data/test/layout_test.rb
CHANGED
|
@@ -9,21 +9,18 @@ require "json"
|
|
|
9
9
|
# case gets its own controller and no global state is mutated.
|
|
10
10
|
|
|
11
11
|
class LayoutDefaultController < ActionController::Base
|
|
12
|
-
include InertiaJb::Controller
|
|
13
12
|
layout "application"
|
|
14
13
|
# No `inertia_config` — `config.layout` defaults to `true`.
|
|
15
14
|
def index; end
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
class LayoutFalseController < ActionController::Base
|
|
19
|
-
include InertiaJb::Controller
|
|
20
18
|
layout "application"
|
|
21
19
|
inertia_config layout: false
|
|
22
20
|
def index; end
|
|
23
21
|
end
|
|
24
22
|
|
|
25
23
|
class LayoutNamedController < ActionController::Base
|
|
26
|
-
include InertiaJb::Controller
|
|
27
24
|
layout "application"
|
|
28
25
|
inertia_config layout: "inertia"
|
|
29
26
|
def index; end
|
|
@@ -32,7 +29,6 @@ end
|
|
|
32
29
|
# A plain (non-Inertia) HTML action living in an app that configured
|
|
33
30
|
# `config.layout = false`. Its layout must behave like vanilla Rails.
|
|
34
31
|
class PlainController < ActionController::Base
|
|
35
|
-
include InertiaJb::Controller
|
|
36
32
|
layout "application"
|
|
37
33
|
inertia_config layout: false
|
|
38
34
|
def index; end
|
|
@@ -42,9 +38,9 @@ module LayoutTestHelpers
|
|
|
42
38
|
TEMPLATES = {
|
|
43
39
|
"layouts/application.html.erb" => "<html><body><%= yield %></body></html>",
|
|
44
40
|
"layouts/inertia.html.erb" => "<main data-inertia-layout=\"1\"><%= yield %></main>",
|
|
45
|
-
"layout_default/index.
|
|
46
|
-
"layout_false/index.
|
|
47
|
-
"layout_named/index.
|
|
41
|
+
"layout_default/index.inertia" => "{ ok: true }",
|
|
42
|
+
"layout_false/index.inertia" => "{ ok: true }",
|
|
43
|
+
"layout_named/index.inertia" => "{ ok: true }",
|
|
48
44
|
"plain/index.html.erb" => "plain-body-<%= 1 + 1 %>"
|
|
49
45
|
}.freeze
|
|
50
46
|
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
require "action_view/testing/resolvers"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
# The hand-off from template to renderer keys on the *rendered result*, not on
|
|
8
|
+
# `default_render`, so explicit renders are first-class. These tests pin that
|
|
9
|
+
# down, plus the two boundaries of the `:inertia` format: it must take priority
|
|
10
|
+
# over inertia-rails' `config.default_render`, and it must not leak into other
|
|
11
|
+
# formats' template lookup.
|
|
12
|
+
|
|
13
|
+
class RenderController < ActionController::Base
|
|
14
|
+
layout "application"
|
|
15
|
+
|
|
16
|
+
def implicit; end
|
|
17
|
+
|
|
18
|
+
# The Rails idiom after a failed create: re-render another action's template.
|
|
19
|
+
# (422 spelled numerically — `:unprocessable_entity` is deprecated in Rack 3.2
|
|
20
|
+
# while `:unprocessable_content` doesn't exist on older Rack.)
|
|
21
|
+
def create
|
|
22
|
+
render :implicit, status: 422
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def cross_template
|
|
26
|
+
render template: "other/thing"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# A page template whose whole body is a bare `render(partial:)` — jb hands
|
|
30
|
+
# back a Jb::TemplateResult, which reaches inertia-rails as `props:` and so
|
|
31
|
+
# needs no `{ **... }` wrapping.
|
|
32
|
+
def bare_partial
|
|
33
|
+
@author = { id: 42, name: "John" }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# A plain JSON endpoint next door: `.json.jb` must keep serializing to JSON.
|
|
37
|
+
def api; end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# inertia-rails' `config.default_render` short-circuits `default_render` into
|
|
41
|
+
# `render inertia: true`, which is why installing this gem asks for it to be off.
|
|
42
|
+
class DefaultRenderController < ActionController::Base
|
|
43
|
+
layout "application"
|
|
44
|
+
inertia_config default_render: true
|
|
45
|
+
|
|
46
|
+
def with_template; end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# A template still carrying the pre-0.4 name.
|
|
50
|
+
class LegacyNameController < ActionController::Base
|
|
51
|
+
def index; end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The component name is always produced by inertia-rails' resolver, never
|
|
55
|
+
# assembled by this gem.
|
|
56
|
+
class CustomResolverController < ActionController::Base
|
|
57
|
+
inertia_config component_path_resolver: ->(path:, action:) { "#{path.camelize}/#{action.camelize}" }
|
|
58
|
+
|
|
59
|
+
def index; end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
module RenderingTestHelpers
|
|
63
|
+
TEMPLATES = {
|
|
64
|
+
"layouts/application.html.erb" => "<html><body><%= yield %></body></html>",
|
|
65
|
+
"render/implicit.inertia" => "{ page: 'implicit' }",
|
|
66
|
+
"render/cross_template.inertia" => "{ page: 'never rendered' }",
|
|
67
|
+
"other/thing.inertia" => "{ page: 'other' }",
|
|
68
|
+
"render/bare_partial.inertia" => "render(partial: 'authors/author', object: @author)",
|
|
69
|
+
"authors/_author.html.jb" => "{ id: author[:id], name: author[:name] }",
|
|
70
|
+
"render/api.json.jb" => "{ ok: true }",
|
|
71
|
+
"default_render/with_template.inertia" => "{ from: 'template' }",
|
|
72
|
+
"legacy_name/index.html.inertia" => "{ page: 'legacy' }",
|
|
73
|
+
"custom_resolver/index.inertia" => "{ ok: true }"
|
|
74
|
+
}.freeze
|
|
75
|
+
|
|
76
|
+
def setup
|
|
77
|
+
super
|
|
78
|
+
@controller.prepend_view_path(ActionView::FixtureResolver.new(TEMPLATES))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def teardown
|
|
82
|
+
super
|
|
83
|
+
@routes.clear!
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def draw(*actions)
|
|
89
|
+
controller_path = @controller.class.controller_path
|
|
90
|
+
@routes = ActionDispatch::Routing::RouteSet.new
|
|
91
|
+
@routes.draw do
|
|
92
|
+
actions.each { |action| get action.to_s => "#{controller_path}##{action}" }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def inertia_get(action, **kwargs)
|
|
97
|
+
@request.headers["X-Inertia"] = "true"
|
|
98
|
+
get action, **kwargs
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def page
|
|
102
|
+
JSON.parse(response.body)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class ExplicitRenderTest < ActionController::TestCase
|
|
107
|
+
include RenderingTestHelpers
|
|
108
|
+
tests RenderController
|
|
109
|
+
|
|
110
|
+
def setup
|
|
111
|
+
super
|
|
112
|
+
draw :implicit, :create, :cross_template, :bare_partial, :api
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_implicit_render_uses_the_template_component
|
|
116
|
+
inertia_get :implicit
|
|
117
|
+
|
|
118
|
+
assert_response :success
|
|
119
|
+
assert_equal "application/json; charset=utf-8", response.content_type
|
|
120
|
+
assert_equal "render/implicit", page["component"]
|
|
121
|
+
assert_equal "implicit", page.dig("props", "page")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_full_page_load_is_html
|
|
125
|
+
get :implicit
|
|
126
|
+
|
|
127
|
+
assert_response :success
|
|
128
|
+
assert_equal "text/html; charset=utf-8", response.content_type
|
|
129
|
+
assert_includes response.body, "<html><body>"
|
|
130
|
+
assert_includes response.body, "data-page"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The old throw/catch design raised UncaughtThrowError here.
|
|
134
|
+
def test_explicit_render_of_another_action_keeps_status_and_component
|
|
135
|
+
inertia_get :create
|
|
136
|
+
|
|
137
|
+
assert_response 422
|
|
138
|
+
assert_equal "render/implicit", page["component"],
|
|
139
|
+
"the component must follow the rendered template, not the acting action"
|
|
140
|
+
assert_equal "implicit", page.dig("props", "page")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def test_explicit_render_template_across_directories
|
|
144
|
+
inertia_get :cross_template
|
|
145
|
+
|
|
146
|
+
assert_response :success
|
|
147
|
+
assert_equal "other/thing", page["component"]
|
|
148
|
+
assert_equal "other", page.dig("props", "page")
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def test_page_template_may_be_a_bare_partial_render
|
|
152
|
+
inertia_get :bare_partial
|
|
153
|
+
|
|
154
|
+
assert_response :success
|
|
155
|
+
assert_equal({ "id" => 42, "name" => "John" }, page["props"].except("errors"))
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# `:inertia` is a template format, not a wire format: it must not shadow the
|
|
159
|
+
# `.json.jb` lookup for a plain API request.
|
|
160
|
+
def test_json_endpoint_on_the_same_controller_is_untouched
|
|
161
|
+
get :api, format: :json
|
|
162
|
+
|
|
163
|
+
assert_response :success
|
|
164
|
+
assert_equal "application/json; charset=utf-8", response.content_type
|
|
165
|
+
assert_nil response.headers["X-Inertia"]
|
|
166
|
+
assert_equal({ "ok" => true }, page)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Pins the reason the installation instructions ask for
|
|
171
|
+
# `config.default_render = false`: with it on, inertia-rails answers
|
|
172
|
+
# `default_render` itself and the `.inertia` template is never reached.
|
|
173
|
+
class DefaultRenderTest < ActionController::TestCase
|
|
174
|
+
include RenderingTestHelpers
|
|
175
|
+
tests DefaultRenderController
|
|
176
|
+
|
|
177
|
+
def setup
|
|
178
|
+
super
|
|
179
|
+
draw :with_template
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def test_config_default_render_true_short_circuits_before_the_template
|
|
183
|
+
inertia_get :with_template
|
|
184
|
+
|
|
185
|
+
assert_response :success
|
|
186
|
+
assert_equal "default_render/with_template", page["component"]
|
|
187
|
+
refute page["props"].key?("from"),
|
|
188
|
+
"config.default_render = true renders `inertia: true` without consulting " \
|
|
189
|
+
"the template — hence the initializer step"
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
class InertiaMimeTypeTest < ActiveSupport::TestCase
|
|
194
|
+
def test_inertia_is_a_valid_template_format
|
|
195
|
+
assert Mime[:inertia], "Mime[:inertia] must exist for `lookup_context.formats` to accept it"
|
|
196
|
+
assert_includes ActionView::Template::Types.symbols, :inertia
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Registered as an *alias* of text/html: `Mime::Type.register` would repoint
|
|
200
|
+
# `LOOKUP["text/html"]` at :inertia and break HTML negotiation app-wide.
|
|
201
|
+
def test_inertia_does_not_take_part_in_content_negotiation
|
|
202
|
+
assert_equal :html, Mime::Type.lookup("text/html").symbol
|
|
203
|
+
assert_equal [Mime[:html]], Mime::Type.parse("text/html")
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
class CustomComponentResolverTest < ActionController::TestCase
|
|
208
|
+
include RenderingTestHelpers
|
|
209
|
+
tests CustomResolverController
|
|
210
|
+
|
|
211
|
+
def setup
|
|
212
|
+
super
|
|
213
|
+
draw :index
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def test_component_name_goes_through_config_component_path_resolver
|
|
217
|
+
inertia_get :index
|
|
218
|
+
|
|
219
|
+
assert_response :success
|
|
220
|
+
assert_equal "CustomResolver/Index", page["component"]
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
class LegacyTemplateNameTest < ActionController::TestCase
|
|
225
|
+
include RenderingTestHelpers
|
|
226
|
+
tests LegacyNameController
|
|
227
|
+
|
|
228
|
+
def setup
|
|
229
|
+
super
|
|
230
|
+
draw :index
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def test_html_inertia_template_reports_the_rename
|
|
234
|
+
error = assert_raises(ActionView::Template::Error) { get :index }
|
|
235
|
+
|
|
236
|
+
assert_includes error.message, "`<action>.inertia`"
|
|
237
|
+
end
|
|
238
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: inertia_jb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kikyous
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: inertia_rails
|
|
@@ -81,12 +81,13 @@ files:
|
|
|
81
81
|
- lib/inertia_jb/controller.rb
|
|
82
82
|
- lib/inertia_jb/handler.rb
|
|
83
83
|
- lib/inertia_jb/helper.rb
|
|
84
|
+
- lib/inertia_jb/props.rb
|
|
84
85
|
- lib/inertia_jb/railtie.rb
|
|
85
|
-
- lib/inertia_jb/renderer.rb
|
|
86
86
|
- lib/inertia_jb/version.rb
|
|
87
87
|
- test/controller_test.rb
|
|
88
88
|
- test/layout_suppression_test.rb
|
|
89
89
|
- test/layout_test.rb
|
|
90
|
+
- test/rendering_test.rb
|
|
90
91
|
- test/test_helper.rb
|
|
91
92
|
homepage: https://github.com/kikyous/inertia_jb
|
|
92
93
|
licenses:
|
|
@@ -119,4 +120,5 @@ test_files:
|
|
|
119
120
|
- test/controller_test.rb
|
|
120
121
|
- test/layout_suppression_test.rb
|
|
121
122
|
- test/layout_test.rb
|
|
123
|
+
- test/rendering_test.rb
|
|
122
124
|
- test/test_helper.rb
|
data/lib/inertia_jb/renderer.rb
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module InertiaJb
|
|
4
|
-
# Prepended onto +ActionView::TemplateRenderer+ to guarantee that a
|
|
5
|
-
# `.html.inertia` template is never wrapped in a layout.
|
|
6
|
-
#
|
|
7
|
-
# An `.inertia` template's body (see {Handler}) throws its props Hash back to
|
|
8
|
-
# the controller before it produces any output, so a surrounding layout is
|
|
9
|
-
# always discarded. Left alone, Rails would still resolve and compile the
|
|
10
|
-
# controller's layout and emit phantom `render_layout.action_view` /
|
|
11
|
-
# `render_template.action_view` events for it — their `ensure` blocks fire as
|
|
12
|
-
# the `throw` unwinds the stack — whose timing absorbs the prop-building work,
|
|
13
|
-
# polluting request logs and APM traces with a layout render that never
|
|
14
|
-
# actually happened.
|
|
15
|
-
#
|
|
16
|
-
# +render_template+ is the one point where Rails has already resolved the
|
|
17
|
-
# template (so we can see its handler) but has not yet looked up the layout
|
|
18
|
-
# (`find_layout` runs one call deeper, in +render_with_layout+). Nulling the
|
|
19
|
-
# layout here for our handler skips the lookup entirely — cleanly, without the
|
|
20
|
-
# controller having to guess in advance which template an action will render.
|
|
21
|
-
#
|
|
22
|
-
# This only affects our own templates. The real Inertia shell is applied later
|
|
23
|
-
# by inertia-rails when it renders the `inertia` root template (an ERB
|
|
24
|
-
# template) with `config.layout`, and plain `.erb`/`.jb` templates keep their
|
|
25
|
-
# layouts exactly as in vanilla Rails.
|
|
26
|
-
module Renderer
|
|
27
|
-
private
|
|
28
|
-
|
|
29
|
-
def render_template(view, template, layout_name, locals)
|
|
30
|
-
layout_name = nil if template.handler == InertiaJb::Handler
|
|
31
|
-
super
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|