inertia_rails 3.20.0 → 3.21.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e56b13d7d02abe1f35709de08b6873cc0d407215a97891502bb4ee7439e0d005
4
- data.tar.gz: a627501dd94ab5d9ad747c2decce31ab47fe4870e677d2197395680cb4dfaa3e
3
+ metadata.gz: 8e115fc23929dd31823522f220656a19369a8f5532215bf2c36a2f01e038ca20
4
+ data.tar.gz: 81bf7c11c9fb32be96de1d70e212894057bbb16c49ce87c76ea17071fe9635e5
5
5
  SHA512:
6
- metadata.gz: 5f9b35e5039a9516daaa7ff6d9c1b702011d3ddd2161fb40922143fde7951522ca3e8268691d2b3490baae217febb046b04d07d677facf39b81dad9f1f5bd076
7
- data.tar.gz: 3523dadfe1c2877767cf534b4a6c95ef979250bbf3c78a86af31e6220247cb860d020402b359a32c8b5295b1d246aa30d8a5a590dd8fae44de89623b24521880
6
+ metadata.gz: 80d695972717a9ad929c580bffb339102b19cb0890b1f235d3b2076b0b8094d7d8f5ce67de1d9b18ebe440dc03982be37371d522d7168fb8be447fa3e7b15fe0
7
+ data.tar.gz: dac3f813eecdc130f9b2081f084122c9783cb3d74793847c8f2ca1f9c95d080159594ccf1e015f05c4b1848faccd53fa069c4f97694614fb5ea96ac7b54765b2
data/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [Unreleased]
8
+
9
+ ## [3.21.0] - 2026-04-14
10
+
11
+ * Skip excluded hash props on partial reloads (@erickreutz)
12
+ * Fix Vite plugin insertion order in install generator (@onk)
13
+ * Fix typo in `inertia()` plugin configuration example in docs (@onk)
14
+ * Better SSR Puma plugin lookup defaults (@skryukov)
15
+ * Add prop-level caching and caching documentation (@skryukov)
16
+ * Skip session cleanup in middleware when session was never loaded (@khamusa)
17
+ * Support `layout` option in `render inertia:` calls (@skryukov)
18
+
7
19
  ## [3.20.0] - 2026-04-04
8
20
 
9
21
  * Fix partial reload filtering with arrays
@@ -87,18 +87,21 @@ module Inertia
87
87
  say 'Installing Inertia npm packages'
88
88
  add_dependencies(inertia_package, *FRAMEWORKS[framework]['packages'])
89
89
 
90
- unless File.read(vite_config_path).include?(FRAMEWORKS[framework]['vite_plugin_import'])
91
- say "Adding Vite plugin for #{framework}"
92
- insert_into_file vite_config_path, "\n #{FRAMEWORKS[framework]['vite_plugin_call']},", after: 'plugins: ['
93
- prepend_file vite_config_path, "#{FRAMEWORKS[framework]['vite_plugin_import']}\n"
94
- end
95
-
96
90
  unless File.read(vite_config_path).include?('@inertiajs/vite')
97
91
  say 'Adding Inertia Vite plugin'
98
- insert_into_file vite_config_path, "\n inertia(),", after: 'plugins: ['
92
+ # Append to the end of the plugins array.
93
+ gsub_file vite_config_path, /(plugins: \[.*?)(\n\s*\])/m, "\\1\n inertia(),\\2"
99
94
  prepend_file vite_config_path, "import inertia from '@inertiajs/vite'\n"
100
95
  end
101
96
 
97
+ unless File.read(vite_config_path).include?(FRAMEWORKS[framework]['vite_plugin_import'])
98
+ say "Adding Vite plugin for #{framework}"
99
+ # Append to the end of the plugins array.
100
+ gsub_file vite_config_path, /(plugins: \[.*?)(\n\s*\])/m,
101
+ "\\1\n #{FRAMEWORKS[framework]['vite_plugin_call']},\\2"
102
+ prepend_file vite_config_path, "#{FRAMEWORKS[framework]['vite_plugin_import']}\n"
103
+ end
104
+
102
105
  say "Copying #{inertia_entrypoint} entrypoint"
103
106
  copy_file "#{framework}/#{inertia_entrypoint}", js_file_path("entrypoints/#{inertia_entrypoint}")
104
107
 
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InertiaRails
4
+ class CachedProp < BaseProp
5
+ include PropCacheable
6
+
7
+ def initialize(key, **options, &block)
8
+ super(cache: options.merge!(key: key), &block)
9
+ end
10
+ end
11
+ end
@@ -71,6 +71,10 @@ module InertiaRails
71
71
 
72
72
  # Whether to include shared prop keys in the page response metadata.
73
73
  expose_shared_prop_keys: true,
74
+
75
+ # Cache store for prop-level caching and SSR response caching.
76
+ # Defaults to Rails.cache when nil.
77
+ cache_store: nil,
74
78
  }.freeze
75
79
 
76
80
  OPTION_NAMES = DEFAULTS.keys.freeze
@@ -141,6 +145,10 @@ module InertiaRails
141
145
  @options[:on_ssr_error]
142
146
  end
143
147
 
148
+ def cache_store
149
+ @options[:cache_store] || Rails.cache
150
+ end
151
+
144
152
  OPTION_NAMES.each do |option|
145
153
  unless method_defined?(option)
146
154
  define_method(option) do
@@ -112,6 +112,14 @@ module InertiaRails
112
112
  view_assigns.except(*@_inertia_skip_props)
113
113
  end
114
114
 
115
+ # Rails < 8: _normalize_options overwrites :layout with a resolved default,
116
+ # making an explicit `layout: false` indistinguishable from "not provided".
117
+ # Stash the original value so the renderer can tell the two apart.
118
+ def _normalize_options(options)
119
+ options[:_inertia_layout] = options[:layout] if options.key?(:inertia) && options.key?(:layout)
120
+ super
121
+ end
122
+
115
123
  def inertia_configuration
116
124
  self.class._inertia_configuration.bind_controller(self)
117
125
  end
@@ -4,6 +4,7 @@ module InertiaRails
4
4
  class DeferProp < IgnoreOnFirstLoadProp
5
5
  prepend PropOnceable
6
6
  prepend PropMergeable
7
+ prepend PropCacheable
7
8
 
8
9
  DEFAULT_GROUP = 'default'
9
10
 
@@ -15,6 +15,14 @@ module InertiaRails
15
15
  initializer 'inertia_rails.renderer' do
16
16
  ActiveSupport.on_load(:action_controller) do
17
17
  ActionController::Renderers.add :inertia do |component, options|
18
+ # See Controller#_normalize_options — restore the user's original
19
+ # :layout or discard the Rails-injected default.
20
+ if options.key?(:_inertia_layout)
21
+ options[:layout] = options.delete(:_inertia_layout)
22
+ else
23
+ options.delete(:layout)
24
+ end
25
+
18
26
  InertiaRails::Renderer.new(
19
27
  component,
20
28
  self,
@@ -26,7 +26,11 @@ module InertiaRails
26
26
  request = ActionDispatch::Request.new(@env)
27
27
 
28
28
  # Inertia session data is added via redirect_to
29
- unless keep_inertia_session_options?(status)
29
+ # Guard with session.loaded? to avoid forcing session I/O (and unnecessary
30
+ # database writes) on requests that never accessed the session, e.g. sessionless
31
+ # controllers. If the session was never loaded the Inertia keys cannot have been
32
+ # set, so the cleanup would be a no-op anyway.
33
+ unless keep_inertia_session_options?(status) || !request.session.loaded?
30
34
  request.session.delete(:inertia_errors)
31
35
  request.session.delete(:inertia_clear_history)
32
36
  request.session.delete(:inertia_preserve_fragment)
@@ -3,5 +3,6 @@
3
3
  module InertiaRails
4
4
  class OptionalProp < IgnoreOnFirstLoadProp
5
5
  prepend PropOnceable
6
+ prepend PropCacheable
6
7
  end
7
8
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InertiaRails
4
+ module PropCacheable
5
+ def initialize(**props, &block)
6
+ cache_arg = props.delete(:cache)
7
+
8
+ if cache_arg.is_a?(Hash)
9
+ raise ArgumentError, 'cache: hash requires a :key' unless cache_arg.key?(:key)
10
+
11
+ @cache_key = derive_cache_key(cache_arg.delete(:key))
12
+ @cache_options = cache_arg.freeze
13
+ elsif cache_arg
14
+ @cache_key = derive_cache_key(cache_arg)
15
+ @cache_options = nil
16
+ end
17
+
18
+ super
19
+ end
20
+
21
+ def cached?
22
+ !@cache_key.nil?
23
+ end
24
+
25
+ def call(controller, **context)
26
+ return super unless cached?
27
+
28
+ json = InertiaRails.cache_store.fetch(@cache_key, **(@cache_options || {})) { super.to_json }
29
+ RawJson.new(json)
30
+ end
31
+
32
+ private
33
+
34
+ def derive_cache_key(raw_key)
35
+ expanded = ActiveSupport::Cache.expand_cache_key(raw_key)
36
+ "inertia_rails/#{expanded}"
37
+ end
38
+ end
39
+ end
@@ -82,6 +82,8 @@ module InertiaRails
82
82
  prop = prop.to_inertia if prop.respond_to?(:to_inertia)
83
83
 
84
84
  if prop.is_a?(Hash) && prop.any?
85
+ next if !parent_was_resolved && excluded_by_partial_request?(path)
86
+
85
87
  nested = deep_transform_props(prop, path, parent_was_resolved: parent_was_resolved)
86
88
  transformed_props[key] = nested unless nested.empty?
87
89
  next
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InertiaRails
4
+ # Wraps a pre-serialized JSON string so it embeds directly
5
+ # into a larger JSON structure without re-serialization.
6
+ #
7
+ # - JSON.generate calls #to_json, embedding the raw string.
8
+ # - ActiveSupport::JSON.encode calls #as_json first, which
9
+ # returns parsed Ruby data that the encoder re-serializes.
10
+ class RawJson
11
+ def initialize(json_string)
12
+ @json_string = json_string
13
+ end
14
+
15
+ def to_json(*)
16
+ @json_string
17
+ end
18
+
19
+ def as_json(*)
20
+ JSON.parse(@json_string)
21
+ end
22
+ end
23
+ end
@@ -27,6 +27,7 @@ module InertiaRails
27
27
  @encrypt_history = options.fetch(:encrypt_history, @configuration.encrypt_history)
28
28
  @clear_history = options.fetch(:clear_history, controller.session[:inertia_clear_history] || false)
29
29
  @preserve_fragment = options.fetch(:preserve_fragment, controller.session[:inertia_preserve_fragment] || false)
30
+ @layout_override = options.fetch(:layout) { @configuration.layout }
30
31
  @ssr_cache = options[:ssr_cache]
31
32
 
32
33
  deep_merge = options.fetch(:deep_merge, @configuration.deep_merge_shared_data)
@@ -70,7 +71,7 @@ module InertiaRails
70
71
  end
71
72
 
72
73
  def layout
73
- layout = @configuration.layout
74
+ layout = @layout_override
74
75
  layout.nil? || layout
75
76
  end
76
77
 
@@ -12,7 +12,7 @@ module InertiaRails
12
12
  return unless bundle_exists?
13
13
 
14
14
  if (cache_options = cache_options_hash)
15
- Rails.cache.fetch(cache_key, **cache_options) { request }
15
+ InertiaRails.cache_store.fetch(cache_key, **cache_options) { request }
16
16
  else
17
17
  request
18
18
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InertiaRails
4
- VERSION = '3.20.0'
4
+ VERSION = '3.21.0'
5
5
  end
data/lib/inertia_rails.rb CHANGED
@@ -12,6 +12,8 @@ require_relative 'inertia_rails/current'
12
12
  require_relative 'inertia_rails/errors'
13
13
 
14
14
  # props
15
+ require_relative 'inertia_rails/raw_json'
16
+ require_relative 'inertia_rails/prop_cacheable'
15
17
  require_relative 'inertia_rails/prop_onceable'
16
18
  require_relative 'inertia_rails/prop_mergeable'
17
19
  require_relative 'inertia_rails/base_prop'
@@ -19,6 +21,7 @@ require_relative 'inertia_rails/ignore_on_first_load_prop'
19
21
  require_relative 'inertia_rails/always_prop'
20
22
  require_relative 'inertia_rails/lazy_prop'
21
23
  require_relative 'inertia_rails/optional_prop'
24
+ require_relative 'inertia_rails/cached_prop'
22
25
  require_relative 'inertia_rails/defer_prop'
23
26
  require_relative 'inertia_rails/merge_prop'
24
27
  require_relative 'inertia_rails/once_prop'
@@ -54,6 +57,10 @@ module InertiaRails
54
57
  @configuration ||= Configuration.default
55
58
  end
56
59
 
60
+ def cache_store
61
+ configuration.cache_store
62
+ end
63
+
57
64
  def deprecator # :nodoc:
58
65
  @deprecator ||= ActiveSupport::Deprecation.new
59
66
  end
@@ -82,6 +89,10 @@ module InertiaRails
82
89
  MergeProp.new(deep_merge: true, match_on: match_on, &block)
83
90
  end
84
91
 
92
+ def cache(...)
93
+ CachedProp.new(...)
94
+ end
95
+
85
96
  def defer(...)
86
97
  DeferProp.new(...)
87
98
  end
@@ -138,14 +138,17 @@ Puma::Plugin.create do
138
138
 
139
139
  return Array(config.ssr_bundle).find { |path| File.exist?(path) } if config.ssr_bundle
140
140
 
141
- if defined?(ViteRuby)
142
- ssr_dir = ViteRuby.config.ssr_output_dir
143
- candidates = Dir.glob(File.join(ssr_dir, 'inertia.*')) if ssr_dir
144
- return candidates.first if candidates&.any?
141
+ patterns = %w[ssr/*.js public/assets-ssr/*.js]
142
+ if defined?(ViteRuby) && (ssr_dir = ViteRuby.config.ssr_output_dir)
143
+ patterns.prepend(File.join(ssr_dir, '*.js'))
145
144
  end
146
145
 
147
- path = 'public/assets-ssr/inertia.js'
148
- path if File.exist?(path)
146
+ patterns.each do |pattern|
147
+ candidates = Dir.glob(pattern)
148
+ return candidates.first if candidates.any?
149
+ end
150
+
151
+ nil
149
152
  end
150
153
 
151
154
  def resolve_base_url
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inertia_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.20.0
4
+ version: 3.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Knoles
@@ -184,6 +184,7 @@ files:
184
184
  - lib/inertia_rails.rb
185
185
  - lib/inertia_rails/always_prop.rb
186
186
  - lib/inertia_rails/base_prop.rb
187
+ - lib/inertia_rails/cached_prop.rb
187
188
  - lib/inertia_rails/configuration.rb
188
189
  - lib/inertia_rails/controller.rb
189
190
  - lib/inertia_rails/current.rb
@@ -209,10 +210,12 @@ files:
209
210
  - lib/inertia_rails/once_prop.rb
210
211
  - lib/inertia_rails/optional_prop.rb
211
212
  - lib/inertia_rails/precognition.rb
213
+ - lib/inertia_rails/prop_cacheable.rb
212
214
  - lib/inertia_rails/prop_evaluator.rb
213
215
  - lib/inertia_rails/prop_mergeable.rb
214
216
  - lib/inertia_rails/prop_onceable.rb
215
217
  - lib/inertia_rails/props_resolver.rb
218
+ - lib/inertia_rails/raw_json.rb
216
219
  - lib/inertia_rails/renderer.rb
217
220
  - lib/inertia_rails/rspec.rb
218
221
  - lib/inertia_rails/rspec/deprecated.rb