inertia-rage 0.0.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 +7 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +218 -0
- data/Rakefile +12 -0
- data/lib/inertia/configuration.rb +110 -0
- data/lib/inertia/controller_helpers.rb +159 -0
- data/lib/inertia/frontend.rb +161 -0
- data/lib/inertia/inertia.rb +87 -0
- data/lib/inertia/middleware/assets.rb +40 -0
- data/lib/inertia/middleware/version.rb +36 -0
- data/lib/inertia/props.rb +31 -0
- data/lib/inertia/protocol_builder.rb +209 -0
- data/lib/inertia/rage_extension.rb +48 -0
- data/lib/inertia/renderer.rb +69 -0
- data/lib/inertia/request_context.rb +85 -0
- data/lib/inertia/rspec.rb +171 -0
- data/lib/inertia/version.rb +5 -0
- data/lib/inertia/vite_dev_server.rb +27 -0
- data/lib/inertia-rage.rb +1 -0
- metadata +77 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1a6b20a9b2fd94ff4fa3e0595f894f95c6fb59b8ea09f5565359c814bc755ce3
|
|
4
|
+
data.tar.gz: a3368121cb820f940c09199ad6ef3aed2006c5aeb5f7c41b00af848f26307eb5
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1cb1310953698b5fd1a5274ba43eda6ceca9b7abaf5dcf850b9e589646638f8c4182f2d2694b852fd08a375744e37d3de2fd99fe5904316ed92701687f658352
|
|
7
|
+
data.tar.gz: 4e55f0e48d08c6bd63be5ea53b79659564bc20ee012ac27662595c89b0102888aa6ad06e87d18fba365a26d44a2c02b9de3f92512699fcc1cff07bdae81605f5
|
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--require spec_helper
|
data/.yardopts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--no-private
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"inertia-rage" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["developers@rage-rb.dev"](mailto:"developers@rage-rb.dev").
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Roman Samoilov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
<p align="center"><img height="200" alt="logo" src="https://github.com/user-attachments/assets/470e237b-f947-47f1-882c-1b7191709f50" /></p>
|
|
2
|
+
|
|
3
|
+
# Inertia Rage
|
|
4
|
+
|
|
5
|
+
The official [Inertia.js](https://inertiajs.com) adapter for the [Rage](https://github.com/rage-rb/rage) framework.
|
|
6
|
+
|
|
7
|
+
This gem handles Inertia page responses, provides flexible prop types, and integrates seamlessly with Vite.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Create a Rage app and add the gem:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
rage new my-app -d postgresql
|
|
15
|
+
bundle add inertia-rage
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Create a frontend project in any directory within your project's root or `app` folder:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
npm create vite@latest app/frontend
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Install the required packages and initialize your Inertia app. Refer to the Inertia documentation for [client-side setup](https://inertiajs.com/docs/v3/installation/client-side-setup).
|
|
25
|
+
|
|
26
|
+
<details>
|
|
27
|
+
|
|
28
|
+
<summary>If you are using React</summary>
|
|
29
|
+
|
|
30
|
+
Vite's React template uses `root` as the container element id, but Inertia expects `app`. Update `index.html` accordingly:
|
|
31
|
+
|
|
32
|
+
```diff
|
|
33
|
+
<body>
|
|
34
|
+
- <div id="root"></div>
|
|
35
|
+
+ <div id="app"></div>
|
|
36
|
+
<script type="module" src="/src/main.ts"></script>
|
|
37
|
+
</body>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
</details>
|
|
41
|
+
|
|
42
|
+
Start the app:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
rage s
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Rage automatically starts the Vite dev server in development and pre-builds assets in other environments. In most cases, `rage s` is all you need to run your app.
|
|
49
|
+
|
|
50
|
+
## Rendering
|
|
51
|
+
|
|
52
|
+
Use `render inertia:` to render Inertia responses in your controller actions:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
class PostsController < ApplicationController
|
|
56
|
+
def index
|
|
57
|
+
render inertia: "Posts/Index", props: { posts: current_user.posts }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Rage can also infer the component name from the current controller and action. The following will render the `Posts/Index` component:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
class PostsController < ApplicationController
|
|
66
|
+
def index
|
|
67
|
+
render inertia: { posts: current_user.posts }
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Redirects are supported via `redirect_to`, `redirect_back`, and `redirect_back_or_to`:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
class PostsController < ApplicationController
|
|
76
|
+
def create
|
|
77
|
+
Post.create!(post_params)
|
|
78
|
+
redirect_back fallback_location: "/"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Props
|
|
84
|
+
|
|
85
|
+
Inertia Rage supports several prop types to optimize data loading and improve performance.
|
|
86
|
+
|
|
87
|
+
### Lazy Props
|
|
88
|
+
|
|
89
|
+
Define lazy props using procs:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
render inertia: {
|
|
93
|
+
user:,
|
|
94
|
+
posts: -> { user.posts }
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Lazy props are evaluated on the initial page load just like regular props. The difference emerges during partial reloads: lazy props are only evaluated when explicitly requested. For example, if a partial reload requests only `user`, the `posts` query is skipped entirely.
|
|
99
|
+
|
|
100
|
+
### Deferred Props
|
|
101
|
+
|
|
102
|
+
Deferred props are excluded from the initial page load and fetched automatically in a subsequent request:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
render inertia: {
|
|
106
|
+
user:,
|
|
107
|
+
comments: Inertia.deferred { user.build_comments_tree }
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Use deferred props for expensive operations that would otherwise slow down the initial page load.
|
|
112
|
+
|
|
113
|
+
### Optional Props
|
|
114
|
+
|
|
115
|
+
Optional props are never evaluated during the initial page load. The frontend must explicitly request them:
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
render inertia: {
|
|
119
|
+
user:,
|
|
120
|
+
last_posted_at: Inertia.optional { user.posts.last.created_at }
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Once Props
|
|
125
|
+
|
|
126
|
+
Once props are cached by the frontend after their first evaluation. On subsequent requests, the cached value is used and the prop is not re-evaluated. Since Inertia resets the cache for once props that are absent from the page, you'll typically want to use them inside `inertia_share` blocks:
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
class ApplicationController < RageController::API
|
|
130
|
+
inertia_share do
|
|
131
|
+
{ permissions: Inertia.once { current_user.permissions } }
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Shared Data
|
|
137
|
+
|
|
138
|
+
Use `inertia_share` to share data across all Inertia responses:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
class ApplicationController < RageController::API
|
|
142
|
+
inertia_share do
|
|
143
|
+
{ has_new_notifications: current_user.notifications.unread.exists? }
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`inertia_share` accepts the same arguments as [`before_action`](https://api.rage-rb.dev/RageController/API#before_action-class_method):
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
class DashboardsController < ApplicationController
|
|
152
|
+
inertia_share if: :user_signed_in?, except: :destroy do
|
|
153
|
+
{ all_dashboards: Dashboard.where.not(user: current_user) }
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Testing with RSpec
|
|
159
|
+
|
|
160
|
+
The gem provides an [`inertia`](https://inertia-api.rage-rb.dev/Inertia/RSpec/TestResponse) helper for testing Inertia responses. Require `inertia/rspec` in your request specs to access it:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
require "inertia/rspec"
|
|
164
|
+
|
|
165
|
+
RSpec.describe UsersController, type: :request do
|
|
166
|
+
it "renders user posts" do
|
|
167
|
+
get "posts"
|
|
168
|
+
|
|
169
|
+
expect(inertia.component).to eq("Posts/Index")
|
|
170
|
+
expect(inertia.props).to have_key(:posts)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Partial reloading is supported via the [`inertia` option](https://inertia-api.rage-rb.dev/Inertia/RSpec/RequestHelpers.html) on the `get` method:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
require "inertia/rspec"
|
|
179
|
+
|
|
180
|
+
RSpec.describe UsersController, type: :request do
|
|
181
|
+
it "renders post comments" do
|
|
182
|
+
get "posts/1", inertia: { only: :comments }
|
|
183
|
+
|
|
184
|
+
expect(inertia.props.keys).to eq(:comments)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Configuration
|
|
190
|
+
|
|
191
|
+
Use `Inertia.configure` to customize the default behavior:
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
Inertia.configure do |config|
|
|
195
|
+
config.build_on_start = false
|
|
196
|
+
config.dev_server.port = 5000
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
See the [API documentation](https://inertia-api.rage-rb.dev/Inertia/Configuration.html) for the complete list of configuration options.
|
|
201
|
+
|
|
202
|
+
## Learn More
|
|
203
|
+
|
|
204
|
+
- [Props API Reference](https://inertia-api.rage-rb.dev/Inertia)
|
|
205
|
+
- [Controller API Reference](https://inertia-api.rage-rb.dev/Inertia/ControllerHelpers.html)
|
|
206
|
+
- [Configuration API Reference](https://inertia-api.rage-rb.dev/Inertia/Configuration.html)
|
|
207
|
+
|
|
208
|
+
## Contributing
|
|
209
|
+
|
|
210
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/rage-rb/inertia-rage. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/rage-rb/inertia-rage/blob/master/CODE_OF_CONDUCT.md).
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
215
|
+
|
|
216
|
+
## Code of Conduct
|
|
217
|
+
|
|
218
|
+
Everyone interacting in the Inertia::Rage project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/rage-rb/inertia-rage/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inertia
|
|
4
|
+
##
|
|
5
|
+
# Stores global configuration options for the Inertia integration.
|
|
6
|
+
#
|
|
7
|
+
# @example Configure via block
|
|
8
|
+
# Inertia.configure do |config|
|
|
9
|
+
# config.frontend_path = "client"
|
|
10
|
+
# config.build_path = "public/dist"
|
|
11
|
+
# config.build_on_start = false
|
|
12
|
+
#
|
|
13
|
+
# config.dev_server.host = "0.0.0.0"
|
|
14
|
+
# config.dev_server.port = 3000
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
class Configuration
|
|
18
|
+
# @private
|
|
19
|
+
def initialize
|
|
20
|
+
@build_on_start = !Rage.env.development?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Set whether to build frontend assets on server start.
|
|
24
|
+
#
|
|
25
|
+
# @param value [Boolean]
|
|
26
|
+
# @see #build_on_start?
|
|
27
|
+
def build_on_start=(value)
|
|
28
|
+
@build_on_start = value
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns whether frontend assets should be built when the server starts.
|
|
32
|
+
#
|
|
33
|
+
# Defaults to `true` in non-development environments.
|
|
34
|
+
#
|
|
35
|
+
# @return [Boolean]
|
|
36
|
+
def build_on_start?
|
|
37
|
+
!!@build_on_start
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns the configured frontend directory path.
|
|
41
|
+
#
|
|
42
|
+
# @return [Pathname, nil]
|
|
43
|
+
# @see #frontend_path=
|
|
44
|
+
def frontend_path
|
|
45
|
+
@frontend_path
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Sets the path to the frontend application directory.
|
|
49
|
+
#
|
|
50
|
+
# When set, this overrides automatic Vite config detection. The path should
|
|
51
|
+
# be relative to the application root.
|
|
52
|
+
#
|
|
53
|
+
# @param path [String] path relative to the application root
|
|
54
|
+
def frontend_path=(path)
|
|
55
|
+
@frontend_path = Rage.root.join(path)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns the configured build output path.
|
|
59
|
+
#
|
|
60
|
+
# @return [Pathname, nil]
|
|
61
|
+
# @see #build_path=
|
|
62
|
+
def build_path
|
|
63
|
+
@build_path
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Sets the path where built frontend assets are located.
|
|
67
|
+
#
|
|
68
|
+
# When set, this overrides the default `dist` directory inside {#frontend_path}.
|
|
69
|
+
# The path should be relative to the application root.
|
|
70
|
+
#
|
|
71
|
+
# Building directly into `public/` is not supported because a root-level
|
|
72
|
+
# `index.html` would intercept all requests to the application's `/` endpoint.
|
|
73
|
+
# Use a nested directory instead, e.g., `public/dist`.
|
|
74
|
+
#
|
|
75
|
+
# @param path [String] path relative to the application root
|
|
76
|
+
# @raise [ArgumentError] if path resolves to the public directory root
|
|
77
|
+
# @note This setting should be paired with updating the `build.outDir` option in your Vite config.
|
|
78
|
+
def build_path=(path)
|
|
79
|
+
@build_path = Rage.root.join(path)
|
|
80
|
+
|
|
81
|
+
if @build_path == Rage.root.join("public")
|
|
82
|
+
raise ArgumentError, "build_path cannot be set to public/; use a nested directory instead, e.g., public/dist"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Returns the dev server configuration.
|
|
87
|
+
#
|
|
88
|
+
# @return [DevServer] the dev server configuration object
|
|
89
|
+
def dev_server
|
|
90
|
+
@dev_server ||= DevServer.new
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
##
|
|
94
|
+
# Stores configuration for the Vite development server.
|
|
95
|
+
#
|
|
96
|
+
class DevServer
|
|
97
|
+
# @return [String] the hostname for the Vite dev server (default: "localhost")
|
|
98
|
+
attr_accessor :host
|
|
99
|
+
|
|
100
|
+
# @return [Integer] the port for the Vite dev server (default: 5173)
|
|
101
|
+
attr_accessor :port
|
|
102
|
+
|
|
103
|
+
# @private
|
|
104
|
+
def initialize
|
|
105
|
+
@host = "localhost"
|
|
106
|
+
@port = 5173
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module Inertia
|
|
6
|
+
##
|
|
7
|
+
# Provides controller helper methods for Inertia.js integration.
|
|
8
|
+
module ControllerHelpers
|
|
9
|
+
module ClassMethods
|
|
10
|
+
# Shares data with all Inertia responses in a controller.
|
|
11
|
+
#
|
|
12
|
+
# This method registers a before_action that evaluates the given block
|
|
13
|
+
# in the controller instance context and merges the returned hash into
|
|
14
|
+
# the shared data. The shared data is automatically included in all
|
|
15
|
+
# Inertia responses rendered by the controller.
|
|
16
|
+
#
|
|
17
|
+
# Multiple `inertia_share` calls are cumulative - each block's data
|
|
18
|
+
# is merged into the existing shared data.
|
|
19
|
+
#
|
|
20
|
+
# @param options [Hash] options passed to `before_action` (e.g., `only:`, `except:`, `if:`, `unless:`)
|
|
21
|
+
# @yield Block evaluated in controller context that returns a Hash of data to share
|
|
22
|
+
# @yieldreturn [Hash] the data to merge into the shared props
|
|
23
|
+
#
|
|
24
|
+
# @example Share data for all actions
|
|
25
|
+
# class ApplicationController < RageController::API
|
|
26
|
+
# inertia_share do
|
|
27
|
+
# { current_user: current_user&.as_json }
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# @example Share data only for specific actions
|
|
32
|
+
# class UsersController < ApplicationController
|
|
33
|
+
# inertia_share only: [:index, :show] do
|
|
34
|
+
# { permissions: current_user.permissions }
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# @example Share data conditionally
|
|
39
|
+
# class DashboardController < ApplicationController
|
|
40
|
+
# inertia_share if: :user_signed_in? do
|
|
41
|
+
# { notifications: current_user.unread_notifications }
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
def inertia_share(**options, &block)
|
|
45
|
+
raise ArgumentError, "inertia_share requires a block" unless block
|
|
46
|
+
|
|
47
|
+
before_action(**options) do
|
|
48
|
+
data = instance_eval(&block)
|
|
49
|
+
return unless data
|
|
50
|
+
|
|
51
|
+
if self.inertia_shared_data
|
|
52
|
+
self.inertia_shared_data.merge!(data)
|
|
53
|
+
else
|
|
54
|
+
self.inertia_shared_data = data
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Redirects the client to the specified location.
|
|
61
|
+
#
|
|
62
|
+
# @param location [String] the URL to redirect to
|
|
63
|
+
# @param external [Boolean] whether to force an external (full page) redirect.
|
|
64
|
+
# When `true`, the browser will perform a full page visit instead of an Inertia visit
|
|
65
|
+
#
|
|
66
|
+
# @example Basic redirect
|
|
67
|
+
# redirect_to "/dashboard"
|
|
68
|
+
#
|
|
69
|
+
# @example Force an external redirect
|
|
70
|
+
# redirect_to "https://example.com", external: true
|
|
71
|
+
def redirect_to(location, external: false)
|
|
72
|
+
if external
|
|
73
|
+
headers["x-inertia-location"] = location
|
|
74
|
+
head 409
|
|
75
|
+
return
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
head(request.get? || request.post? ? 302 : 303)
|
|
79
|
+
headers["location"] = location
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Redirects the client back to the referring page, with a fallback location.
|
|
83
|
+
#
|
|
84
|
+
# @param fallback_location [String] the URL to redirect to if there is no referer
|
|
85
|
+
# @param external [Boolean] whether to force an external (full page) redirect.
|
|
86
|
+
# When `true`, the browser will perform a full page visit instead of an Inertia visit
|
|
87
|
+
#
|
|
88
|
+
# @example Redirect back with a fallback
|
|
89
|
+
# redirect_back fallback_location: "/dashboard"
|
|
90
|
+
#
|
|
91
|
+
# @see #redirect_back_or_to
|
|
92
|
+
def redirect_back(fallback_location:, external: false)
|
|
93
|
+
redirect_back_or_to fallback_location, external:
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Redirects the client back to the referring page, or to the specified fallback location.
|
|
97
|
+
#
|
|
98
|
+
# @param fallback_location [String] the URL to redirect to if there is no referer
|
|
99
|
+
# @param external [Boolean] whether to force an external (full page) redirect.
|
|
100
|
+
# When `true`, the browser will perform a full page visit instead of an Inertia visit
|
|
101
|
+
#
|
|
102
|
+
# @example Redirect back or to a fallback
|
|
103
|
+
# redirect_back_or_to "/dashboard"
|
|
104
|
+
#
|
|
105
|
+
# @example Force an external redirect
|
|
106
|
+
# redirect_back_or_to "/dashboard", external: true
|
|
107
|
+
def redirect_back_or_to(fallback_location, external: false)
|
|
108
|
+
referer = request.env["HTTP_REFERER"]
|
|
109
|
+
|
|
110
|
+
if referer
|
|
111
|
+
redirect_to referer, external:
|
|
112
|
+
else
|
|
113
|
+
redirect_to fallback_location, external:
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @private
|
|
118
|
+
def self.included(klass)
|
|
119
|
+
klass.before_action :protect_from_csrf
|
|
120
|
+
klass.attr_accessor :inertia_shared_data
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
# Appends additional request information to the log payload.
|
|
126
|
+
#
|
|
127
|
+
# This method is called by the framework to enrich log entries with
|
|
128
|
+
# request parameters. Parameters are only included in development mode
|
|
129
|
+
# to avoid logging sensitive data in production.
|
|
130
|
+
def append_info_to_payload(context)
|
|
131
|
+
context[:params] = params if Rage.env.development?
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def protect_from_csrf
|
|
135
|
+
# safe methods are always allowed
|
|
136
|
+
method = request.env["REQUEST_METHOD"]
|
|
137
|
+
return if method == "GET" || method == "HEAD" || method == "OPTIONS"
|
|
138
|
+
|
|
139
|
+
sec_fetch_site = request.env["HTTP_SEC_FETCH_SITE"]
|
|
140
|
+
return if sec_fetch_site == "same-origin" || sec_fetch_site == "none"
|
|
141
|
+
|
|
142
|
+
# check the Origin header
|
|
143
|
+
if sec_fetch_site.nil?
|
|
144
|
+
origin = request.env["HTTP_ORIGIN"]
|
|
145
|
+
# either the request is same-origin or not a browser request
|
|
146
|
+
return unless origin
|
|
147
|
+
|
|
148
|
+
begin
|
|
149
|
+
origin_host = URI(origin).authority
|
|
150
|
+
return if origin_host == request.env["HTTP_HOST"]
|
|
151
|
+
rescue URI::InvalidURIError
|
|
152
|
+
# fallthrough
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
render plain: "CSRF rejected", status: 403
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|