-
Notifications
You must be signed in to change notification settings - Fork 44
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
base: master
Are you sure you want to change the base?
Changes from 12 commits
2e8fc70
b5187be
c5624be
20e9671
a153301
967fa4a
4d2468d
f80603e
e4700d0
a780760
c0d938a
005de89
187e45f
48a0994
f8a6663
5fb2df2
46626e4
7f84110
39710b7
05b211d
ce859b2
2aacfed
c747bad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,3 +20,6 @@ | |
|
||
# Appraisal | ||
gemfiles/*.gemfile.lock | ||
|
||
# Local files, such as .env.development.local | ||
*.local |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,13 @@ | ||
#!/usr/bin/env ruby | ||
|
||
require "bundler/setup" | ||
require "inertia_rails/rails" | ||
|
||
# You can add fixtures and/or initialization code here to make experimenting | ||
# with your gem easier. You can also use a different console, if you like. | ||
require "pathname" | ||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", | ||
Pathname.new(__FILE__).realpath) | ||
|
||
# (If you use this, don't forget to add pry to your Gemfile!) | ||
# require "pry" | ||
# Pry.start | ||
require "rubygems" | ||
require "bundler/setup" | ||
require "rails/all" | ||
require "inertia_rails" | ||
|
||
require "irb" | ||
IRB.start(__FILE__) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# frozen_string_literal: true | ||
|
||
module InertiaRails | ||
class Configuration | ||
DEFAULTS = { | ||
default_render: false, | ||
|
||
# Let Rails decide which layout should be used based on the controller configuration. | ||
layout: true, | ||
|
||
deep_merge_shared_data: false, | ||
ssr_enabled: false, | ||
ssr_url: 'http://localhost:13714', | ||
version: nil, | ||
}.freeze | ||
|
||
OPTION_NAMES = DEFAULTS.keys.freeze | ||
|
||
protected attr_reader :controller | ||
protected attr_reader :options | ||
|
||
def initialize(controller: nil, **attrs) | ||
@controller = controller | ||
@options = attrs.extract!(*OPTION_NAMES) | ||
|
||
unless attrs.empty? | ||
raise ArgumentError, "Unknown options for #{self.class}: #{attrs.keys}" | ||
end | ||
end | ||
|
||
def bind_controller(controller) | ||
Configuration.new(**@options, controller: controller) | ||
end | ||
|
||
def to_h | ||
@options.to_h | ||
end | ||
|
||
def merge(config) | ||
Configuration.new(**@options.merge(config.options)) | ||
end | ||
|
||
OPTION_NAMES.each do |option| | ||
define_method(option) { | ||
evaluate_option @options[option] | ||
} | ||
define_method("#{option}=") { |value| | ||
@options[option] = value | ||
} | ||
end | ||
|
||
def self.default | ||
new(**DEFAULTS) | ||
end | ||
|
||
private | ||
|
||
def evaluate_option(value) | ||
return value unless value.respond_to?(:call) | ||
return value.call unless controller | ||
controller.instance_exec(&value) | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,33 +5,25 @@ module Controller | |
extend ActiveSupport::Concern | ||
|
||
included do | ||
helper_method :inertia_headers | ||
|
||
before_action do | ||
error_sharing = proc do | ||
# :inertia_errors are deleted from the session by the middleware | ||
if @_request && session[:inertia_errors].present? | ||
{ errors: session[:inertia_errors] } | ||
else | ||
{} | ||
end | ||
end | ||
|
||
@_inertia_shared_plain_data ||= {} | ||
@_inertia_shared_blocks ||= [error_sharing] | ||
@_inertia_html_headers ||= [] | ||
end | ||
|
||
after_action do | ||
cookies['XSRF-TOKEN'] = form_authenticity_token unless !protect_against_forgery? | ||
cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery? | ||
end | ||
end | ||
|
||
module ClassMethods | ||
def inertia_share(hash = nil, &block) | ||
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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Addressed in #137 |
||
@inertia_shared_data ||= [] | ||
@inertia_shared_data << attrs unless attrs.empty? | ||
@inertia_shared_data << block if block | ||
end | ||
|
||
def inertia_config(**attrs) | ||
config = InertiaRails::Configuration.new(**attrs) | ||
|
||
if @inertia_configuration | ||
@inertia_configuration.merge!(config) | ||
else | ||
@inertia_configuration = config | ||
end | ||
end | ||
|
||
|
@@ -41,28 +33,25 @@ def use_inertia_instance_props | |
@_inertia_skip_props = view_assigns.keys + ['_inertia_skip_props'] | ||
end | ||
end | ||
end | ||
|
||
def inertia_headers | ||
@_inertia_html_headers.join.html_safe | ||
end | ||
def _inertia_configuration | ||
config = superclass.try(:_inertia_configuration) || ::InertiaRails.configuration | ||
@inertia_configuration ? config.merge(@inertia_configuration) : config | ||
end | ||
|
||
def inertia_headers=(value) | ||
@_inertia_html_headers = value | ||
def _inertia_shared_data | ||
[*superclass.try(:_inertia_shared_data), *@inertia_shared_data] | ||
end | ||
end | ||
|
||
def default_render | ||
if InertiaRails.default_render? | ||
if inertia_configuration.default_render | ||
render(inertia: true) | ||
else | ||
super | ||
end | ||
end | ||
|
||
def shared_data | ||
(@_inertia_shared_plain_data || {}).merge(evaluated_blocks) | ||
end | ||
|
||
def redirect_to(options = {}, response_options = {}) | ||
capture_inertia_errors(response_options) | ||
super(options, response_options) | ||
|
@@ -77,19 +66,27 @@ def redirect_back(fallback_location:, allow_other_host: true, **options) | |
) | ||
end | ||
|
||
private | ||
|
||
def inertia_view_assigns | ||
return {} unless @_inertia_instance_props | ||
view_assigns.except(*@_inertia_skip_props) | ||
end | ||
|
||
private | ||
def inertia_configuration | ||
self.class._inertia_configuration.bind_controller(self) | ||
end | ||
|
||
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 commentThe reason will be displayed to describe this comment to others. Learn more. I used to set 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 |
||
|
||
# When the global configuration is not set, let Rails decide which layout | ||
# should be used based on the controller configuration. | ||
layout.nil? ? true : layout | ||
self.class._inertia_shared_data.filter_map { |shared_data| | ||
if shared_data.respond_to?(:call) | ||
instance_exec(&shared_data) | ||
else | ||
shared_data | ||
end | ||
}.reduce(initial_data, &:merge) | ||
end | ||
|
||
def inertia_location(url) | ||
|
@@ -102,9 +99,5 @@ def capture_inertia_errors(options) | |
session[:inertia_errors] = inertia_errors | ||
end | ||
end | ||
|
||
def evaluated_blocks | ||
(@_inertia_shared_blocks || []).map { |block| instance_exec(&block) }.reduce(&:merge) || {} | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,52 +1,20 @@ | ||
# Needed for `thread_mattr_accessor` | ||
require 'active_support/core_ext/module/attribute_accessors_per_thread' | ||
require 'inertia_rails/lazy' | ||
require 'inertia_rails/configuration' | ||
|
||
module InertiaRails | ||
def self.configure | ||
yield(Configuration) | ||
end | ||
|
||
def self.version | ||
Configuration.evaluated_version | ||
end | ||
|
||
def self.layout | ||
Configuration.layout | ||
end | ||
|
||
def self.ssr_enabled? | ||
Configuration.ssr_enabled | ||
end | ||
CONFIGURATION = Configuration.default | ||
|
||
def self.ssr_url | ||
Configuration.ssr_url | ||
end | ||
|
||
def self.default_render? | ||
Configuration.default_render | ||
def self.configure | ||
yield(CONFIGURATION) | ||
end | ||
|
||
def self.deep_merge_shared_data? | ||
Configuration.deep_merge_shared_data | ||
def self.configuration | ||
CONFIGURATION | ||
end | ||
|
||
def self.lazy(value = nil, &block) | ||
InertiaRails::Lazy.new(value, &block) | ||
end | ||
|
||
private | ||
|
||
module Configuration | ||
mattr_accessor(:layout) { nil } | ||
mattr_accessor(:version) { nil } | ||
mattr_accessor(:ssr_enabled) { false } | ||
mattr_accessor(:ssr_url) { 'http://localhost:13714' } | ||
mattr_accessor(:default_render) { false } | ||
mattr_accessor(:deep_merge_shared_data) { false } | ||
|
||
def self.evaluated_version | ||
self.version.respond_to?(:call) ? self.version.call : self.version | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -5,8 +5,7 @@ def initialize(app) | |||||||
end | ||||||||
|
||||||||
def call(env) | ||||||||
InertiaRailsRequest.new(@app, env) | ||||||||
.response | ||||||||
InertiaRailsRequest.new(@app, env).response | ||||||||
end | ||||||||
|
||||||||
class InertiaRailsRequest | ||||||||
|
@@ -58,11 +57,15 @@ def get? | |||||||
request_method == 'GET' | ||||||||
end | ||||||||
|
||||||||
def controller | ||||||||
@env["action_controller.instance"] | ||||||||
end | ||||||||
|
||||||||
def request_method | ||||||||
@env['REQUEST_METHOD'] | ||||||||
end | ||||||||
|
||||||||
def inertia_version | ||||||||
def client_version | ||||||||
@env['HTTP_X_INERTIA_VERSION'] | ||||||||
end | ||||||||
|
||||||||
|
@@ -71,17 +74,15 @@ def inertia_request? | |||||||
end | ||||||||
|
||||||||
def version_stale? | ||||||||
sent_version != saved_version | ||||||||
coerce_version(client_version) != coerce_version(server_version) | ||||||||
end | ||||||||
|
||||||||
def sent_version | ||||||||
return nil if inertia_version.nil? | ||||||||
|
||||||||
InertiaRails.version.is_a?(Numeric) ? inertia_version.to_f : inertia_version | ||||||||
def server_version | ||||||||
controller.send(:inertia_configuration).version | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||||
end | ||||||||
|
||||||||
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 commentThe reason will be displayed to describe this comment to others. Learn more. is that a typo?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, that's the original logic. |
||||||||
end | ||||||||
|
||||||||
def force_refresh(request) | ||||||||
|
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
: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 wasnil
, 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):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!