Skip to content

Raise when setting unknown config.view_component options - #2755

Merged
joelhawksley merged 3 commits into
mainfrom
strict-config-options
Oct 29, 2026
Merged

joelhawksley merged 3 commits into
mainfrom
strict-config-options

Conversation

@finalburn

Copy link
Copy Markdown
Collaborator

Fixes #2754.

What

config.view_component, config.view_component.previews and config.view_component.generate were plain ActiveSupport::OrderedOptions, so setting any key worked and nothing read it. Options renamed or removed in 4.0 (preview_controller, test_controller, …) and typos (instrumentaton_enabled) were silently ignored.

They're now ViewComponent::Config::Options, a subclass that raises ViewComponent::UnknownConfigOptionError when an unknown option is set, at the line in the app's config that sets it:

`config.view_component.preview_controller` was renamed to `config.view_component.previews.controller` in ViewComponent 4.0.
`config.view_component.test_controller` was removed in ViewComponent 4.0. Use the `vc_test_controller_class` test helper instead.
`config.view_component.instrumentaton_enabled` isn't a ViewComponent configuration option. Valid options are: generate, previews, instrumentation_enabled.

The 4.0 renames and removals live in frozen constants on ViewComponent::Config (RENAMED_OPTIONS, RENAMED_GENERATE_OPTIONS, REMOVED_OPTIONS). Reading an unknown option still returns nil, so gems that check old keys don't break at boot.

Future renames

Options takes a deprecated: map so a future rename can warn before it raises:

  1. In a minor release, add the new option and map the old name to its path in deprecated. Setting or reading the old name emits a ViewComponent::Deprecation warning and uses the new option.
  2. In the next major, move the old name to the renamed/removed constants so that setting it raises.

deprecated is empty for now. A test fails once the major version reaches DEPRECATION_HORIZON while any deprecated options remain.

Things to note

  • Apps that still set dead keys will now fail to boot until they remove them. That's the intent, but it's a behaviour change in a minor release.

Testing

New tests in test/sandbox/test/config_test.rb. I couldn't run the full suite locally: bundle install fails because rubydex and cruise need a Rust toolchain. Instead I ran config_test.rb against the installed ActiveSupport with a stub test_helper, which covers all the new tests and test_all_methods_are_documented, and standardrb lib test passes. The full suite will run in CI.

🤖 Generated with Claude Code

@czj

czj commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Thanks for picking this up so quickly, @finalburn! I ran the full suite on this branch (Ruby 4.0.9, Rails 8.1): all green, apart from one flaky system test that passes on its own.

On the "overcomplicated" part, two ideas to slim it down:

  • Drop the deprecated: mechanism for now. Nothing uses it yet, and it's about half the diff. It can come with the next rename that needs it.
  • Merge RENAMED_GENERATE_OPTIONS into RENAMED_OPTIONS. It only repeats component_parent_class.

One small gap: config.view_component.previews = ActiveSupport::OrderedOptions.new replaces the strict options with a plain one, so typos under it go unnoticed again. Probably rare, maybe worth a guard.

Happy to help if you'd like!

@joelhawksley
joelhawksley force-pushed the strict-config-options branch from 753e608 to 4ff6843 Compare October 29, 2026 17:16
@joelhawksley
joelhawksley marked this pull request as ready for review October 29, 2026 17:17
finalburn and others added 3 commits October 29, 2026 11:18
`config.view_component` and its `previews` and `generate` namespaces were
plain `ActiveSupport::OrderedOptions`, so any key could be set and options
renamed or removed in 4.0 (such as `preview_controller`) were silently
ignored, as were typos.

They're now `ViewComponent::Config::Options`, which raises
`ViewComponent::UnknownConfigOptionError` when an unknown option is set.
For options renamed or removed in 4.0, the error names the replacement.
Reading an unknown option still returns `nil`.

`Options` also supports deprecating an option before it starts raising:
an old name listed in `deprecated` warns and forwards to its replacement,
and a test fails once the deprecation horizon is reached while any
deprecated options remain.

Fixes #2754

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@joelhawksley
joelhawksley force-pushed the strict-config-options branch from 4ff6843 to 5dba83c Compare October 29, 2026 17:19
@joelhawksley
joelhawksley requested a max review from Copilot October 29, 2026 17:19
@joelhawksley
joelhawksley merged commit d42d85c into main Oct 29, 2026
30 checks passed
@joelhawksley
joelhawksley deleted the strict-config-options branch October 29, 2026 17:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Inherited Hash mutation methods bypass validation and namespace wrapping, allowing removed keys and unvalidated nested configuration to be accepted silently.

3 open issues
What changed in this PR

This PR adds fail-fast validation to ViewComponent’s Rails configuration, helping applications catch obsolete settings and typos instead of silently ignoring them.

Changes:

  • Introduces validated configuration options with migration guidance for settings renamed or removed in 4.0.
  • Preserves validation when replacing nested configuration with hashes, while keeping unknown reads non-raising.
  • Adds regression tests and documents the behavior change.
File Description
test/​sandbox/​test/​config_test.rb Tests valid settings, rejected keys, unknown reads, and nested replacements.
lib/​view_component/​errors.rb Adds configuration errors with replacement guidance and valid-option lists.
lib/​view_component/​config.rb Introduces validated options and mappings for obsolete settings.
docs/​CHANGELOG.md Documents stricter configuration validation.

Review effort: Max

@removed = removed
end

def []=(key, value)
"#{Options::ROOT_NAMESPACE}.generate",
%i[path preview_path parent_class sidecar stimulus_controller typescript locale distinct_locale_files preview use_component_path_for_rspec_tests],
default: false,
renamed: RENAMED_OPTIONS
})
options = Options.new(
Options::ROOT_NAMESPACE,
%i[generate previews instrumentation_enabled],
@finalburn

Copy link
Copy Markdown
Collaborator Author

Thanks for wrapping this one up @joelhawksley!

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.

Config keys removed or renamed in 4.0 are silently ignored

4 participants