-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add inertia_config
to enable controller-specific config
#121
Conversation
With this alone, the same Rails app could serve different Inertia apps, setting a different version for each one in base controllers.
def saved_version | ||
InertiaRails.version.is_a?(Numeric) ? InertiaRails.version.to_f : InertiaRails.version | ||
def coerce_version(version) | ||
server_version.is_a?(Numeric) ? version.to_f : version |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is that a typo?
server_version.is_a?(Numeric) ? version.to_f : version | |
version.is_a?(Numeric) ? version.to_f : version |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, that's the original logic.
default_render: false, | ||
|
||
# Let Rails decide which layout should be used based on the controller configuration. | ||
layout: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that's a breaking change, since current default is nil
.
We can write about that in the changelog, or add a default to Renderer#layout
:
def layout
# we can also raise a warning here?
return true if configuration.layout.nil?
configuration.layout
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made the original change in #86.
The controller method was returning true
if there global was nil
, so the behavior stays the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just tested, if someone sets nil
manually, this PR will brake their code (Rails renders page without layout):
InertiaRails.configure do |config|
config.layout = nil
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unlikely that users are doing that, given it's the default.
That behavior would also be consistent with using layout: false
.
If this is released in a major version, I'd say it's more intuitive if whatever the user configures is not overriden internally.
As an alternative, we can raise if the user sets it to nil
, since it's more likely a mistake.
Finally, I'd suggest removing layout
in the future, it makes more sense to customize using plain Rails in controllers instead, although I'm not proposing we do this now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup, that's a great addition, and since README uses true
as a default, a note in the changelog should be enough, I guess 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't feel strongly about it, we could be strict in backwards compatibility first, and remove it later, just mentioning that I don't think it's as useful a setting after #86.
Thanks for the review 😃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've re-introduced the layout.nil? ? true : layout
check, in order to minimize friction when upgrading. Thanks!
@@ -1,3 +1,3 @@ | |||
<%= inertia_headers %> | |||
<%= local_assigns[:inertia_ssr_head] %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's wrap this in inertia_headers
helper for backwards compatibility?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed 👍
I would add it with a warning that it will be removed.
These are not headers, it's html
for the head
, so the name is inaccurate.
In addition, it does not make sense to call this in the controller, as it wouldn't have been set yet.
Also, rename it to `inertia_ssr_head`, which is a better description.
inertia_config
to enable controller-specific config
Shared data is now frozen to prevent Also, match the method names for instance variables. Updated a test that used to call `inertia_share` inside a `before_action` as that seems like something a user should never do.
d52201a
to
39710b7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ElMassimo Excellent PR, very important to make easy to set up Inertia V2. This #131 task could be easily resolved if this PR is merged
@@ -54,63 +54,6 @@ | |||
it { is_expected.to eq props.merge({ errors: errors }) } | |||
end | |||
|
|||
context 'multithreaded inertia share' do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ElMassimo just for curiosity, why do you remove this test? It was failing or because we do not have this problem anymore by default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not possible to have multi-threading issues anymore, as the objects that can be shared between threads are now frozen (not writable, and thus not susceptible to concurrent writes either).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the response!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fwiw, i pulled the branch and added these tests back (just out of curiosity), and they do still pass.
These were made unnecessary by #111. At the time, I asked @PedroAugustoRamalhoDuarte to keep the specs so we weren't changing spec + implementation at the same time. I'm good with removing them now that we have evidence that refactor passed the original spec. (kind of like adding a new DB column, migrating data, then removing the old column once everything is verified working in production instead of just renaming the column in one shot)
def inertia_layout | ||
layout = ::InertiaRails.layout | ||
def inertia_shared_data | ||
initial_data = session[:inertia_errors].present? ? {errors: session[:inertia_errors]} : {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used to set inertia_share(errors: {})
to mimic Laravel plugin's default, with this change — I always overwrite inertia's errors with my empty hash.
Should we move this to the end of the chain?
def inertia_shared_data
self.class._inertia_shared_data.filter_map { |shared_data|
if shared_data.respond_to?(:call)
instance_exec(&shared_data)
else
shared_data
end
}.reduce({}, &:merge).tap do |data|
data[:errors] = session[:inertia_errors] if session[:inertia_errors].present?
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think what you are describing is only working by accident. Before this change, I believe inertia_share(errors: {})
would have overwritten the errors from the session (exactly as you described). That change moved the error sharing into a block, which gets evaluated after the plain data is computed, allowing your technique to work. I'd call that unintended behavior because presumably this equivalent looking code just wouldn't work:
# This will always overwrite the session errors because this block will be evaluated after the error sharing block from the library
inertia_share do
{errors: {}}
end
IMO, if the application code explicitly sets a prop it should overwrite whatever came out of the library internals, so this PR is actually fixing that.
That said, maybe our default should match the Laravel default and always return something in an errors prop? That seems to be the real thing at issue here. I don't have a strong opinion, but I'd lean towards keeping the default we have since it'd be a potentially breaking change.
I consider that separate from the scope of this PR and won't hold up merging it for this.
(Please let me know if I'm misunderstanding your use case)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, great, I just noticed a breaking change, although it appears to be a fix 😂.
Regarding errors: {}
being the default—that's a significant breaking change, and I would suggest adding a compatibility configuration flag for opting into backward compatibility until the next major release. I also think we should first discuss the possibility of adding this behavior to the protocol. But anyway, that's a separate issue 👍
lib/inertia_rails/controller.rb
Outdated
before_action do | ||
@_inertia_shared_plain_data = @_inertia_shared_plain_data.merge(hash) if hash | ||
@_inertia_shared_blocks = @_inertia_shared_blocks + [block] if block_given? | ||
def inertia_share(**attrs, &block) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a breaking change, should we continue supporting hashes or at least throw a deprecation at first, wdyt?
inertia_share({foo: 'bar'}) # => throws error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in #137
Hey @ElMassimo thanks for this PR. I'm digging into it now (at long last)! Just as a heads up, I resolved a merge conflict in the README and committed it to your branch. Hope that's not stepping on your toes. |
```ruby | ||
class EventsController < ApplicationController | ||
inertia_config( | ||
version: "events-#{InertiaRails.configuration.version}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Local version configuration seems like a bit of a footgun. In this example, won't you trigger a hard refresh every time you visit this controller from a different controller since those versions will always be mismatched?
All the other configuration options make perfect sense to optionally configure per-controller. Maybe we should exclude version
(and only version
) from being locally configured?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
won't you trigger a hard refresh every time you visit this controller from a different controller since those versions will always be mismatched?
You could have different apps, with entirely different Vite bundles, so that would actually make sense and the hard refresh would "make it work". Linking across apps using Inertia navigations isn't "recommended", but it's nice that it would work out of the box.
Having an exception for version
seems unnecessary, it's completely optional to use it, and in some cases it's actually useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah interesting. So, to make sure I understand with a real life example, you might have a "storefront", with one JS bundle, and an "admin interface" with a separate bundle.
And your StorefrontBaseController
and AdminBaseController
would configure different versions. And in that scenario, if you Inertia-navigate from /storefront
to /admin
, a hard refresh isn't a problem.
One could create a mess by locally versioning controllers that target the same JS bundle (as I descriebd), but we are Rails developers and we like our knives sharp :)
I'm on board now. Thanks!
…ge to users explicitly sending a hash into the method
Made a couple tweak commits and finally got this thing merged! Thanks so much @ElMassimo ! Lovely code that adds some really nice features. Thanks also to @skryukov for your sharp eyes to catch a few breaking changes that weren't covered by our test suite. I'm going to follow up with a few specs I created locally to cover those things. I'll get a release out sometime this week in advance of any Inertia v2.0 work. |
Released in 3.3.0! |
Description 📖
This pull request is a complete refactor of the implementation in order to introduce controller-specific configuration.
inertia_config
A new way for controllers to override the global configuration, and provide dynamic values, such as:
Just like
inertia_share
, configuration is inheritable, and subclasses can override values.In addition, all configuration options can now be procs (before only
version
supported this).Performance 🚀
The implementation of
inertia_share
no longer relies onbefore_action
, improving performance when calculating shared data at runtime, and reducing object allocations.As an additional benefit, we no longer run this code unnecessarily for non-Inertia requests.
Notes ✏️
Each commit is a gradual refactoring, and can be read in isolation (and passes all tests). I'd still suggest using Squash and Merge.
We also
freeze
config and shared data to prevent any mutations, ensuring it's safe for multiple threads to access the configuration from the controller class.