Skip to content
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

Open
wants to merge 23 commits into
base: master
Choose a base branch
from

Conversation

ElMassimo
Copy link
Contributor

@ElMassimo ElMassimo commented Jun 26, 2024

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:

class PostsController < ApplicationController
  inertia_config(
    ssr_enabled: -> { action_name == "index" },
  )
end

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 on before_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.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that a typo?

Suggested change
server_version.is_a?(Numeric) ? version.to_f : version
version.is_a?(Numeric) ? version.to_f : version

Copy link
Contributor Author

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,
Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor

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

Copy link
Contributor Author

@ElMassimo ElMassimo Jun 26, 2024

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.

Copy link
Contributor

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 👍

Copy link
Contributor Author

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 😃

Copy link
Contributor Author

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] %>
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@ElMassimo ElMassimo changed the title refactor: remove all global state while maintaining backwards compatibility feat: add inertia_config to enable controller-specific config Jun 26, 2024
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.
Copy link
Contributor

@PedroAugustoRamalhoDuarte PedroAugustoRamalhoDuarte left a 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
Copy link
Contributor

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?

Copy link
Contributor Author

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).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the response!!

Copy link
Collaborator

@bknoles bknoles Oct 17, 2024

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)

@skryukov skryukov mentioned this pull request Oct 10, 2024
11 tasks
def inertia_layout
layout = ::InertiaRails.layout
def inertia_shared_data
initial_data = session[:inertia_errors].present? ? {errors: session[:inertia_errors]} : {}
Copy link
Contributor

@skryukov skryukov Oct 10, 2024

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

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)
Copy link
Contributor

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #137


InertiaRails.version.is_a?(Numeric) ? inertia_version.to_f : inertia_version
def server_version
controller.send(:inertia_configuration).version
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Controller might be nil (i.e. in case of 404 error), I think this is a valid fix:

Suggested change
controller.send(:inertia_configuration).version
config = controller ? controller.send(:inertia_configuration) : ::InertiaRails.configuration
config.version

@bknoles
Copy link
Collaborator

bknoles commented Oct 15, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants