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

Inertia.js v2.0 compatibility #132

Merged
merged 7 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,17 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: ['3.1', '3.2', '3.3']
rails: ['6.1', '7.0', '7.1', '7.2']
ruby: ['3.0', '3.1', '3.2', '3.3']
rails: ['6.1', '7.0', '7.1', '7.2', '8.0']
exclude:
- ruby: '3.0'
rails: '8.0'
- ruby: '3.1'
rails: '8.0'
- ruby: '3.0'
rails: '7.2'
- ruby: '3.1'
rails: '7.2'

runs-on: ubuntu-latest
name: Test against Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }}
Expand Down
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,12 @@ end
}
```

### Lazy Props
### Optional Props

On the front end, Inertia supports the concept of "partial reloads" where only the props requested are returned by the server. Sometimes, you may want to use this flow to avoid processing a particularly slow prop on the intial load. In this case, you can use Lazy props. Lazy props aren't evaluated unless they're specifically requested by name in a partial reload.
On the frontend, Inertia supports the concept of "partial reloads" where only the props requested are returned by the server. Sometimes, you may want to use this flow to avoid processing a particularly slow prop on the initial load. In this case, you can use Optional props. Optional props aren't evaluated unless they're specifically requested by name in a partial reload.

```ruby
inertia_share some_data: InertiaRails.lazy(lambda { some_very_slow_method })

# Using a Ruby block syntax
inertia_share some_data: InertiaRails.lazy { some_very_slow_method }
inertia_share some_data: InertiaRails.optional { some_very_slow_method }
```

### Routing
Expand Down Expand Up @@ -269,6 +266,14 @@ end

__Default__: `false`

#### `encrypt_history`

When enabled, you instruct Inertia to encrypt your app's history, it uses
the browser's built-in [`crypto` api](https://developer.mozilla.org/en-US/docs/Web/API/Crypto)
to encrypt the current page's data before pushing it to the history state.

__Default__: `false`

#### `ssr_enabled` _(experimental)_

Whether to use a JavaScript server to pre-render your JavaScript pages,
Expand Down
2 changes: 2 additions & 0 deletions lib/inertia_rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
props: options[:props],
view_data: options[:view_data],
deep_merge: options[:deep_merge],
encrypt_history: options[:encrypt_history],
clear_history: options[:clear_history],
).render
end

Expand Down
3 changes: 3 additions & 0 deletions lib/inertia_rails/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class Configuration
# controller configuration.
layout: true,

# Whether to encrypt the history state in the client.
encrypt_history: false,

# SSR options.
ssr_enabled: false,
ssr_url: 'http://localhost:13714',
Expand Down
11 changes: 6 additions & 5 deletions lib/inertia_rails/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def default_render
end

def redirect_to(options = {}, response_options = {})
capture_inertia_errors(response_options)
capture_inertia_session_options(response_options)
super
end

Expand Down Expand Up @@ -155,10 +155,11 @@ def inertia_location(url)
head :conflict
end

def capture_inertia_errors(options)
if (inertia_errors = options.dig(:inertia, :errors))
session[:inertia_errors] = inertia_errors.to_hash
end
def capture_inertia_session_options(options)
return unless (inertia = options[:inertia])

session[:inertia_errors] = inertia[:errors].to_hash if inertia[:errors]
session[:inertia_clear_history] = inertia[:clear_history] if inertia[:clear_history]
end
end
end
19 changes: 19 additions & 0 deletions lib/inertia_rails/defer_prop.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module InertiaRails
class DeferProp < IgnoreOnFirstLoadProp
DEFAULT_GROUP = "default"

attr_reader :group

def initialize(group: nil, merge: nil, &block)
@group = group || DEFAULT_GROUP
@merge = merge
@block = block
end

def merge?
@merge
end
end
end
6 changes: 6 additions & 0 deletions lib/inertia_rails/ignore_on_first_load_prop.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

module InertiaRails
class IgnoreOnFirstLoadProp < BaseProp
end
end
16 changes: 16 additions & 0 deletions lib/inertia_rails/inertia_rails.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
require 'inertia_rails/base_prop'
require 'inertia_rails/ignore_on_first_load_prop'
require 'inertia_rails/always_prop'
require 'inertia_rails/lazy_prop'
require 'inertia_rails/optional_prop'
require 'inertia_rails/defer_prop'
require 'inertia_rails/merge_prop'
require 'inertia_rails/configuration'

module InertiaRails
Expand All @@ -19,8 +23,20 @@ def lazy(value = nil, &block)
LazyProp.new(value, &block)
end

def optional(&block)
OptionalProp.new(&block)
end

def always(&block)
AlwaysProp.new(&block)
end

def merge(&block)
MergeProp.new(&block)
end

def defer(group: nil, merge: nil, &block)
DeferProp.new(group: group, merge: merge, &block)
end
end
end
6 changes: 5 additions & 1 deletion lib/inertia_rails/lazy_prop.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# frozen_string_literal: true

module InertiaRails
class LazyProp < BaseProp
class LazyProp < IgnoreOnFirstLoadProp
def initialize(value = nil, &block)
raise ArgumentError, 'You must provide either a value or a block, not both' if value && block

InertiaRails.deprecator.warn(
"`lazy` is deprecated and will be removed in InertiaRails 4.0, use `optional` instead."
)

@value = value
@block = block
end
Expand Down
14 changes: 14 additions & 0 deletions lib/inertia_rails/merge_prop.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

module InertiaRails
class MergeProp < BaseProp
def initialize(*)
super
@merge = true
end

def merge?
@merge
end
end
end
9 changes: 7 additions & 2 deletions lib/inertia_rails/middleware.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module InertiaRails
class Middleware
def initialize(app)
Expand All @@ -20,7 +22,10 @@ def response
request = ActionDispatch::Request.new(@env)

# Inertia errors are added to the session via redirect_to
request.session.delete(:inertia_errors) unless keep_inertia_errors?(status)
unless keep_inertia_session_options?(status)
request.session.delete(:inertia_errors)
request.session.delete(:inertia_clear_history)
end

status = 303 if inertia_non_post_redirect?(status)

Expand All @@ -29,7 +34,7 @@ def response

private

def keep_inertia_errors?(status)
def keep_inertia_session_options?(status)
redirect_status?(status) || stale_inertia_request?
end

Expand Down
4 changes: 4 additions & 0 deletions lib/inertia_rails/optional_prop.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module InertiaRails
class OptionalProp < IgnoreOnFirstLoadProp
end
end
44 changes: 39 additions & 5 deletions lib/inertia_rails/renderer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

require 'net/http'
require 'json'
require_relative "inertia_rails"
require_relative 'inertia_rails'

module InertiaRails
class Renderer
Expand All @@ -12,9 +12,11 @@ class Renderer
:controller,
:props,
:view_data,
:encrypt_history,
:clear_history
)

def initialize(component, controller, request, response, render_method, props: nil, view_data: nil, deep_merge: nil)
def initialize(component, controller, request, response, render_method, props: nil, view_data: nil, deep_merge: nil, encrypt_history: nil, clear_history: nil)
@controller = controller
@configuration = controller.__send__(:inertia_configuration)
@component = resolve_component(component)
Expand All @@ -24,6 +26,8 @@ def initialize(component, controller, request, response, render_method, props: n
@props = props || controller.__send__(:inertia_view_assigns)
@view_data = view_data || {}
@deep_merge = !deep_merge.nil? ? deep_merge : configuration.deep_merge_shared_data
@encrypt_history = !encrypt_history.nil? ? encrypt_history : configuration.encrypt_history
@clear_history = clear_history || controller.session[:inertia_clear_history] || false
end

def render
Expand Down Expand Up @@ -78,7 +82,7 @@ def computed_props
if rendering_partial_component?
partial_keys.none? || key.in?(partial_keys) || prop.is_a?(AlwaysProp)
else
!prop.is_a?(LazyProp)
!prop.is_a?(IgnoreOnFirstLoadProp)
end
end

Expand All @@ -97,12 +101,22 @@ def computed_props
end

def page
{
default_page = {
component: component,
props: computed_props,
url: @request.original_fullpath,
version: configuration.version,
encryptHistory: encrypt_history,
clearHistory: clear_history,
}

deferred_props = deferred_props_keys
default_page[:deferredProps] = deferred_props if deferred_props.present?

merge_props = merge_props_keys
default_page[:mergeProps] = merge_props if merge_props.present?

default_page
end

def deep_transform_values(hash, &block)
Expand All @@ -121,8 +135,28 @@ def drop_partial_except_keys(hash)
end
end

def deferred_props_keys
return if rendering_partial_component?

@props.each_with_object({}) do |(key, prop), result|
(result[prop.group] ||= []) << key if prop.is_a?(DeferProp)
end
end

def merge_props_keys
@props.each_with_object([]) do |(key, prop), result|
if prop.try(:merge?) && reset_keys.exclude?(key)
result << key
end
end
end

def partial_keys
(@request.headers['X-Inertia-Partial-Data'] || '').split(',').compact.map(&:to_sym)
@partial_keys ||= (@request.headers['X-Inertia-Partial-Data'] || '').split(',').compact.map(&:to_sym)
end

def reset_keys
(@request.headers['X-Inertia-Reset'] || '').split(',').compact.map(&:to_sym)
end

def partial_except_keys
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class InertiaConfigTestController < ApplicationController
ssr_url: "http://localhost:7777",
layout: "test",
version: "1.0",
encrypt_history: false,
)

# Test that modules included in the same class can also call it.
Expand Down
25 changes: 25 additions & 0 deletions spec/dummy/app/controllers/inertia_encrypt_history_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class InertiaEncryptHistoryController < ApplicationController
inertia_config(
encrypt_history: -> { action_name != "default_config" }
)

def default_config
render inertia: 'TestComponent'
end

def encrypt_history
render inertia: 'TestComponent'
end

def override_config
render inertia: 'TestComponent', encrypt_history: false
end

def clear_history
render inertia: 'TestComponent', clear_history: true
end

def clear_history_after_redirect
redirect_to :empty_test, inertia: {clear_history: true}
end
end
Loading