inertia_rails 3.2.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76a7152d32edba5a140ed16b25efe25a6766a7d53c955f39f6b5efb2b5fc0d12
4
- data.tar.gz: c1dc0d2bf09b2f3c71e4729a26dc6c24c78dc0fe4c5b8d09484bacee18543953
3
+ metadata.gz: 58d0616b3504cdbe3a0002ea2568b8af2bf8ab442882115109c1c4fcd636fbf2
4
+ data.tar.gz: ad704410b5c41f0c75a6dc6b2029e9e3742ec16539ab607414f86d0c63a7a397
5
5
  SHA512:
6
- metadata.gz: 70ff7787dd839a58c652ffe5fa402be2baa2036ea54b86b2b033ecaf2062304a4f5e8433316eea192ab25009d6c5e8b1cf45dfeba71dc6f8775b361cb3055f50
7
- data.tar.gz: 9e9072c293cd62385df9e8be3f464c7b688acc665ad4ce40daea5fd29c0414c18ba5c9f72967edfc3c7963974867d2791486e78714cea98ac212c64f38144ccb
6
+ metadata.gz: b19bade9694bad666820b90e25727658679ce057c06c501f95344d70cbced198ed217d5f965d8146eb303ae48794675e93da9cd371c012afb8965533cf8c9eba
7
+ data.tar.gz: 1a1ce123b90feecc66c211ea38cbcd4a3db6d2696ec0807a0e196baad99b32901d205fefae38497db2eba22d8da7f7dbc8b2923c20474f6626d639e7c033a99d
data/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ 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
+ ## [3.4.0] - 2024-11-02
8
+
9
+ * Inertia Rails documentation (@skryukov)
10
+ * Add specs for config refactor (#139)
11
+ * New feature: if/unless/only/except options for inertia_share. Enables per-action sharing! (#137, @skryukov)
12
+ * Bugfix: for inertia errors when using message_pack to serialize cookies. (#143, @BenMorganMY)
13
+ * Test Rails 7.2 in CI/CD (#145, @skryukov)
14
+ * Bring redirect behavior in line with Rails 7.0 behavior (#146, @skryukov)
15
+ * Gemspec cleanup (#149, @skryukov)
16
+
17
+ ## [3.3.0] - 2024-10-27
18
+
19
+ * Refactor Inertia configuration into a controller class method. Thanks @ElMassimo!
20
+ * Documentation updates. Thanks @osbre and @austenmadden!
21
+ * Further fixes to the `Vary` header. Thanks @skryukov!
22
+ * Add configuration option for the component path in the renderer.
23
+
7
24
  ## [3.2.0] - 2024-06-19
8
25
 
9
26
  * Refactor the internals of shared Inertia data to use controller instance variables instead of module level variables that run a higher risk of being leaked between requests. Big thanks to @ledermann for the initial work many years ago and to @PedroAugustoRamalhoDuarte for finishing it up!
data/README.md CHANGED
@@ -7,14 +7,26 @@
7
7
 
8
8
  ### Backend
9
9
 
10
- Just add the inertia rails gem to your Gemfile
10
+ Add the `inertia_rails` gem to your Gemfile.
11
+
11
12
  ```ruby
12
13
  gem 'inertia_rails'
13
14
  ```
14
15
 
16
+ For more instructions, see [Server-side setup](https://inertia-rails.netlify.app/guide/server-side-setup.html).
17
+
15
18
  ### Frontend
16
19
 
17
- Rails 7 specific frontend docs coming soon. For now, check out the official Inertia docs at https://inertiajs.com/ or see an example using React/Vite [here](https://github.com/BrandonShar/inertia-rails-template)
20
+ We are discussing on bringing official docs for Inertia Rails to this repo, as
21
+ the [official docs](https://inertiajs.com/client-side-setup) are specific to Laravel.
22
+
23
+ In the meantime, you can refer to the community-maintained [Client-side setup](https://inertia-rails.netlify.app/guide/client-side-setup.html).
24
+
25
+ Examples:
26
+
27
+ - [React/Vite](https://github.com/BrandonShar/inertia-rails-template)
28
+ - [React/Vite + SSR](https://github.com/ElMassimo/inertia-rails-ssr-template)
29
+ - [PingCRM with Vue and Vite](https://github.com/ledermann/pingcrm)
18
30
 
19
31
  ## Usage
20
32
 
@@ -63,12 +75,24 @@ end
63
75
  In order to use instance props, you must call `use_inertia_instance_props` on the controller (or a base controller it inherits from). If any props are provided manually, instance props
64
76
  are automatically disabled for that response. Instance props are only included if they are defined after the before filter is set from `use_inertia_instance_props`.
65
77
 
66
- Automatic component name is also opt in, you must set the `default_render` config value to `true`. Otherwise, you can simply `render inertia: true` for the same behavior explicitly.
78
+ Automatic component name is also opt in, you must set the [`default_render`](#default_render) config value to `true`. Otherwise, you can simply `render inertia: true` for the same behavior explicitly.
79
+
80
+ If the default component path doesn't match your convention, you can define a method to resolve it however you like via the `component_path_resolver` config value. The value of this should be callable and will receive the path and action and should return a string component path.
81
+
82
+ ```ruby
83
+ inertia_config(
84
+ component_path_resolver: ->(path:, action:) do
85
+ "Storefront/#{path.camelize}/#{action.camelize}"
86
+ end
87
+ )
88
+
89
+ ```
90
+
91
+
67
92
 
68
93
  ### Layout
69
94
 
70
- Inertia layouts use the rails layout convention and can be set or changed in the same way. The original `layout` config option is still functional, but will likely be deprecated in the future in favor
71
- of using rails layouts.
95
+ Inertia layouts use the rails layout convention and can be set or changed in the same way.
72
96
 
73
97
  ```ruby
74
98
  class EventsController < ApplicationController
@@ -133,20 +157,14 @@ end
133
157
  }
134
158
  ```
135
159
 
136
- Deep merging can be set as the project wide default via the InertiaRails configuration:
160
+ Deep merging can be configured using the [`deep_merge_shared_data`](#deep_merge_shared_data) configuration option.
137
161
 
138
- ```ruby
139
- # config/initializers/some_initializer.rb
140
- InertiaRails.configure do |config|
141
- config.deep_merge_shared_data = true
142
- end
143
-
144
- ```
145
-
146
- If deep merging is enabled by default, it's possible to opt out within the action:
162
+ If deep merging is enabled, you can still opt-out within the action:
147
163
 
148
164
  ```ruby
149
165
  class CrazyScorersController < ApplicationController
166
+ inertia_config(deep_merge_shared_data: true)
167
+
150
168
  inertia_share do
151
169
  {
152
170
  basketball_data: {
@@ -163,7 +181,7 @@ class CrazyScorersController < ApplicationController
163
181
  end
164
182
  end
165
183
 
166
- # Even if deep merging is set by default, since the renderer has `deep_merge: false`, it will send a shallow merge to the frontend:
184
+ # `deep_merge: false` overrides the default:
167
185
  {
168
186
  basketball_data: {
169
187
  points: 100,
@@ -177,6 +195,9 @@ On the front end, Inertia supports the concept of "partial reloads" where only t
177
195
 
178
196
  ```ruby
179
197
  inertia_share some_data: InertiaRails.lazy(lambda { some_very_slow_method })
198
+
199
+ # Using a Ruby block syntax
200
+ inertia_share some_data: InertiaRails.lazy { some_very_slow_method }
180
201
  ```
181
202
 
182
203
  ### Routing
@@ -187,34 +208,85 @@ If you don't need a controller to handle a static component, you can route direc
187
208
  inertia 'about' => 'AboutComponent'
188
209
  ```
189
210
 
190
- ### SSR
211
+ ### SSR _(experimental)_
212
+
213
+ Enable SSR via the configuration options for [`ssr_enabled`](#ssr_enabled-experimental) and [`ssr_url`](#ssr_url-experimental).
214
+
215
+ When using SSR, don't forget to add `<%= inertia_ssr_head %>` to the `<head>` of your layout (i.e. `application.html.erb`).
191
216
 
192
- Enable SSR via the config settings for `ssr_enabled` and `ssr_url`.
217
+ ## Configuration ⚙️
193
218
 
194
- When using SSR, don't forget to add `<%= inertia_headers %>` to the `<head>` of your `application.html.erb`.
219
+ Inertia Rails can be configured globally or in a specific controller (and subclasses).
195
220
 
196
- ## Configuration
221
+ ### Global Configuration
197
222
 
198
- Inertia Rails has a few different configuration options that can be set anywhere, but the most common location is from within an initializer.
223
+ If using global configuration, we recommend you place the code inside an initializer:
199
224
 
200
- The default config is shown below
201
225
  ```ruby
226
+ # config/initializers/inertia.rb
227
+
202
228
  InertiaRails.configure do |config|
203
-
204
- # set the current version for automatic asset refreshing. A string value should be used if any.
205
- config.version = nil
206
- # enable default inertia rendering (warning! this will override rails default rendering behavior)
207
- config.default_render = true
208
-
209
- # ssr specific options
210
- config.ssr_enabled = false
211
- config.ssr_url = 'http://localhost:13714'
229
+ # Example: force a full-reload if the deployed assets change.
230
+ config.version = ViteRuby.digest
231
+ end
232
+ ```
212
233
 
213
- config.deep_merge_shared_data = false
214
-
234
+ The default configuration can be found [here](https://github.com/inertiajs/inertia-rails/blob/master/lib/inertia_rails/configuration.rb#L5-L22).
235
+
236
+ ### Local Configuration
237
+
238
+ Use `inertia_config` in your controllers to override global settings:
239
+
240
+ ```ruby
241
+ class EventsController < ApplicationController
242
+ inertia_config(
243
+ version: "events-#{InertiaRails.configuration.version}",
244
+ ssr_enabled: -> { action_name == "index" },
245
+ )
215
246
  end
216
247
  ```
217
248
 
249
+ ### Configuration Options
250
+
251
+ #### `version` _(recommended)_
252
+
253
+ This allows Inertia to detect if the app running in the client is oudated,
254
+ forcing a full page visit instead of an XHR visit on the next request.
255
+
256
+ See [assets versioning](https://inertiajs.com/asset-versioning).
257
+
258
+ __Default__: `nil`
259
+
260
+ #### `deep_merge_shared_data`
261
+
262
+ When enabled, props will be deep merged with shared data, combining hashes
263
+ with the same keys instead of replacing them.
264
+
265
+ __Default__: `false`
266
+
267
+ #### `default_render`
268
+
269
+ Overrides Rails default rendering behavior to render using Inertia by default.
270
+
271
+ __Default__: `false`
272
+
273
+ #### `ssr_enabled` _(experimental)_
274
+
275
+ Whether to use a JavaScript server to pre-render your JavaScript pages,
276
+ allowing your visitors to receive fully rendered HTML when they first visit
277
+ your application.
278
+
279
+ Requires a JS server to be available at `ssr_url`. [_Example_](https://github.com/ElMassimo/inertia-rails-ssr-template)
280
+
281
+ __Default__: `false`
282
+
283
+ #### `ssr_url` _(experimental)_
284
+
285
+ The URL of the JS server that will pre-render the app using the specified
286
+ component and props.
287
+
288
+ __Default__: `"http://localhost:13714"`
289
+
218
290
  ## Testing
219
291
 
220
292
  If you're using Rspec, Inertia Rails comes with some nice test helpers to make things simple.
@@ -0,0 +1,27 @@
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
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InertiaRails
4
+ class Configuration
5
+ DEFAULTS = {
6
+ # Whether to combine hashes with the same keys instead of replacing them.
7
+ deep_merge_shared_data: false,
8
+
9
+ # Overrides Rails default rendering behavior to render using Inertia by default.
10
+ default_render: false,
11
+
12
+ # Allows the user to hook into the default rendering behavior and change it to fit their needs
13
+ component_path_resolver: ->(path:, action:) { "#{path}/#{action}" },
14
+
15
+ # DEPRECATED: Let Rails decide which layout should be used based on the
16
+ # controller configuration.
17
+ layout: true,
18
+
19
+ # SSR options.
20
+ ssr_enabled: false,
21
+ ssr_url: 'http://localhost:13714',
22
+
23
+ # Used to detect version drift between server and client.
24
+ version: nil,
25
+ }.freeze
26
+
27
+ OPTION_NAMES = DEFAULTS.keys.freeze
28
+
29
+ protected attr_reader :controller
30
+ protected attr_reader :options
31
+
32
+ def initialize(controller: nil, **attrs)
33
+ @controller = controller
34
+ @options = attrs.extract!(*OPTION_NAMES)
35
+
36
+ unless attrs.empty?
37
+ raise ArgumentError, "Unknown options for #{self.class}: #{attrs.keys}"
38
+ end
39
+ end
40
+
41
+ def bind_controller(controller)
42
+ Configuration.new(**@options, controller: controller)
43
+ end
44
+
45
+ def freeze
46
+ @options.freeze
47
+ super
48
+ end
49
+
50
+ def merge!(config)
51
+ @options.merge!(config.options)
52
+ self
53
+ end
54
+
55
+ def merge(config)
56
+ Configuration.new(**@options.merge(config.options))
57
+ end
58
+
59
+ # Internal: Finalizes the configuration for a specific controller.
60
+ def with_defaults(config)
61
+ @options = config.options.merge(@options)
62
+ freeze
63
+ end
64
+
65
+ def component_path_resolver(path:, action:)
66
+ @options[:component_path_resolver].call(path:, action:)
67
+ end
68
+
69
+ OPTION_NAMES.each do |option|
70
+ define_method(option) {
71
+ evaluate_option @options[option]
72
+ } unless method_defined?(option)
73
+ define_method("#{option}=") { |value|
74
+ @options[option] = value
75
+ }
76
+ end
77
+
78
+ def self.default
79
+ new(**DEFAULTS)
80
+ end
81
+
82
+ private
83
+
84
+ def evaluate_option(value)
85
+ return value unless value.respond_to?(:call)
86
+ return value.call unless controller
87
+ controller.instance_exec(&value)
88
+ end
89
+ end
90
+ end
@@ -1,37 +1,42 @@
1
1
  require_relative "inertia_rails"
2
+ require_relative "helper"
3
+ require_relative "action_filter"
2
4
 
3
5
  module InertiaRails
4
6
  module Controller
5
7
  extend ActiveSupport::Concern
6
8
 
7
9
  included do
8
- helper_method :inertia_headers
9
-
10
- before_action do
11
- error_sharing = proc do
12
- # :inertia_errors are deleted from the session by the middleware
13
- if @_request && session[:inertia_errors].present?
14
- { errors: session[:inertia_errors] }
15
- else
16
- {}
17
- end
18
- end
19
-
20
- @_inertia_shared_plain_data ||= {}
21
- @_inertia_shared_blocks ||= [error_sharing]
22
- @_inertia_html_headers ||= []
23
- end
10
+ helper ::InertiaRails::Helper
24
11
 
25
12
  after_action do
26
- cookies['XSRF-TOKEN'] = form_authenticity_token unless !protect_against_forgery?
13
+ cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
27
14
  end
28
15
  end
29
16
 
30
17
  module ClassMethods
31
- def inertia_share(hash = nil, &block)
32
- before_action do
33
- @_inertia_shared_plain_data = @_inertia_shared_plain_data.merge(hash) if hash
34
- @_inertia_shared_blocks = @_inertia_shared_blocks + [block] if block_given?
18
+ def inertia_share(hash = nil, **props, &block)
19
+ options = extract_inertia_share_options(props)
20
+ return push_to_inertia_share(**(hash || props), &block) if options.empty?
21
+
22
+ push_to_inertia_share do
23
+ next unless options[:if].all? { |filter| instance_exec(&filter) } if options[:if]
24
+ next unless options[:unless].none? { |filter| instance_exec(&filter) } if options[:unless]
25
+
26
+ next hash unless block
27
+
28
+ res = instance_exec(&block)
29
+ hash ? hash.merge(res) : res
30
+ end
31
+ end
32
+
33
+ def inertia_config(**attrs)
34
+ config = InertiaRails::Configuration.new(**attrs)
35
+
36
+ if @inertia_config
37
+ @inertia_config.merge!(config)
38
+ else
39
+ @inertia_config = config
35
40
  end
36
41
  end
37
42
 
@@ -41,55 +46,108 @@ module InertiaRails
41
46
  @_inertia_skip_props = view_assigns.keys + ['_inertia_skip_props']
42
47
  end
43
48
  end
44
- end
45
49
 
46
- def inertia_headers
47
- @_inertia_html_headers.join.html_safe
48
- end
50
+ def _inertia_configuration
51
+ @_inertia_configuration ||= begin
52
+ config = superclass.try(:_inertia_configuration) || ::InertiaRails.configuration
53
+ @inertia_config&.with_defaults(config) || config
54
+ end
55
+ end
56
+
57
+ def _inertia_shared_data
58
+ @_inertia_shared_data ||= begin
59
+ shared_data = superclass.try(:_inertia_shared_data)
60
+
61
+ if @inertia_share && shared_data.present?
62
+ shared_data + @inertia_share.freeze
63
+ else
64
+ @inertia_share || shared_data || []
65
+ end.freeze
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def push_to_inertia_share(**attrs, &block)
72
+ @inertia_share ||= []
73
+ @inertia_share << attrs.freeze unless attrs.empty?
74
+ @inertia_share << block if block
75
+ end
76
+
77
+ def extract_inertia_share_options(props)
78
+ options = props.slice(:if, :unless, :only, :except)
79
+
80
+ return options if options.empty?
81
+
82
+ if props.except(:if, :unless, :only, :except).any?
83
+ raise ArgumentError, "You must not mix shared data and [:if, :unless, :only, :except] options, pass data as a hash or a block."
84
+ end
85
+
86
+ transform_inertia_share_option(options, :only, :if)
87
+ transform_inertia_share_option(options, :except, :unless)
88
+
89
+ options.transform_values! do |filters|
90
+ Array(filters).map!(&method(:filter_to_proc))
91
+ end
92
+
93
+ options
94
+ end
95
+
96
+ def transform_inertia_share_option(options, from, to)
97
+ if (from_value = options.delete(from))
98
+ filter = InertiaRails::ActionFilter.new(from, from_value)
99
+ options[to] = Array(options[to]).unshift(filter)
100
+ end
101
+ end
49
102
 
50
- def inertia_headers=(value)
51
- @_inertia_html_headers = value
103
+ def filter_to_proc(filter)
104
+ case filter
105
+ when Symbol
106
+ -> { send(filter) }
107
+ when Proc
108
+ filter
109
+ when InertiaRails::ActionFilter
110
+ -> { filter.match?(self) }
111
+ else
112
+ raise ArgumentError, "You must pass a symbol or a proc as a filter."
113
+ end
114
+ end
52
115
  end
53
116
 
54
117
  def default_render
55
- if InertiaRails.default_render?
118
+ if inertia_configuration.default_render
56
119
  render(inertia: true)
57
120
  else
58
121
  super
59
122
  end
60
123
  end
61
124
 
62
- def shared_data
63
- (@_inertia_shared_plain_data || {}).merge(evaluated_blocks)
64
- end
65
-
66
125
  def redirect_to(options = {}, response_options = {})
67
126
  capture_inertia_errors(response_options)
68
- super(options, response_options)
127
+ super
69
128
  end
70
129
 
71
- def redirect_back(fallback_location:, allow_other_host: true, **options)
72
- capture_inertia_errors(options)
73
- super(
74
- fallback_location: fallback_location,
75
- allow_other_host: allow_other_host,
76
- **options,
77
- )
78
- end
130
+ private
79
131
 
80
132
  def inertia_view_assigns
81
133
  return {} unless @_inertia_instance_props
82
134
  view_assigns.except(*@_inertia_skip_props)
83
135
  end
84
136
 
85
- private
137
+ def inertia_configuration
138
+ self.class._inertia_configuration.bind_controller(self)
139
+ end
86
140
 
87
- def inertia_layout
88
- layout = ::InertiaRails.layout
141
+ def inertia_shared_data
142
+ initial_data = session[:inertia_errors].present? ? {errors: session[:inertia_errors]} : {}
89
143
 
90
- # When the global configuration is not set, let Rails decide which layout
91
- # should be used based on the controller configuration.
92
- layout.nil? ? true : layout
144
+ self.class._inertia_shared_data.filter_map { |shared_data|
145
+ if shared_data.respond_to?(:call)
146
+ instance_exec(&shared_data)
147
+ else
148
+ shared_data
149
+ end
150
+ }.reduce(initial_data, &:merge)
93
151
  end
94
152
 
95
153
  def inertia_location(url)
@@ -99,12 +157,8 @@ module InertiaRails
99
157
 
100
158
  def capture_inertia_errors(options)
101
159
  if (inertia_errors = options.dig(:inertia, :errors))
102
- session[:inertia_errors] = inertia_errors
160
+ session[:inertia_errors] = inertia_errors.to_hash
103
161
  end
104
162
  end
105
-
106
- def evaluated_blocks
107
- (@_inertia_shared_blocks || []).map { |block| instance_exec(&block) }.reduce(&:merge) || {}
108
- end
109
163
  end
110
164
  end
@@ -0,0 +1,14 @@
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
+ end
@@ -1,52 +1,20 @@
1
1
  # Needed for `thread_mattr_accessor`
2
2
  require 'active_support/core_ext/module/attribute_accessors_per_thread'
3
3
  require 'inertia_rails/lazy'
4
+ require 'inertia_rails/configuration'
4
5
 
5
6
  module InertiaRails
6
- def self.configure
7
- yield(Configuration)
8
- end
9
-
10
- def self.version
11
- Configuration.evaluated_version
12
- end
13
-
14
- def self.layout
15
- Configuration.layout
16
- end
17
-
18
- def self.ssr_enabled?
19
- Configuration.ssr_enabled
20
- end
7
+ CONFIGURATION = Configuration.default
21
8
 
22
- def self.ssr_url
23
- Configuration.ssr_url
24
- end
25
-
26
- def self.default_render?
27
- Configuration.default_render
9
+ def self.configure
10
+ yield(CONFIGURATION)
28
11
  end
29
12
 
30
- def self.deep_merge_shared_data?
31
- Configuration.deep_merge_shared_data
13
+ def self.configuration
14
+ CONFIGURATION
32
15
  end
33
16
 
34
17
  def self.lazy(value = nil, &block)
35
18
  InertiaRails::Lazy.new(value, &block)
36
19
  end
37
-
38
- private
39
-
40
- module Configuration
41
- mattr_accessor(:layout) { nil }
42
- mattr_accessor(:version) { nil }
43
- mattr_accessor(:ssr_enabled) { false }
44
- mattr_accessor(:ssr_url) { 'http://localhost:13714' }
45
- mattr_accessor(:default_render) { false }
46
- mattr_accessor(:deep_merge_shared_data) { false }
47
-
48
- def self.evaluated_version
49
- self.version.respond_to?(:call) ? self.version.call : self.version
50
- end
51
- end
52
20
  end
@@ -5,8 +5,7 @@ module InertiaRails
5
5
  end
6
6
 
7
7
  def call(env)
8
- InertiaRailsRequest.new(@app, env)
9
- .response
8
+ InertiaRailsRequest.new(@app, env).response
10
9
  end
11
10
 
12
11
  class InertiaRailsRequest
@@ -58,11 +57,15 @@ module InertiaRails
58
57
  request_method == 'GET'
59
58
  end
60
59
 
60
+ def controller
61
+ @env["action_controller.instance"]
62
+ end
63
+
61
64
  def request_method
62
65
  @env['REQUEST_METHOD']
63
66
  end
64
67
 
65
- def inertia_version
68
+ def client_version
66
69
  @env['HTTP_X_INERTIA_VERSION']
67
70
  end
68
71
 
@@ -71,17 +74,15 @@ module InertiaRails
71
74
  end
72
75
 
73
76
  def version_stale?
74
- sent_version != saved_version
77
+ coerce_version(client_version) != coerce_version(server_version)
75
78
  end
76
79
 
77
- def sent_version
78
- return nil if inertia_version.nil?
79
-
80
- InertiaRails.version.is_a?(Numeric) ? inertia_version.to_f : inertia_version
80
+ def server_version
81
+ controller&.send(:inertia_configuration)&.version
81
82
  end
82
83
 
83
- def saved_version
84
- InertiaRails.version.is_a?(Numeric) ? InertiaRails.version.to_f : InertiaRails.version
84
+ def coerce_version(version)
85
+ server_version.is_a?(Numeric) ? version.to_f : version
85
86
  end
86
87
 
87
88
  def force_refresh(request)
@@ -1,52 +1,74 @@
1
+ require 'net/http'
2
+ require 'json'
1
3
  require_relative "inertia_rails"
2
4
 
3
5
  module InertiaRails
4
6
  class Renderer
5
- attr_reader :component, :view_data
7
+ attr_reader(
8
+ :component,
9
+ :configuration,
10
+ :controller,
11
+ :props,
12
+ :view_data,
13
+ )
6
14
 
7
15
  def initialize(component, controller, request, response, render_method, props: nil, view_data: nil, deep_merge: nil)
8
- @component = component.is_a?(TrueClass) ? "#{controller.controller_path}/#{controller.action_name}" : component
9
16
  @controller = controller
17
+ @configuration = controller.__send__(:inertia_configuration)
18
+ @component = resolve_component(component)
10
19
  @request = request
11
20
  @response = response
12
21
  @render_method = render_method
13
- @props = props ? props : controller.inertia_view_assigns
22
+ @props = props || controller.__send__(:inertia_view_assigns)
14
23
  @view_data = view_data || {}
15
- @deep_merge = !deep_merge.nil? ? deep_merge : InertiaRails.deep_merge_shared_data?
24
+ @deep_merge = !deep_merge.nil? ? deep_merge : configuration.deep_merge_shared_data
16
25
  end
17
26
 
18
27
  def render
28
+ if @response.headers["Vary"].blank?
29
+ @response.headers["Vary"] = 'X-Inertia'
30
+ else
31
+ @response.headers["Vary"] = "#{@response.headers["Vary"]}, X-Inertia"
32
+ end
19
33
  if @request.headers['X-Inertia']
20
- @response.set_header('Vary', 'X-Inertia')
21
34
  @response.set_header('X-Inertia', 'true')
22
35
  @render_method.call json: page, status: @response.status, content_type: Mime[:json]
23
36
  else
24
- return render_ssr if ::InertiaRails.ssr_enabled? rescue nil
25
- @render_method.call template: 'inertia', layout: layout, locals: (view_data).merge({page: page})
37
+ return render_ssr if configuration.ssr_enabled rescue nil
38
+ @render_method.call template: 'inertia', layout: layout, locals: view_data.merge(page: page)
26
39
  end
27
40
  end
28
41
 
29
42
  private
30
43
 
31
44
  def render_ssr
32
- uri = URI("#{::InertiaRails.ssr_url}/render")
45
+ uri = URI("#{configuration.ssr_url}/render")
33
46
  res = JSON.parse(Net::HTTP.post(uri, page.to_json, 'Content-Type' => 'application/json').body)
34
47
 
35
- @controller.inertia_headers = res['head']
36
- @render_method.call html: res['body'].html_safe, layout: layout, locals: (view_data).merge({page: page})
48
+ controller.instance_variable_set("@_inertia_ssr_head", res['head'].join.html_safe)
49
+ @render_method.call html: res['body'].html_safe, layout: layout, locals: view_data.merge(page: page)
37
50
  end
38
51
 
39
52
  def layout
40
- @controller.send(:inertia_layout)
53
+ layout = configuration.layout
54
+ layout.nil? ? true : layout
55
+ end
56
+
57
+ def shared_data
58
+ controller.__send__(:inertia_shared_data)
59
+ end
60
+
61
+ # Cast props to symbol keyed hash before merging so that we have a consistent data structure and
62
+ # avoid duplicate keys after merging.
63
+ #
64
+ # Functionally, this permits using either string or symbol keys in the controller. Since the results
65
+ # is cast to json, we should treat string/symbol keys as identical.
66
+ def merge_props(shared_data, props)
67
+ shared_data.deep_symbolize_keys.send(@deep_merge ? :deep_merge : :merge, props.deep_symbolize_keys)
41
68
  end
42
69
 
43
70
  def computed_props
44
- # Cast props to symbol keyed hash before merging so that we have a consistent data structure and
45
- # avoid duplicate keys after merging.
46
- #
47
- # Functionally, this permits using either string or symbol keys in the controller. Since the results
48
- # is cast to json, we should treat string/symbol keys as identical.
49
- _props = @controller.shared_data.merge.deep_symbolize_keys.send(prop_merge_method, @props.deep_symbolize_keys).select do |key, prop|
71
+ _props = merge_props(shared_data, props).select do |key, prop|
50
72
  if rendering_partial_component?
51
73
  key.in? partial_keys
52
74
  else
@@ -57,7 +79,7 @@ module InertiaRails
57
79
  deep_transform_values(
58
80
  _props,
59
81
  lambda do |prop|
60
- prop.respond_to?(:call) ? @controller.instance_exec(&prop) : prop
82
+ prop.respond_to?(:call) ? controller.instance_exec(&prop) : prop
61
83
  end
62
84
  )
63
85
  end
@@ -67,7 +89,7 @@ module InertiaRails
67
89
  component: component,
68
90
  props: computed_props,
69
91
  url: @request.original_fullpath,
70
- version: ::InertiaRails.version,
92
+ version: configuration.version,
71
93
  }
72
94
  end
73
95
 
@@ -85,8 +107,10 @@ module InertiaRails
85
107
  @request.inertia_partial? && @request.headers['X-Inertia-Partial-Component'] == component
86
108
  end
87
109
 
88
- def prop_merge_method
89
- @deep_merge ? :deep_merge : :merge
110
+ def resolve_component(component)
111
+ return component unless component.is_a? TrueClass
112
+
113
+ configuration.component_path_resolver(path: controller.controller_path, action: controller.action_name)
90
114
  end
91
115
  end
92
116
  end
@@ -25,9 +25,16 @@ module InertiaRails
25
25
  protected
26
26
 
27
27
  def set_values(params)
28
- @view_data = params[:locals].except(:page)
29
- @props = params[:locals][:page][:props]
30
- @component = params[:locals][:page][:component]
28
+ if params[:locals].present?
29
+ @view_data = params[:locals].except(:page)
30
+ @props = params[:locals][:page][:props]
31
+ @component = params[:locals][:page][:component]
32
+ else
33
+ # Sequential Inertia request
34
+ @view_data = {}
35
+ @props = params[:json][:props]
36
+ @component = params[:json][:component]
37
+ end
31
38
  end
32
39
  end
33
40
 
@@ -1,3 +1,3 @@
1
1
  module InertiaRails
2
- VERSION = "3.2.0"
2
+ VERSION = "3.4.0"
3
3
  end
data/lib/inertia_rails.rb CHANGED
@@ -21,4 +21,8 @@ end
21
21
 
22
22
  module InertiaRails
23
23
  class Error < StandardError; end
24
+
25
+ def self.deprecator # :nodoc:
26
+ @deprecator ||= ActiveSupport::Deprecation.new
27
+ end
24
28
  end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inertia_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Knoles
8
8
  - Brandon Shar
9
9
  - Eugene Granovsky
10
10
  autorequire:
11
- bindir: exe
11
+ bindir: bin
12
12
  cert_chain: []
13
- date: 2024-06-19 00:00:00.000000000 Z
13
+ date: 2024-11-02 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: railties
@@ -18,135 +18,29 @@ dependencies:
18
18
  requirements:
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: '5'
21
+ version: '6'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: '5'
29
- - !ruby/object:Gem::Dependency
30
- name: bundler
31
- requirement: !ruby/object:Gem::Requirement
32
- requirements:
33
- - - "~>"
34
- - !ruby/object:Gem::Version
35
- version: '2.0'
36
- type: :development
37
- prerelease: false
38
- version_requirements: !ruby/object:Gem::Requirement
39
- requirements:
40
- - - "~>"
41
- - !ruby/object:Gem::Version
42
- version: '2.0'
43
- - !ruby/object:Gem::Dependency
44
- name: rake
45
- requirement: !ruby/object:Gem::Requirement
46
- requirements:
47
- - - "~>"
48
- - !ruby/object:Gem::Version
49
- version: '13.0'
50
- type: :development
51
- prerelease: false
52
- version_requirements: !ruby/object:Gem::Requirement
53
- requirements:
54
- - - "~>"
55
- - !ruby/object:Gem::Version
56
- version: '13.0'
57
- - !ruby/object:Gem::Dependency
58
- name: rspec-rails
59
- requirement: !ruby/object:Gem::Requirement
60
- requirements:
61
- - - "~>"
62
- - !ruby/object:Gem::Version
63
- version: '4.0'
64
- type: :development
65
- prerelease: false
66
- version_requirements: !ruby/object:Gem::Requirement
67
- requirements:
68
- - - "~>"
69
- - !ruby/object:Gem::Version
70
- version: '4.0'
71
- - !ruby/object:Gem::Dependency
72
- name: rails-controller-testing
73
- requirement: !ruby/object:Gem::Requirement
74
- requirements:
75
- - - ">="
76
- - !ruby/object:Gem::Version
77
- version: '0'
78
- type: :development
79
- prerelease: false
80
- version_requirements: !ruby/object:Gem::Requirement
81
- requirements:
82
- - - ">="
83
- - !ruby/object:Gem::Version
84
- version: '0'
85
- - !ruby/object:Gem::Dependency
86
- name: sqlite3
87
- requirement: !ruby/object:Gem::Requirement
88
- requirements:
89
- - - ">="
90
- - !ruby/object:Gem::Version
91
- version: '0'
92
- type: :development
93
- prerelease: false
94
- version_requirements: !ruby/object:Gem::Requirement
95
- requirements:
96
- - - ">="
97
- - !ruby/object:Gem::Version
98
- version: '0'
99
- - !ruby/object:Gem::Dependency
100
- name: responders
101
- requirement: !ruby/object:Gem::Requirement
102
- requirements:
103
- - - ">="
104
- - !ruby/object:Gem::Version
105
- version: '0'
106
- type: :development
107
- prerelease: false
108
- version_requirements: !ruby/object:Gem::Requirement
109
- requirements:
110
- - - ">="
111
- - !ruby/object:Gem::Version
112
- version: '0'
113
- - !ruby/object:Gem::Dependency
114
- name: debug
115
- requirement: !ruby/object:Gem::Requirement
116
- requirements:
117
- - - ">="
118
- - !ruby/object:Gem::Version
119
- version: '0'
120
- type: :development
121
- prerelease: false
122
- version_requirements: !ruby/object:Gem::Requirement
123
- requirements:
124
- - - ">="
125
- - !ruby/object:Gem::Version
126
- version: '0'
127
- description:
28
+ version: '6'
29
+ description: Quickly build modern single-page React, Vue and Svelte apps using classic
30
+ server-side routing and controllers.
128
31
  email:
129
- - brain@bellawatt.com
32
+ - brian@bellawatt.com
130
33
  - brandon@bellawatt.com
131
34
  - eugene@bellawatt.com
132
35
  executables: []
133
36
  extensions: []
134
37
  extra_rdoc_files: []
135
38
  files:
136
- - ".github/workflows/push.yml"
137
- - ".gitignore"
138
- - ".rspec"
139
39
  - CHANGELOG.md
140
- - CODE_OF_CONDUCT.md
141
- - Gemfile
142
40
  - LICENSE.txt
143
41
  - README.md
144
- - Rakefile
145
42
  - app/controllers/inertia_rails/static_controller.rb
146
43
  - app/views/inertia.html.erb
147
- - bin/console
148
- - bin/setup
149
- - inertia_rails.gemspec
150
44
  - lib/generators/inertia_rails/install/controller.rb
151
45
  - lib/generators/inertia_rails/install/react/InertiaExample.jsx
152
46
  - lib/generators/inertia_rails/install/react/inertia.jsx
@@ -156,8 +50,11 @@ files:
156
50
  - lib/generators/inertia_rails/install/vue/inertia.js
157
51
  - lib/generators/inertia_rails/install_generator.rb
158
52
  - lib/inertia_rails.rb
53
+ - lib/inertia_rails/action_filter.rb
54
+ - lib/inertia_rails/configuration.rb
159
55
  - lib/inertia_rails/controller.rb
160
56
  - lib/inertia_rails/engine.rb
57
+ - lib/inertia_rails/helper.rb
161
58
  - lib/inertia_rails/inertia_rails.rb
162
59
  - lib/inertia_rails/lazy.rb
163
60
  - lib/inertia_rails/middleware.rb
@@ -175,9 +72,12 @@ homepage: https://github.com/inertiajs/inertia-rails
175
72
  licenses:
176
73
  - MIT
177
74
  metadata:
75
+ bug_tracker_uri: https://github.com/inertiajs/inertia-rails/issues
76
+ changelog_uri: https://github.com/inertiajs/inertia-rails/blob/master/CHANGELOG.md
77
+ documentation_uri: https://github.com/inertiajs/inertia-rails/blob/master/README.md
178
78
  homepage_uri: https://github.com/inertiajs/inertia-rails
179
79
  source_code_uri: https://github.com/inertiajs/inertia-rails
180
- changelog_uri: https://github.com/inertiajs/inertia-rails/blob/master/CHANGELOG.md
80
+ rubygems_mfa_required: 'true'
181
81
  post_install_message:
182
82
  rdoc_options: []
183
83
  require_paths:
@@ -193,8 +93,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
93
  - !ruby/object:Gem::Version
194
94
  version: '0'
195
95
  requirements: []
196
- rubygems_version: 3.5.10
96
+ rubygems_version: 3.5.11
197
97
  signing_key:
198
98
  specification_version: 4
199
- summary: Inertia adapter for Rails
99
+ summary: Inertia.js adapter for Rails
200
100
  test_files: []
@@ -1,33 +0,0 @@
1
- name: Testing
2
-
3
- on: [push, pull_request]
4
-
5
- jobs:
6
- test:
7
- strategy:
8
- fail-fast: false
9
- matrix:
10
- ruby: ['3.1', '3.2', '3.3']
11
- rails: ['6.1', '7.0', '7.1']
12
-
13
- runs-on: ubuntu-latest
14
- name: Test against Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }}
15
-
16
- steps:
17
- - uses: actions/checkout@v4
18
-
19
- - name: Setup System
20
- run: sudo apt-get install libsqlite3-dev
21
-
22
- - name: Set up Ruby
23
- uses: ruby/setup-ruby@v1
24
- with:
25
- ruby-version: ${{ matrix.ruby }}
26
- bundler-cache: true
27
- env:
28
- RAILS_VERSION: ${{ matrix.rails }}
29
-
30
- - name: Run tests
31
- run: bundle exec rake
32
- env:
33
- RAILS_VERSION: ${{ matrix.rails }}
data/.gitignore DELETED
@@ -1,22 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
- /Gemfile.lock
10
-
11
- /spec/dummy/db/*.sqlite3
12
- /spec/dummy/db/*.sqlite3-journal
13
- /spec/dummy/db/log/*.log
14
- /spec/dummy/tmp/
15
- /spec/dummy/.sass-cache
16
- /spec/dummy/log/
17
-
18
- # rspec failure tracking
19
- .rspec_status
20
-
21
- # Appraisal
22
- gemfiles/*.gemfile.lock
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require rails_helper
data/CODE_OF_CONDUCT.md DELETED
@@ -1,74 +0,0 @@
1
- # Contributor Covenant Code of Conduct
2
-
3
- ## Our Pledge
4
-
5
- In the interest of fostering an open and welcoming environment, we as
6
- contributors and maintainers pledge to making participation in our project and
7
- our community a harassment-free experience for everyone, regardless of age, body
8
- size, disability, ethnicity, gender identity and expression, level of experience,
9
- nationality, personal appearance, race, religion, or sexual identity and
10
- orientation.
11
-
12
- ## Our Standards
13
-
14
- Examples of behavior that contributes to creating a positive environment
15
- include:
16
-
17
- * Using welcoming and inclusive language
18
- * Being respectful of differing viewpoints and experiences
19
- * Gracefully accepting constructive criticism
20
- * Focusing on what is best for the community
21
- * Showing empathy towards other community members
22
-
23
- Examples of unacceptable behavior by participants include:
24
-
25
- * The use of sexualized language or imagery and unwelcome sexual attention or
26
- advances
27
- * Trolling, insulting/derogatory comments, and personal or political attacks
28
- * Public or private harassment
29
- * Publishing others' private information, such as a physical or electronic
30
- address, without explicit permission
31
- * Other conduct which could reasonably be considered inappropriate in a
32
- professional setting
33
-
34
- ## Our Responsibilities
35
-
36
- Project maintainers are responsible for clarifying the standards of acceptable
37
- behavior and are expected to take appropriate and fair corrective action in
38
- response to any instances of unacceptable behavior.
39
-
40
- Project maintainers have the right and responsibility to remove, edit, or
41
- reject comments, commits, code, wiki edits, issues, and other contributions
42
- that are not aligned to this Code of Conduct, or to ban temporarily or
43
- permanently any contributor for other behaviors that they deem inappropriate,
44
- threatening, offensive, or harmful.
45
-
46
- ## Scope
47
-
48
- This Code of Conduct applies both within project spaces and in public spaces
49
- when an individual is representing the project or its community. Examples of
50
- representing a project or community include using an official project e-mail
51
- address, posting via an official social media account, or acting as an appointed
52
- representative at an online or offline event. Representation of a project may be
53
- further defined and clarified by project maintainers.
54
-
55
- ## Enforcement
56
-
57
- Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
- reported by contacting the project team at TODO: Write your email address. All
59
- complaints will be reviewed and investigated and will result in a response that
60
- is deemed necessary and appropriate to the circumstances. The project team is
61
- obligated to maintain confidentiality with regard to the reporter of an incident.
62
- Further details of specific enforcement policies may be posted separately.
63
-
64
- Project maintainers who do not follow or enforce the Code of Conduct in good
65
- faith may face temporary or permanent repercussions as determined by other
66
- members of the project's leadership.
67
-
68
- ## Attribution
69
-
70
- This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
- available at [http://contributor-covenant.org/version/1/4][version]
72
-
73
- [homepage]: http://contributor-covenant.org
74
- [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile DELETED
@@ -1,7 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in inertia-rails.gemspec
4
- gemspec
5
-
6
- version = ENV["RAILS_VERSION"] || "7.1"
7
- gem "rails", "~> #{version}.0"
data/Rakefile DELETED
@@ -1,6 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new(:spec)
5
-
6
- task :default => :spec
data/bin/console DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "inertia_rails/rails"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
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
@@ -1,37 +0,0 @@
1
- lib = File.expand_path("lib", __dir__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require "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 = ["brain@bellawatt.com", "brandon@bellawatt.com", "eugene@bellawatt.com"]
10
-
11
- spec.summary = %q{Inertia adapter for Rails}
12
- spec.homepage = "https://github.com/inertiajs/inertia-rails"
13
- spec.license = "MIT"
14
-
15
- spec.metadata["homepage_uri"] = spec.homepage
16
- spec.metadata["source_code_uri"] = spec.homepage
17
- spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
18
-
19
- # Specify which files should be added to the gem when it is released.
20
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
- end
24
- spec.bindir = "exe"
25
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
- spec.require_paths = ["lib"]
27
-
28
- spec.add_runtime_dependency "railties", '>= 5'
29
-
30
- spec.add_development_dependency "bundler", "~> 2.0"
31
- spec.add_development_dependency "rake", "~> 13.0"
32
- spec.add_development_dependency "rspec-rails", "~> 4.0"
33
- spec.add_development_dependency "rails-controller-testing"
34
- spec.add_development_dependency "sqlite3"
35
- spec.add_development_dependency "responders"
36
- spec.add_development_dependency "debug"
37
- end