Skip to content
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
20 changes: 7 additions & 13 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config --exclude-limit 10000`
# on 2025-04-01 18:59:20 UTC using RuboCop version 1.75.1.
# on 2025-04-10 07:56:29 UTC using RuboCop version 1.75.2.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand Down Expand Up @@ -51,14 +51,13 @@ Layout/ExtraSpacing:
Layout/FirstHashElementIndentation:
EnforcedStyle: consistent

# Offense count: 6
# Offense count: 5
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces.
# SupportedStyles: space, no_space
# SupportedStylesForEmptyBraces: space, no_space
Layout/SpaceBeforeBlockBraces:
Exclude:
- 'spec/dummy/app/controllers/inertia_render_test_controller.rb'
- 'spec/inertia/error_sharing_spec.rb'
- 'spec/inertia/request_spec.rb'

Expand Down Expand Up @@ -106,13 +105,12 @@ Layout/TrailingEmptyLines:
- 'lib/tasks/inertia_rails.rake'
- 'spec/inertia/rails_mimic_spec.rb'

# Offense count: 10
# Offense count: 9
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowInHeredoc.
Layout/TrailingWhitespace:
Exclude:
- 'lib/inertia_rails/rspec.rb'
- 'spec/dummy/app/controllers/inertia_render_test_controller.rb'
- 'spec/dummy/config/environments/test.rb'

# Offense count: 1
Expand Down Expand Up @@ -227,7 +225,7 @@ Style/ExpandPathArguments:
Exclude:
- 'spec/rails_helper.rb'

# Offense count: 65
# Offense count: 63
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: always, always_true, never
Expand All @@ -239,7 +237,6 @@ Style/FrozenStringLiteralComment:
- 'lib/inertia_rails/controller.rb'
- 'lib/inertia_rails/engine.rb'
- 'lib/inertia_rails/helper.rb'
- 'lib/inertia_rails/inertia_rails.rb'
- 'lib/inertia_rails/rspec.rb'
- 'lib/inertia_rails/version.rb'
- 'lib/patches/better_errors.rb'
Expand All @@ -259,7 +256,6 @@ Style/FrozenStringLiteralComment:
- 'spec/dummy/app/controllers/inertia_merge_shared_controller.rb'
- 'spec/dummy/app/controllers/inertia_multithreaded_share_controller.rb'
- 'spec/dummy/app/controllers/inertia_rails_mimic_controller.rb'
- 'spec/dummy/app/controllers/inertia_render_test_controller.rb'
- 'spec/dummy/app/controllers/inertia_responders_test_controller.rb'
- 'spec/dummy/app/controllers/inertia_session_continuity_test_controller.rb'
- 'spec/dummy/app/controllers/inertia_share_test_controller.rb'
Expand Down Expand Up @@ -337,14 +333,13 @@ Style/IfUnlessModifierOfIfUnless:
Exclude:
- 'lib/inertia_rails/controller.rb'

# Offense count: 3
# Offense count: 2
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: line_count_dependent, lambda, literal
Style/Lambda:
Exclude:
- 'spec/dummy/app/controllers/inertia_lambda_shared_props_controller.rb'
- 'spec/dummy/app/controllers/inertia_render_test_controller.rb'
- 'spec/dummy/app/controllers/transformed_inertia_rails_mimic_controller.rb'

# Offense count: 1
Expand Down Expand Up @@ -456,7 +451,7 @@ Style/TrailingCommaInArguments:
- 'spec/dummy/app/controllers/inertia_config_test_controller.rb'
- 'spec/dummy/app/controllers/inertia_rails_mimic_controller.rb'

# Offense count: 22
# Offense count: 12
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyleForMultiline.
# SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma
Expand All @@ -466,13 +461,12 @@ Style/TrailingCommaInHashLiteral:
- 'spec/dummy/app/controllers/inertia_lambda_shared_props_controller.rb'
- 'spec/dummy/app/controllers/inertia_merge_instance_props_controller.rb'
- 'spec/dummy/app/controllers/inertia_merge_shared_controller.rb'
- 'spec/dummy/app/controllers/inertia_render_test_controller.rb'
- 'spec/dummy/config/environments/development.rb'
- 'spec/dummy/config/environments/test.rb'
- 'spec/inertia/response_spec.rb'
- 'spec/inertia/rspec_helper_spec.rb'

# Offense count: 24
# Offense count: 19
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
# URISchemes: http, https
Expand Down
46 changes: 35 additions & 11 deletions docs/guide/merging-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,37 @@ By default, Inertia overwrites props with the same name when reloading a page. H

## Server side

To specify that a prop should be merged, you can use the `merge` method on the prop value.
> `deep_merge` requires `@inertiajs/core` v2.0.8 or higher, and `inertia_rails` v3.8.0 or higher.

To specify that a prop should be merged, use the `merge` or `deep_merge` method on the prop's value.

Use `merge` for merging simple arrays, and `deep_merge` for handling nested objects that include arrays or complex structures, such as pagination objects.

```ruby
class UsersController < ApplicationController
include Pagy::Backend

def index
_pagy, records = pagy(User.all)

render inertia: 'Users/Index', props: {
results: InertiaRails.merge { records },
pagy, records = pagy(User.all)

render inertia: {
# simple array:
users: InertiaRails.merge { records.as_json(...) },
# pagination object:
data: InertiaRails.deep_merge {
{
records: records.as_json(...),
pagy: pagy_metadata(pagy)
}
}
}
end
end
```

On the client side, Inertia detects that this prop should be merged. If the prop returns an array, it will append the response to the current prop value. If it's an object, it will merge the response with the current prop value.
On the client side, Inertia detects that this prop should be merged. If the prop returns an array, it will append the response to the current prop value. If it's an object, it will merge the response with the current prop value. If you have opted to `deepMerge`, Inertia ensures a deep merge of the entire structure.

**Of note:** During the merging process, if the value is an array, the incoming items will be _appended_ to the existing array, not merged by index.
Copy link
Collaborator

Choose a reason for hiding this comment

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

good callout!


You can also combine [deferred props](/guide/deferred-props) with mergeable props to defer the loading of the prop and ultimately mark it as mergeable once it's loaded.

Expand All @@ -29,8 +43,18 @@ class UsersController < ApplicationController
include Pagy::Backend

def index
render inertia: 'Users/Index', props: {
results: InertiaRails.defer(merge: true) { pagy(User.all)[1] },
pagy, records = pagy(User.all)

render inertia: {
# simple array:
users: InertiaRails.defer(merge: true) { records.as_json(...) },
# pagination object:
data: InertiaRails.defer(deep_merge: true) {
{
records: records.as_json(...),
pagy: pagy_metadata(pagy)
}
}
}
end
end
Expand All @@ -48,23 +72,23 @@ The `reset` request option accepts an array of the props keys you would like to
```js
import { router } from '@inertiajs/vue3'

router.reload({ reset: ['results'] })
router.reload({ reset: ['users'] })
```

== React

```js
import { router } from '@inertiajs/react'

router.reload({ reset: ['results'] })
router.reload({ reset: ['users'] })
```

== Svelte 4|Svelte 5

```js
import { router } from '@inertiajs/svelte'

router.reload({ reset: ['results'] })
router.reload({ reset: ['users'] })
```

:::
12 changes: 9 additions & 3 deletions lib/inertia_rails/defer_prop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,22 @@ class DeferProp < IgnoreOnFirstLoadProp

attr_reader :group

def initialize(group: nil, merge: nil, &block)
def initialize(group: nil, merge: nil, deep_merge: nil, &block)
raise ArgumentError, 'Cannot set both `deep_merge` and `merge` to true' if deep_merge && merge

super(&block)

@group = group || DEFAULT_GROUP
@merge = merge
@block = block
@merge = merge || deep_merge
@deep_merge = deep_merge
end

def merge?
@merge
end

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

require 'inertia_rails/base_prop'
require 'inertia_rails/ignore_on_first_load_prop'
require 'inertia_rails/always_prop'
Expand Down Expand Up @@ -35,8 +37,12 @@ def merge(&block)
MergeProp.new(&block)
end

def defer(group: nil, merge: nil, &block)
DeferProp.new(group: group, merge: merge, &block)
def deep_merge(&block)
MergeProp.new(deep_merge: true, &block)
end

def defer(group: nil, merge: nil, deep_merge: nil, &block)
DeferProp.new(group: group, merge: merge, deep_merge: deep_merge, &block)
end
end
end
12 changes: 8 additions & 4 deletions lib/inertia_rails/merge_prop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

module InertiaRails
class MergeProp < BaseProp
def initialize(*)
super
@merge = true
def initialize(deep_merge: false, &block)
super(&block)
@deep_merge = deep_merge
end

def merge?
@merge
true
end

def deep_merge?
@deep_merge
end
end
end
8 changes: 7 additions & 1 deletion lib/inertia_rails/renderer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,14 @@ def page
deferred_props = deferred_props_keys
default_page[:deferredProps] = deferred_props if deferred_props.present?

merge_props = merge_props_keys
all_merge_props = merge_props_keys

deep_merge_props, merge_props = all_merge_props.partition do |key|
@props[key].deep_merge?
end

default_page[:mergeProps] = merge_props if merge_props.present?
default_page[:deepMergeProps] = deep_merge_props if deep_merge_props.present?

default_page
end
Expand Down
27 changes: 15 additions & 12 deletions spec/dummy/app/controllers/inertia_render_test_controller.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# frozen_string_literal: true

class InertiaRenderTestController < ApplicationController

def props
render inertia: 'TestComponent', props: {
name: 'Brandon',
sport: -> { 'hockey' }
sport: -> { 'hockey' },
}
end

Expand All @@ -18,9 +19,9 @@ def except_props
end,
nested: {
first: 'first nested param',
second: 'second nested param'
second: 'second nested param',
},
always: InertiaRails.always { 'always prop' }
always: InertiaRails.always { 'always prop' },
}
end

Expand All @@ -36,10 +37,10 @@ def deeply_nested_props
nested: {
first: 'first nested param',
second: 'second nested param',
evaluated: -> do
evaluated: lambda do
{
first: 'first evaluated nested param',
second: 'second evaluated nested param'
second: 'second evaluated nested param',
}
end,
deeply_nested: {
Expand All @@ -48,10 +49,10 @@ def deeply_nested_props
what_about_nil: nil,
what_about_empty_hash: {},
deeply_nested_always: InertiaRails.always { 'deeply nested always prop' },
deeply_nested_lazy: InertiaRails.lazy { 'deeply nested lazy prop' }
}
deeply_nested_lazy: InertiaRails.lazy { 'deeply nested lazy prop' },
},
},
always: InertiaRails.always { 'always prop' }
always: InertiaRails.always { 'always prop' },
}
end

Expand Down Expand Up @@ -79,7 +80,7 @@ def lazy_props
level: InertiaRails.lazy do
'worse than he believes'
end,
grit: InertiaRails.lazy(->{ 'intense' })
grit: InertiaRails.lazy(-> { 'intense' }),
}
end

Expand All @@ -98,15 +99,17 @@ def always_props
optional: InertiaRails.optional do
'optional prop'
end,
another_optional: InertiaRails.optional { 'another optional prop' }
another_optional: InertiaRails.optional { 'another optional prop' },
}
end

def merge_props
render inertia: 'TestComponent', props: {
merge: InertiaRails.merge { 'merge prop' },
deep_merge: InertiaRails.deep_merge { { deep: 'merge prop' } },
regular: 'regular prop',
deferred_merge: InertiaRails.defer(merge: true) { 'deferred and merge prop' },
deferred_deep_merge: InertiaRails.defer(deep_merge: true) { { deep: 'deferred and merge prop' } },
deferred: InertiaRails.defer { 'deferred' },
}
end
Expand All @@ -118,7 +121,7 @@ def deferred_props
level: InertiaRails.defer do
'worse than he believes'
end,
grit: InertiaRails.defer { 'intense' }
grit: InertiaRails.defer { 'intense' },
}
end
end
Loading