inertia_rails-camelize_props 0.1.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +142 -0
- data/Rakefile +12 -0
- data/lib/inertia_rails/camelize_props/controller.rb +22 -0
- data/lib/inertia_rails/camelize_props/key_list.rb +15 -0
- data/lib/inertia_rails/camelize_props/metadata.rb +58 -0
- data/lib/inertia_rails/camelize_props/naming.rb +36 -0
- data/lib/inertia_rails/camelize_props/option.rb +40 -0
- data/lib/inertia_rails/camelize_props/props_resolver.rb +38 -0
- data/lib/inertia_rails/camelize_props/renderer.rb +43 -0
- data/lib/inertia_rails/camelize_props/version.rb +7 -0
- data/lib/inertia_rails/camelize_props.rb +18 -0
- metadata +71 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 54cfcff4bf70fe371ccf072b30e358ca3b172314995265be7348200af42006e7
|
|
4
|
+
data.tar.gz: 43ed1dae335109693b46daf79998492de2b82fef6178814e33e73899d0b14881
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3a4981c3afa1dacfce183dc94919fa4ec174ff6a3431709c5bbddb70ca9e4cabdc41884322c498f40bb9dd2ad0110eedfdcab0b8be93cc03b483896e77324bbd
|
|
7
|
+
data.tar.gz: 0f42ba571549bf7e43a778c48376e3ea547bfa87625058d59b4e76cceb25e59a6f9f0307ceb31ddbc630c5c2993e924937595a1c2b998e24869e10f9d80d25c7
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Takafumi ONAKA
|
|
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,142 @@
|
|
|
1
|
+
# InertiaRails::CamelizeProps
|
|
2
|
+
|
|
3
|
+
Write props in `snake_case` in Rails, receive them in `camelCase` in your components — without breaking merge props, infinite scroll, deferred props, or partial reloads.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
gem "inertia_rails-camelize_props"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
InertiaRails.configure do |config|
|
|
15
|
+
config.camelize_props = true
|
|
16
|
+
end
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or per controller, like any other Inertia Rails option:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
class MediaController < ApplicationController
|
|
23
|
+
inertia_config(camelize_props: true)
|
|
24
|
+
end
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Then keep writing Rails as usual:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
render inertia: "Media/Index", props: {
|
|
31
|
+
media_list: InertiaRails.scroll(pagy) { ... },
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
and refer to props by their camelCase names on the client:
|
|
36
|
+
|
|
37
|
+
```jsx
|
|
38
|
+
<InfiniteScroll data="mediaList">
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## What gets camelized
|
|
42
|
+
|
|
43
|
+
### Responses
|
|
44
|
+
|
|
45
|
+
Keys are converted:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
props: { user_profile: { display_name: "John Doe" } }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
"props": { "userProfile": { "displayName": "John Doe" } }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
That happens at every level, once the props have been resolved, so whatever a block returned is converted like anything else. The keys of the flash are converted too.
|
|
56
|
+
|
|
57
|
+
A page also names props outside of `props`, in the metadata that tells the client what to merge, what to fetch later and what it already holds. Those names are converted to match, which is what keeps those features working — an infinite scroll over `media_list` goes out as:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"props": { "mediaList": [...] },
|
|
62
|
+
"scrollProps": { "mediaList": { "pageName": "cursor" } },
|
|
63
|
+
"mergeProps": ["mediaList"]
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The rest of that metadata is `prependProps`, `deepMergeProps`, `matchPropsOn`, `deferredProps`, `onceProps`, `rescuedProps` and `sharedProps`.
|
|
68
|
+
|
|
69
|
+
What is left alone names no prop: the group a deferred prop is batched under, the query parameter a scroll prop paginates with, and a `once` key you supplied yourself.
|
|
70
|
+
|
|
71
|
+
### Requests
|
|
72
|
+
|
|
73
|
+
A frontend written in camelCase submits camelCase, so the parameters are renamed before the action runs:
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
# the request arrives as { mediaItem: { mediaId: …, createdAt: … } }
|
|
77
|
+
params.expect(media_item: [:media_id, :created_at])
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Only requests carrying `X-Inertia` are renamed.
|
|
81
|
+
|
|
82
|
+
The partial reload headers (`X-Inertia-Partial-Data`, `X-Inertia-Partial-Except`, `X-Inertia-Reset`, `X-Inertia-Except-Once-Props`) are matched against both the camelCase and the original name, so a frontend that still sends `media_list` keeps working.
|
|
83
|
+
|
|
84
|
+
## Caveats
|
|
85
|
+
|
|
86
|
+
- This gem patches private Inertia Rails internals, so an Inertia Rails upgrade may need a new release here.
|
|
87
|
+
- A name with a digit straight after an underscore does not survive the round trip: `address_line_1` goes out as `addressLine1` and comes back as `address_line1`, since case cannot mark a word boundary in front of a digit. Name attributes without an underscore in front of a digit.
|
|
88
|
+
- Every key is a name to be converted, so do not build keys out of user input. A value that needs arbitrary keys can go as a JSON string, or as an array of objects carrying the key as a field.
|
|
89
|
+
- Remove any other case conversion: a key-renaming `prop_transformer`, which still runs before this one, or [inertia-caseshift](https://github.com/skryukov/inertia-caseshift) on the client.
|
|
90
|
+
|
|
91
|
+
## The problem
|
|
92
|
+
|
|
93
|
+
Inertia Rails already has `prop_transformer`, and its documentation suggests using it to camelize keys:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
config.prop_transformer = ->(props:) { props.deep_transform_keys { |key| key.to_s.camelize(:lower) } }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
This silently breaks every feature that refers to a prop *by name*. Only the props hash is transformed, so the response disagrees with itself — here, an infinite scroll over `media_list`:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"props": { "mediaList": [] },
|
|
104
|
+
"scrollProps": { "media_list": { "pageName": "cursor" } },
|
|
105
|
+
"mergeProps": ["media_list"]
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The client looks up `props["media_list"]` to merge into, finds nothing, and replaces the accumulated list instead of appending to it.
|
|
110
|
+
|
|
111
|
+
Prop names appear in four places, and `prop_transformer` reaches only the first:
|
|
112
|
+
|
|
113
|
+
1. the keys of the props hash
|
|
114
|
+
2. the response metadata — `mergeProps`, `scrollProps`, `deferredProps`, `onceProps`, `matchPropsOn`, … — which list prop names *as values*
|
|
115
|
+
3. partial reload request headers (`X-Inertia-Partial-Data` and friends), matched server-side
|
|
116
|
+
4. prop options such as `match_on:` and `append:`, which are strings on the prop object rather than keys of any hash
|
|
117
|
+
|
|
118
|
+
Moving the `prop_transformer` call to a different point does not help. A hash transform can only run *after* evaluation, since the keys inside a lazy prop's return value do not exist before it; but name matching must finish *before* evaluation, because skipping the evaluation of unrequested props is the whole point of partial reloads. Wherever the transform sits, the matching happens on the other side of it — and the `match_on:` strings are never keys of any hash, so no transform of one reaches them at all.
|
|
119
|
+
|
|
120
|
+
The shape is wrong. A `Hash -> Hash` transformer can only hand the adapter a transformed **result**, and a result is the one thing that cannot be reused: the protocol needs the **mapping** itself, a per-key function it can apply to a single name wherever that name turns up. No `Hash -> Hash` function, called at any point, can supply that.
|
|
121
|
+
|
|
122
|
+
## Why this gem exists
|
|
123
|
+
|
|
124
|
+
Rails names things in `snake_case` and JavaScript in `camelCase`, and neither side should have to take up the other's convention to be understood. Something between them has to carry the difference, and the adapter is already there, writing the props and the protocol's names around them.
|
|
125
|
+
|
|
126
|
+
Inertia Rails offers `prop_transformer`, which the problem above rules out. What answers it is not a `Hash -> Hash` transformation but a naming rule: something the adapter holds and applies to one name at a time, wherever a name appears. The Elixir adapter already offers one, behind a [`camelize_props`](https://inertia.hexdocs.pm/readme.html) flag.
|
|
127
|
+
|
|
128
|
+
[Pull request 267](https://redirect.github.com/inertiajs/inertia-rails/pull/267) tried before, reading the problem as merge props being left out rather than as the shape of the transformer, and added a second lambda for them. Inertia Rails means to deprecate `prop_transformer` rather than improve it, which is right: a `Hash -> Hash` function is the wrong shape for this. This gem adds `camelize_props` instead.
|
|
129
|
+
|
|
130
|
+
[inertia-caseshift](https://github.com/skryukov/inertia-caseshift) answers the same question on the client, in both directions, with one implementation serving every backend. This answers it on the server instead.
|
|
131
|
+
|
|
132
|
+
## Development
|
|
133
|
+
|
|
134
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt.
|
|
135
|
+
|
|
136
|
+
## Contributing
|
|
137
|
+
|
|
138
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/onk/inertia_rails-camelize_props.
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module InertiaRails
|
|
4
|
+
module CamelizeProps
|
|
5
|
+
# Renames the parameters a request arrived with to the names the
|
|
6
|
+
# application declared, so that everything downstream — `permit`, `expect`,
|
|
7
|
+
# a nested form, a gem that reads `params` — sees what it expects.
|
|
8
|
+
#
|
|
9
|
+
# Only requests that are part of the Inertia conversation are renamed. The
|
|
10
|
+
# convention holds between this application and the frontend it handed its
|
|
11
|
+
# names to, and nowhere else.
|
|
12
|
+
module Controller
|
|
13
|
+
def self.install(controller = ::ActionController::Base)
|
|
14
|
+
controller.before_action do
|
|
15
|
+
next unless inertia_configuration.camelize_props && request.inertia?
|
|
16
|
+
|
|
17
|
+
params.deep_transform_keys! { |key| key.to_s.underscore }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module InertiaRails
|
|
4
|
+
module CamelizeProps
|
|
5
|
+
# The prop names a partial reload asked for, which arrive in camelCase
|
|
6
|
+
# because that is how the client was given them. A name is looked up in
|
|
7
|
+
# both forms, so that a frontend still naming props as Rails declared
|
|
8
|
+
# them keeps working.
|
|
9
|
+
class KeyList < Array
|
|
10
|
+
def include?(name)
|
|
11
|
+
super || super(Naming.path(name))
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module InertiaRails
|
|
4
|
+
module CamelizeProps
|
|
5
|
+
# Camelizes the names a page carries outside of `props`: the metadata the
|
|
6
|
+
# client reads to decide what to merge, defer or remember, and the flash it
|
|
7
|
+
# reads by key. All of them were chosen by the application and handed to
|
|
8
|
+
# the client, so all of them follow the same convention.
|
|
9
|
+
module Metadata
|
|
10
|
+
PATH_LISTS = %i[mergeProps prependProps deepMergeProps matchPropsOn rescuedProps sharedProps].freeze
|
|
11
|
+
|
|
12
|
+
def self.camelize(page)
|
|
13
|
+
page.to_h { |name, value| [name, camelize_entry(name, value)] }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.camelize_entry(name, value)
|
|
17
|
+
return camelize_paths(value) if PATH_LISTS.include?(name)
|
|
18
|
+
|
|
19
|
+
case name
|
|
20
|
+
when :deferredProps then camelize_grouped_paths(value)
|
|
21
|
+
when :scrollProps then camelize_keyed_paths(value)
|
|
22
|
+
when :onceProps then camelize_once(value)
|
|
23
|
+
when :flash then Naming.deep(value)
|
|
24
|
+
else value
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
private_class_method :camelize_entry
|
|
28
|
+
|
|
29
|
+
def self.camelize_paths(paths)
|
|
30
|
+
paths.map { |path| Naming.path(path) }
|
|
31
|
+
end
|
|
32
|
+
private_class_method :camelize_paths
|
|
33
|
+
|
|
34
|
+
# The group a deferred prop belongs to is named by the application and is
|
|
35
|
+
# not a prop name.
|
|
36
|
+
def self.camelize_grouped_paths(groups)
|
|
37
|
+
groups.transform_values { |paths| camelize_paths(paths) }
|
|
38
|
+
end
|
|
39
|
+
private_class_method :camelize_grouped_paths
|
|
40
|
+
|
|
41
|
+
# A scroll prop's value describes its pagination rather than any prop.
|
|
42
|
+
def self.camelize_keyed_paths(paths)
|
|
43
|
+
paths.transform_keys { |path| Naming.path(path) }
|
|
44
|
+
end
|
|
45
|
+
private_class_method :camelize_keyed_paths
|
|
46
|
+
|
|
47
|
+
# A once key only names a prop when the application did not supply one of
|
|
48
|
+
# its own, in which case it is an opaque identifier and stays as it is.
|
|
49
|
+
def self.camelize_once(once_props)
|
|
50
|
+
once_props.to_h do |once_key, entry|
|
|
51
|
+
path = entry[:prop]
|
|
52
|
+
[once_key.to_s == path.to_s ? Naming.path(once_key) : once_key, entry.merge(prop: Naming.path(path))]
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
private_class_method :camelize_once
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/string/inflections"
|
|
4
|
+
|
|
5
|
+
module InertiaRails
|
|
6
|
+
module CamelizeProps
|
|
7
|
+
# The mapping this gem owns. Every conversion goes through a single name at
|
|
8
|
+
# a time, so the same mapping serves prop keys, the paths in the protocol
|
|
9
|
+
# metadata, and the matching of names arriving in partial reload headers.
|
|
10
|
+
module Naming
|
|
11
|
+
# Preserves the String/Symbol type of the original, so that keys Inertia
|
|
12
|
+
# Rails looks up by symbol keep matching after conversion.
|
|
13
|
+
def self.key(name)
|
|
14
|
+
camelized = name.to_s.camelize(:lower)
|
|
15
|
+
name.is_a?(Symbol) ? camelized.to_sym : camelized
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Prop paths are dot separated ("user.first_name"), and each segment is a
|
|
19
|
+
# name in its own right. Array indices camelize to themselves.
|
|
20
|
+
def self.path(path)
|
|
21
|
+
path.to_s.split(".").map { |segment| segment.camelize(:lower) }.join(".")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.deep(value)
|
|
25
|
+
case value
|
|
26
|
+
when Hash
|
|
27
|
+
value.each_with_object({}) { |(name, nested), result| result[key(name)] = deep(nested) }
|
|
28
|
+
when Array
|
|
29
|
+
value.map { |item| deep(item) }
|
|
30
|
+
else
|
|
31
|
+
value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "inertia_rails"
|
|
4
|
+
|
|
5
|
+
module InertiaRails
|
|
6
|
+
module CamelizeProps
|
|
7
|
+
# Registers `camelize_props` as an Inertia Rails option, so that it can be
|
|
8
|
+
# set globally or per controller like any other one.
|
|
9
|
+
#
|
|
10
|
+
# Inertia Rails builds its option list into frozen constants as it loads,
|
|
11
|
+
# and rejects any option it does not recognize, so adding one means
|
|
12
|
+
# replacing those constants rather than adding to them.
|
|
13
|
+
module Option
|
|
14
|
+
NAME = :camelize_props
|
|
15
|
+
|
|
16
|
+
def self.install(configuration = ::InertiaRails::Configuration)
|
|
17
|
+
return if configuration::OPTION_NAMES.include?(NAME)
|
|
18
|
+
|
|
19
|
+
defaults = configuration::DEFAULTS.merge(NAME => false).freeze
|
|
20
|
+
replace_constant(configuration, :DEFAULTS, defaults)
|
|
21
|
+
replace_constant(configuration, :OPTION_NAMES, defaults.keys.freeze)
|
|
22
|
+
define_accessors(configuration)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.replace_constant(configuration, name, value)
|
|
26
|
+
configuration.send(:remove_const, name)
|
|
27
|
+
configuration.const_set(name, value)
|
|
28
|
+
end
|
|
29
|
+
private_class_method :replace_constant
|
|
30
|
+
|
|
31
|
+
# Inertia Rails defines an accessor for each option it knows about while
|
|
32
|
+
# it loads, which has already happened by now.
|
|
33
|
+
def self.define_accessors(configuration)
|
|
34
|
+
configuration.define_method(NAME) { evaluate_option(options[NAME]) }
|
|
35
|
+
configuration.define_method(:"#{NAME}=") { |value| @options[NAME] = value }
|
|
36
|
+
end
|
|
37
|
+
private_class_method :define_accessors
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "inertia_rails"
|
|
4
|
+
|
|
5
|
+
module InertiaRails
|
|
6
|
+
module CamelizeProps
|
|
7
|
+
# Matches the names a partial reload asked for against the props. The
|
|
8
|
+
# requested names are in camelCase, the paths here are not, so each path is
|
|
9
|
+
# offered in both forms and Inertia Rails' own matching decides.
|
|
10
|
+
module PropsResolver
|
|
11
|
+
def self.install(resolver = ::InertiaRails::PropsResolver)
|
|
12
|
+
resolver.prepend(self)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def explicitly_requested?(path)
|
|
18
|
+
candidates(path).any? { |candidate| super(candidate) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# A prop survives an `only` filter as soon as one of its names was asked
|
|
22
|
+
# for, so it is excluded only when every one of them was left out.
|
|
23
|
+
def excluded_by_only_partial_keys?(path)
|
|
24
|
+
candidates(path).all? { |candidate| super(candidate) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def excluded_by_except_partial_keys?(path)
|
|
28
|
+
candidates(path).any? { |candidate| super(candidate) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def candidates(path)
|
|
32
|
+
return [path] unless partial_keys.is_a?(KeyList)
|
|
33
|
+
|
|
34
|
+
[path, Naming.path(path)].uniq
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "inertia_rails"
|
|
4
|
+
|
|
5
|
+
module InertiaRails
|
|
6
|
+
module CamelizeProps
|
|
7
|
+
# Camelizes the page once the renderer has finished building it. Every way a
|
|
8
|
+
# page leaves Rails is covered from here, because the response body, the
|
|
9
|
+
# `data-page` of the initial HTML and the payload posted to the SSR server
|
|
10
|
+
# are all built from this one hash.
|
|
11
|
+
module Renderer
|
|
12
|
+
def self.install(renderer = ::InertiaRails::Renderer)
|
|
13
|
+
renderer.prepend(self)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def page
|
|
19
|
+
page = super
|
|
20
|
+
return page unless @configuration.camelize_props
|
|
21
|
+
return page if @camelized
|
|
22
|
+
|
|
23
|
+
@camelized = true
|
|
24
|
+
@page = Metadata.camelize(page).merge(props: camelize_page_props(page[:props]))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# The names a partial reload asked for are matched against the props by
|
|
28
|
+
# the resolver, which recognizes this list as holding client-side names.
|
|
29
|
+
def parse_header(name)
|
|
30
|
+
keys = super
|
|
31
|
+
return keys unless @configuration.camelize_props
|
|
32
|
+
|
|
33
|
+
KeyList.new(keys || [])
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The meta prop carries serialized meta tags rather than props, under a
|
|
37
|
+
# name Inertia Rails and the client both address directly.
|
|
38
|
+
def camelize_page_props(props)
|
|
39
|
+
Naming.deep(props.except(:_inertia_meta)).merge(props.slice(:_inertia_meta))
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "camelize_props/version"
|
|
4
|
+
require_relative "camelize_props/naming"
|
|
5
|
+
require_relative "camelize_props/key_list"
|
|
6
|
+
require_relative "camelize_props/metadata"
|
|
7
|
+
require_relative "camelize_props/option"
|
|
8
|
+
require_relative "camelize_props/controller"
|
|
9
|
+
require_relative "camelize_props/props_resolver"
|
|
10
|
+
require_relative "camelize_props/renderer"
|
|
11
|
+
|
|
12
|
+
InertiaRails::CamelizeProps::Option.install
|
|
13
|
+
ActiveSupport.on_load(:action_controller_base) do
|
|
14
|
+
InertiaRails::CamelizeProps::Controller.install(self)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
InertiaRails::CamelizeProps::PropsResolver.install
|
|
18
|
+
InertiaRails::CamelizeProps::Renderer.install
|
metadata
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: inertia_rails-camelize_props
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Takafumi ONAKA
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: inertia_rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 3.22.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 3.22.0
|
|
26
|
+
description: Camelizes prop names on the server, including Inertia's protocol metadata
|
|
27
|
+
and partial reload headers.
|
|
28
|
+
email:
|
|
29
|
+
- takafumi.onaka@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- LICENSE.txt
|
|
35
|
+
- README.md
|
|
36
|
+
- Rakefile
|
|
37
|
+
- lib/inertia_rails/camelize_props.rb
|
|
38
|
+
- lib/inertia_rails/camelize_props/controller.rb
|
|
39
|
+
- lib/inertia_rails/camelize_props/key_list.rb
|
|
40
|
+
- lib/inertia_rails/camelize_props/metadata.rb
|
|
41
|
+
- lib/inertia_rails/camelize_props/naming.rb
|
|
42
|
+
- lib/inertia_rails/camelize_props/option.rb
|
|
43
|
+
- lib/inertia_rails/camelize_props/props_resolver.rb
|
|
44
|
+
- lib/inertia_rails/camelize_props/renderer.rb
|
|
45
|
+
- lib/inertia_rails/camelize_props/version.rb
|
|
46
|
+
homepage: https://github.com/onk/inertia_rails-camelize_props
|
|
47
|
+
licenses:
|
|
48
|
+
- MIT
|
|
49
|
+
metadata:
|
|
50
|
+
allowed_push_host: https://rubygems.org
|
|
51
|
+
homepage_uri: https://github.com/onk/inertia_rails-camelize_props
|
|
52
|
+
source_code_uri: https://github.com/onk/inertia_rails-camelize_props
|
|
53
|
+
rubygems_mfa_required: 'true'
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
require_paths:
|
|
56
|
+
- lib
|
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 3.4.0
|
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubygems_version: 3.6.9
|
|
69
|
+
specification_version: 4
|
|
70
|
+
summary: Server-side snake_case to camelCase prop conversion for Inertia Rails.
|
|
71
|
+
test_files: []
|