inertia_jb 0.3.0 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c821eb8ccc74579d17f768744858bb9a14f7838201dd2ff89be61f8f70298780
4
- data.tar.gz: 4e385aefbe3adcdcd6069b422382cd91cc7cbe208f2275230433110a761a4d3d
3
+ metadata.gz: 37e2ba2d8b4c896a4a4f7f006aa32cbee162cc7b798a77e78daab8a305512c65
4
+ data.tar.gz: 7d1f22f9ba1596965f7d8fc52c7ccc3f7f7aae78d3a1093464b77791ed858a27
5
5
  SHA512:
6
- metadata.gz: fb985df39ed4ec250604244d93c968884b8d4a47588e031126d48f5b9489e5015192fc021de81c4b4e4d06018714065ed757046ec9728ed7804f930f073dd95e
7
- data.tar.gz: d3e94480c54b50667a66c9416a59d6124e410b6d5da165e3344137128c9b2b88b111e07897ffccc0b56a915c955e060a0c103129813d476a99e085af337f2ccf
6
+ metadata.gz: 69f29e30c55a3efe39d54695948fec6de98ef96cf1a788dcb7806fb05186bc6e04f3f92cdb4ae5d175457489b042a1a3d5c8e692df2fdb07d7559d22f13edea3
7
+ data.tar.gz: c666ba929d8d67a7daaed36b39985ac9642b546d182dbccf30abc0281b5c115daef7f395157bbee6a0e35943c29cdb5b2ce84d07a528993d6ee528db0e269eea
data/README.md CHANGED
@@ -90,7 +90,7 @@ On an **initial (non-XHR) page load** the `data-page` root element is wrapped in
90
90
  a layout; Inertia (XHR) visits always return a bare JSON body with no layout.
91
91
 
92
92
  The layout is chosen from inertia-rails' `config.layout`, matching
93
- `InertiaRails::Renderer`'s own semantics:
93
+ `InertiaRails::Renderer`'s own semantics.
94
94
 
95
95
 
96
96
  ## Templates and partials
@@ -4,49 +4,78 @@ module InertiaJb
4
4
  # Optional syntax sugar available inside `.html.inertia` templates, so you can
5
5
  # write `optional { ... }` instead of `InertiaRails.optional { ... }`.
6
6
  #
7
- # These simply delegate to the +InertiaRails+ factory methods; the returned
8
- # prop objects are resolved by +InertiaRails::PropsResolver+ when the page is
9
- # built (respecting partial reloads, grouping, etc.).
7
+ # These delegate to the +InertiaRails+ factory methods; the returned prop
8
+ # objects are resolved by +InertiaRails::PropsResolver+ when the page is built
9
+ # (respecting partial reloads, grouping, etc.).
10
+ #
11
+ # Every block is bound to the current view with {InertiaJb.bind_to_view} so
12
+ # that a lazy prop's block runs in the *same* context an eager prop would —
13
+ # see that method for why this matters.
10
14
  module Helper
11
15
  # Only fetched when explicitly requested in a partial reload.
12
16
  def optional(&block)
13
- ::InertiaRails.optional(&block)
17
+ ::InertiaRails.optional(&InertiaJb.bind_to_view(self, block))
14
18
  end
15
19
 
16
20
  # Always fetched, even when not requested in a partial reload.
17
21
  def always(&block)
18
- ::InertiaRails.always(&block)
22
+ ::InertiaRails.always(&InertiaJb.bind_to_view(self, block))
19
23
  end
20
24
 
21
25
  # Excluded from the initial load; fetched in a follow-up request.
22
- def defer(...)
23
- ::InertiaRails.defer(...)
26
+ def defer(*args, **kwargs, &block)
27
+ ::InertiaRails.defer(*args, **kwargs, &InertiaJb.bind_to_view(self, block))
24
28
  end
25
29
 
26
30
  # Infinite-scroll prop (accepts a paginator or explicit metadata).
27
- def scroll(...)
28
- ::InertiaRails.scroll(...)
31
+ def scroll(*args, **kwargs, &block)
32
+ ::InertiaRails.scroll(*args, **kwargs, &InertiaJb.bind_to_view(self, block))
29
33
  end
30
34
 
31
35
  # Merged with existing client-side data instead of replacing it.
32
- def merge(...)
33
- ::InertiaRails.merge(...)
36
+ def merge(*args, **kwargs, &block)
37
+ ::InertiaRails.merge(*args, **kwargs, &InertiaJb.bind_to_view(self, block))
34
38
  end
35
39
 
36
40
  # Deep-merged with existing client-side data.
37
- def deep_merge(...)
38
- ::InertiaRails.deep_merge(...)
41
+ def deep_merge(*args, **kwargs, &block)
42
+ ::InertiaRails.deep_merge(*args, **kwargs, &InertiaJb.bind_to_view(self, block))
39
43
  end
40
44
 
41
45
  # Sent once and cached client-side; skipped on later visits unless reset or expired.
42
- def once(...)
43
- ::InertiaRails.once(...)
46
+ def once(*args, **kwargs, &block)
47
+ ::InertiaRails.once(*args, **kwargs, &InertiaJb.bind_to_view(self, block))
44
48
  end
45
49
 
46
50
  # Server-side cached via Rails.cache; the block's result is stored as JSON
47
51
  # and reused on subsequent requests until the cache expires.
48
- def cache(...)
49
- ::InertiaRails.cache(...)
52
+ def cache(*args, **kwargs, &block)
53
+ ::InertiaRails.cache(*args, **kwargs, &InertiaJb.bind_to_view(self, block))
50
54
  end
51
55
  end
56
+
57
+ # Binds a lazy-prop block to the view that declared it, so the block evaluates
58
+ # in the same view context as an eager prop.
59
+ #
60
+ # Eager props are evaluated while the +.inertia+ template renders, so their
61
+ # +render(partial: ...)+ is the *view* helper — it returns jb data and never
62
+ # touches +response_body+. Lazy props (+defer+/+optional+/+always+/...) are
63
+ # different: inertia-rails resolves them later via
64
+ # +controller.instance_exec(&block)+, i.e. in *controller* context, where the
65
+ # same +render(partial: ...)+ binds to +ActionController+'s +render+ and
66
+ # assigns +response_body+. inertia-rails then performs its own
67
+ # +render(json:)+, and the already-set +response_body+ raises
68
+ # +AbstractController::DoubleRenderError+.
69
+ #
70
+ # Wrapping the block in +view.instance_exec+ keeps every block — eager or
71
+ # lazy — running against the view, so +render+ behaves identically everywhere.
72
+ # Controller-exposed helpers still work in the view (e.g. +policy_scope+,
73
+ # +current_user+ via Pundit/Devise +helper_method+s); the only controller-only
74
+ # call that does not carry over is +render_to_string+ — use +render(partial:,
75
+ # formats: [:html])+ instead, which returns the same String in a view.
76
+ def self.bind_to_view(view, block)
77
+ return block if block.nil?
78
+
79
+ proc { view.instance_exec(&block) }
80
+ end
52
81
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InertiaJb
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.1"
5
5
  end
@@ -34,6 +34,12 @@ class TestController < ActionController::Base
34
34
  def cached
35
35
  @call_count = 0
36
36
  end
37
+
38
+ # lazy props (defer/optional) whose block renders a jb partial.
39
+ def lazy_partial
40
+ @author = { id: 42, name: "John" }
41
+ @comments = [{ body: "first" }, { body: "second" }]
42
+ end
37
43
  end
38
44
 
39
45
  class ControllerTest < ActionController::TestCase
@@ -55,7 +61,13 @@ class ControllerTest < ActionController::TestCase
55
61
  "{ id: @id, stats: optional { { visits: 42 } }, feed: defer(group: :feed) { [1, 2, 3] } }",
56
62
  "test/cached.html.inertia" =>
57
63
  "{ flash: once { 'hello' }, " \
58
- "report: cache('report', expires_in: 1.minute) { Time.current.to_f } }"
64
+ "report: cache('report', expires_in: 1.minute) { Time.current.to_f } }",
65
+ # A deferred/optional prop whose block renders a jb partial. The block is
66
+ # resolved by inertia-rails in controller context, so a bare `render` there
67
+ # would set response_body and collide with inertia's own render(json:).
68
+ "test/lazy_partial.html.inertia" =>
69
+ "{ deferredAuthor: defer { render(partial: 'authors/author', object: @author) }, " \
70
+ "optionalComments: optional { render(partial: 'comments/comment', collection: @comments) } }"
59
71
  }.freeze
60
72
 
61
73
  def setup
@@ -63,7 +75,7 @@ class ControllerTest < ActionController::TestCase
63
75
 
64
76
  @routes = ActionDispatch::Routing::RouteSet.new
65
77
  @routes.draw do
66
- %i[index nested collection with_partial special cached].each do |action|
78
+ %i[index nested collection with_partial special cached lazy_partial].each do |action|
67
79
  get action.to_s => "test##{action}"
68
80
  end
69
81
  end
@@ -190,6 +202,35 @@ class ControllerTest < ActionController::TestCase
190
202
  refute page["props"].key?("flash"), "once prop should be absent when client reports it cached"
191
203
  end
192
204
 
205
+ # ---- lazy props that render jb partials ---------------------------------
206
+
207
+ def test_deferred_prop_rendering_jb_partial_does_not_double_render
208
+ inertia_get :lazy_partial,
209
+ headers: {
210
+ "X-Inertia-Partial-Component" => "test/lazy_partial",
211
+ "X-Inertia-Partial-Data" => "deferredAuthor"
212
+ }
213
+
214
+ assert_response :success
215
+ page = JSON.parse(response.body)
216
+ assert_equal({ "id" => 42, "name" => "John" }, page.dig("props", "deferredAuthor"))
217
+ end
218
+
219
+ def test_optional_prop_rendering_jb_partial_does_not_double_render
220
+ inertia_get :lazy_partial,
221
+ headers: {
222
+ "X-Inertia-Partial-Component" => "test/lazy_partial",
223
+ "X-Inertia-Partial-Data" => "optionalComments"
224
+ }
225
+
226
+ assert_response :success
227
+ page = JSON.parse(response.body)
228
+ assert_equal(
229
+ [{ "body" => "first" }, { "body" => "second" }],
230
+ page.dig("props", "optionalComments")
231
+ )
232
+ end
233
+
193
234
  # ---- cached props (server-cached) ---------------------------------------
194
235
 
195
236
  def test_cached_prop_serves_same_value_across_requests
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.3.0
4
+ version: 0.3.1
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-24 00:00:00.000000000 Z
11
+ date: 2026-07-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inertia_rails