Skip to content

Commit 51e99fb

Browse files
committed
Make accommodations for component-local config
Introduces a `ViewComponent::GlobalConfig` object that will be the source for global configuration going forward. Ideally in the future, this will only house options that universally affect ViewComponent regardless of whether components are sourced from an engine or not (e.g. enabling the capture compatibility patch), and more options can move to a component-local config. For these options, classes inheriting from `ViewComponent::Base` will want to override configuration themselves. This was initially written to support extracting the incoming strict_helpers_enabled? option, but applies to everything.
1 parent 1ad9df2 commit 51e99fb

23 files changed

+59
-45
lines changed

Rakefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ namespace :docs do
9595
require "rails"
9696
require "action_controller"
9797
require "view_component"
98-
ViewComponent::Base.config.view_component_path = "view_component"
98+
ViewComponent::GlobalConfig.view_component_path = "view_component"
9999
require "view_component/docs_builder_component"
100100

101101
error_keys = registry.keys.select { |key| key.to_s.include?("Error::MESSAGE") }.map(&:to_s)

app/controllers/concerns/view_component/preview_actions.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ def previews
5454

5555
# :doc:
5656
def default_preview_layout
57-
ViewComponent::Base.config.default_preview_layout
57+
GlobalConfig.default_preview_layout
5858
end
5959

6060
# :doc:
6161
def show_previews?
62-
ViewComponent::Base.config.show_previews
62+
GlobalConfig.show_previews
6363
end
6464

6565
# :doc:
@@ -102,7 +102,7 @@ def prepend_application_view_paths
102102
end
103103

104104
def prepend_preview_examples_view_path
105-
prepend_view_path(ViewComponent::Base.preview_paths)
105+
prepend_view_path(GlobalConfig.preview_paths)
106106
end
107107
end
108108
end

app/helpers/preview_helper.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def find_template_data_for_preview_source(lookup_context:, template_identifier:)
3535
# Fetch template source via finding it through preview paths
3636
# to accomodate source view when exclusively using templates
3737
# for previews for Rails < 6.1.
38-
all_template_paths = ViewComponent::Base.config.preview_paths.map do |preview_path|
38+
all_template_paths = ViewComponent::GlobalConfig.preview_paths.map do |preview_path|
3939
Dir.glob("#{preview_path}/**/*")
4040
end.flatten
4141

@@ -80,6 +80,6 @@ def prism_language_name_by_template_path(template_file_path:)
8080
# :nocov:
8181

8282
def serve_static_preview_assets?
83-
ViewComponent::Base.config.show_previews && Rails.application.config.public_file_server.enabled
83+
ViewComponent::GlobalConfig.show_previews && Rails.application.config.public_file_server.enabled
8484
end
8585
end
+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<% if @render_args[:component] %>
2-
<% if ViewComponent::Base.config.render_monkey_patch_enabled || Rails.version.to_f >= 6.1 %>
2+
<% if ViewComponent::GlobalConfig.render_monkey_patch_enabled || Rails.version.to_f >= 6.1 %>
33
<%= render(@render_args[:component], @render_args[:args], &@render_args[:block]) %>
44
<% else %>
55
<%= render_component(@render_args[:component], &@render_args[:block]) %>
@@ -8,6 +8,6 @@
88
<%= render template: @render_args[:template], locals: @render_args[:locals] || {} %>
99
<% end %>
1010

11-
<% if ViewComponent::Base.config.show_previews_source %>
11+
<% if ViewComponent::GlobalConfig.show_previews_source %>
1212
<%= preview_source %>
1313
<% end %>

docs/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ nav_order: 5
1010

1111
## main
1212

13+
* Make accommodations for component-local config to be introduced in future.
14+
15+
*Simon Fish*
16+
1317
* Remove JS and CSS docs as they proved difficult to maintain and lacked consensus.
1418

1519
*Joel Hawksley*

lib/rails/generators/abstract_generator.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def file_name
2929
end
3030

3131
def component_path
32-
ViewComponent::Base.config.view_component_path
32+
GlobalConfig.view_component_path
3333
end
3434

3535
def stimulus_controller
@@ -42,15 +42,15 @@ def stimulus_controller
4242
end
4343

4444
def sidecar?
45-
options["sidecar"] || ViewComponent::Base.config.generate.sidecar
45+
options["sidecar"] || GlobalConfig.generate.sidecar
4646
end
4747

4848
def stimulus?
49-
options["stimulus"] || ViewComponent::Base.config.generate.stimulus_controller
49+
options["stimulus"] || GlobalConfig.generate.stimulus_controller
5050
end
5151

5252
def typescript?
53-
options["typescript"] || ViewComponent::Base.config.generate.typescript
53+
options["typescript"] || GlobalConfig.generate.typescript
5454
end
5555
end
5656
end

lib/rails/generators/component/component_generator.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ class ComponentGenerator < Rails::Generators::NamedBase
1313
check_class_collision suffix: "Component"
1414

1515
class_option :inline, type: :boolean, default: false
16-
class_option :locale, type: :boolean, default: ViewComponent::Base.config.generate.locale
16+
class_option :locale, type: :boolean, default: ViewComponent::GlobalConfig.generate.locale
1717
class_option :parent, type: :string, desc: "The parent class for the generated component"
18-
class_option :preview, type: :boolean, default: ViewComponent::Base.config.generate.preview
18+
class_option :preview, type: :boolean, default: ViewComponent::GlobalConfig.generate.preview
1919
class_option :sidecar, type: :boolean, default: false
2020
class_option :stimulus, type: :boolean,
21-
default: ViewComponent::Base.config.generate.stimulus_controller
21+
default: ViewComponent::GlobalConfig.generate.stimulus_controller
2222
class_option :skip_suffix, type: :boolean, default: false
2323

2424
def create_component_file
@@ -42,7 +42,7 @@ def create_component_file
4242
def parent_class
4343
return options[:parent] if options[:parent]
4444

45-
ViewComponent::Base.config.component_parent_class || default_parent_class
45+
ViewComponent::GlobalConfig.component_parent_class || default_parent_class
4646
end
4747

4848
def initialize_signature

lib/rails/generators/locale/component_generator.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class ComponentGenerator < ::Rails::Generators::NamedBase
1212
class_option :sidecar, type: :boolean, default: false
1313

1414
def create_locale_file
15-
if ViewComponent::Base.config.generate.distinct_locale_files
15+
if ViewComponent::GlobalConfig.generate.distinct_locale_files
1616
I18n.available_locales.each do |locale|
1717
create_file destination(locale), translations_hash([locale]).to_yaml
1818
end

lib/rails/generators/preview/component_generator.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ module Preview
44
module Generators
55
class ComponentGenerator < ::Rails::Generators::NamedBase
66
source_root File.expand_path("templates", __dir__)
7-
class_option :preview_path, type: :string, desc: "Path for previews, required when multiple preview paths are configured", default: ViewComponent::Base.config.generate.preview_path
7+
class_option :preview_path, type: :string, desc: "Path for previews, required when multiple preview paths are configured", default: ViewComponent::GlobalConfig.generate.preview_path
88

99
argument :attributes, type: :array, default: [], banner: "attribute"
1010
check_class_collision suffix: "ComponentPreview"
1111

1212
def create_preview_file
13-
preview_paths = ViewComponent::Base.config.preview_paths
13+
preview_paths = ViewComponent::GlobalConfig.preview_paths
1414
optional_path = options[:preview_path]
1515
return if preview_paths.count > 1 && optional_path.blank?
1616

lib/rails/generators/rspec/component_generator.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def create_test_file
1616
private
1717

1818
def spec_component_path
19-
return "spec/components" unless ViewComponent::Base.config.generate.use_component_path_for_rspec_tests
19+
return "spec/components" unless ViewComponent::GlobalConfig.generate.use_component_path_for_rspec_tests
2020

2121
configured_component_path = component_path
2222
if configured_component_path.start_with?("app#{File::SEPARATOR}")

lib/view_component.rb

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module ViewComponent
1313
autoload :ComponentError
1414
autoload :Config
1515
autoload :Deprecation
16+
autoload :GlobalConfig
1617
autoload :InlineTemplate
1718
autoload :Instrumentation
1819
autoload :Preview

lib/view_component/base.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# frozen_string_literal: true
22

33
require "action_view"
4-
require "active_support/configurable"
54
require "view_component/collection"
65
require "view_component/compile_cache"
76
require "view_component/compiler"
@@ -25,7 +24,7 @@ class << self
2524
#
2625
# @return [ActiveSupport::OrderedOptions]
2726
def config
28-
ViewComponent::Config.current
27+
GlobalConfig
2928
end
3029
end
3130

@@ -48,6 +47,8 @@ def config
4847
class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false
4948
self.__vc_strip_trailing_whitespace = false # class_attribute:default doesn't work until Rails 5.2
5049

50+
delegate :component_config, to: :class
51+
5152
attr_accessor :__vc_original_view_context
5253

5354
# Components render in their own view context. Helpers and other functionality
@@ -227,7 +228,6 @@ def controller
227228
# @return [ActionView::Base]
228229
def helpers
229230
raise HelpersCalledBeforeRenderError if view_context.nil?
230-
231231
# Attempt to re-use the original view_context passed to the first
232232
# component rendered in the rendering pipeline. This prevents the
233233
# instantiation of a new view_context via `controller.view_context` which
@@ -550,7 +550,7 @@ def render_template_for(variant = nil, format = nil)
550550
# If Rails application is loaded, removes the first part of the path and the extension.
551551
if defined?(Rails) && Rails.application
552552
child.virtual_path = child.identifier.gsub(
553-
/(.*#{Regexp.quote(ViewComponent::Base.config.view_component_path)})|(\.rb)/, ""
553+
/(.*#{Regexp.quote(GlobalConfig.view_component_path)})|(\.rb)/, ""
554554
)
555555
end
556556

lib/view_component/config.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class << self
1111
alias_method :default, :new
1212

1313
def defaults
14-
ActiveSupport::OrderedOptions.new.merge!({
14+
ActiveSupport::InheritableOptions.new({
1515
generate: default_generate_options,
1616
preview_controller: "ViewComponentsController",
1717
preview_route: "/rails/view_components",

lib/view_component/engine.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
module ViewComponent
88
class Engine < Rails::Engine # :nodoc:
9-
config.view_component = ViewComponent::Config.current
9+
config.view_component = ViewComponent::Config.defaults
1010

1111
if Rails.version.to_f < 8.0
1212
rake_tasks do
@@ -16,8 +16,8 @@ class Engine < Rails::Engine # :nodoc:
1616
initializer "view_component.stats_directories" do |app|
1717
require "rails/code_statistics"
1818

19-
if Rails.root.join(ViewComponent::Base.view_component_path).directory?
20-
Rails::CodeStatistics.register_directory("ViewComponents", ViewComponent::Base.view_component_path)
19+
if Rails.root.join(GlobalConfig.view_component_path).directory?
20+
Rails::CodeStatistics.register_directory("ViewComponents", GlobalConfig.view_component_path)
2121
end
2222

2323
if Rails.root.join("test/components").directory?
@@ -29,7 +29,7 @@ class Engine < Rails::Engine # :nodoc:
2929
initializer "view_component.set_configs" do |app|
3030
options = app.config.view_component
3131

32-
%i[generate preview_controller preview_route show_previews_source].each do |config_option|
32+
%i[generate preview_controller preview_route].each do |config_option|
3333
options[config_option] ||= ViewComponent::Base.public_send(config_option)
3434
end
3535
options.instrumentation_enabled = false if options.instrumentation_enabled.nil?

lib/view_component/global_config.rb

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module ViewComponent
2+
GlobalConfig = (defined?(Rails) && Rails.application) ? Rails.application.config.view_component : Config.defaults # standard:disable Naming/ConstantName
3+
end

lib/view_component/instrumentation.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def render_in(view_context, &block)
2323
private
2424

2525
def notification_name
26-
return "!render.view_component" if ViewComponent::Base.config.use_deprecated_instrumentation_name
26+
return "!render.view_component" if GlobalConfig.use_deprecated_instrumentation_name
2727

2828
"render.view_component"
2929
end

lib/view_component/preview.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def load_previews
109109
private
110110

111111
def preview_paths
112-
Base.preview_paths
112+
ViewComponent::GlobalConfig.preview_paths
113113
end
114114
end
115115
end

lib/view_component/rails/tasks/view_component.rake

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ namespace :view_component do
77
# :nocov:
88
require "rails/code_statistics"
99

10-
if Rails.root.join(ViewComponent::Base.view_component_path).directory?
11-
::STATS_DIRECTORIES << ["ViewComponents", ViewComponent::Base.view_component_path]
10+
if Rails.root.join(ViewComponent::GlobalConfig.view_component_path).directory?
11+
::STATS_DIRECTORIES << ["ViewComponents", ViewComponent::GlobalConfig.view_component_path]
1212
end
1313

1414
if Rails.root.join("test/components").directory?

lib/view_component/test_helpers.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ def with_request_url(full_path, host: nil, method: nil, format: ViewComponent::B
250250
#
251251
# @return [ActionController::Base]
252252
def vc_test_controller
253-
@vc_test_controller ||= __vc_test_helpers_build_controller(Base.test_controller.constantize)
253+
@vc_test_controller ||= __vc_test_helpers_build_controller(GlobalConfig.test_controller.constantize)
254254
end
255255

256256
# Access the request used by `render_inline`:

lib/view_component/use_helpers.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require "view_component/errors"
4+
35
module ViewComponent::UseHelpers
46
extend ActiveSupport::Concern
57

@@ -13,7 +15,7 @@ def use_helper(helper_method, from: nil, prefix: false)
1315

1416
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
1517
def #{helper_method_name}(*args, &block)
16-
raise HelpersCalledBeforeRenderError if view_context.nil?
18+
raise ::ViewComponent::HelpersCalledBeforeRenderError if view_context.nil?
1719
1820
#{define_helper(helper_method: helper_method, source: from)}
1921
end

test/sandbox/test/integration_test.rb

+11-7
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ def test_render_component_in_turbo_stream
579579
expected_response_body = <<~TURBOSTREAM
580580
<turbo-stream action="update" target="area1"><template><span>Hello, world!</span></template></turbo-stream>
581581
TURBOSTREAM
582-
if ViewComponent::Base.config.capture_compatibility_patch_enabled
582+
if ViewComponent::GlobalConfig.capture_compatibility_patch_enabled
583583
assert_equal expected_response_body, response.body
584584
else
585585
assert_not_equal expected_response_body, response.body
@@ -716,22 +716,26 @@ def test_cached_partial
716716
Rails.cache.clear
717717
end
718718

719-
def test_config_options_shared_between_base_and_engine
720-
config_entrypoints = [Rails.application.config.view_component, ViewComponent::Base.config]
721-
2.times do
722-
config_entrypoints.first.yield_self do |config|
719+
def test_globalconfig_is_proxy_for_rails_app_config
720+
config_entrypoints = [
721+
["Rails application config", Rails.application.config.view_component],
722+
["Global config", ViewComponent::GlobalConfig],
723+
["ViewComponent::Base's config", ViewComponent::Base.config]
724+
]
725+
config_entrypoints.permutation(2) do |(first_entrypoint_name, first_entrypoint), (second_entrypoint_name, second_entrypoint)|
726+
first_entrypoint.yield_self do |config|
723727
{
724728
generate: config.generate.dup.tap { |c| c.sidecar = true },
725729
preview_controller: "SomeOtherController",
726730
preview_route: "/some/other/route",
727731
show_previews_source: true
728732
}.each do |option, value|
729733
with_config_option(option, value, config_entrypoint: config) do
730-
assert_equal(config.public_send(option), config_entrypoints.second.public_send(option))
734+
assert_equal(config.public_send(option), second_entrypoint.public_send(option),
735+
"#{first_entrypoint_name} should be the same as #{second_entrypoint_name} for #{option.inspect}")
731736
end
732737
end
733738
end
734-
config_entrypoints.rotate!
735739
end
736740
end
737741

test/sandbox/test/rendering_test.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ def test_render_inline_allocations
1616
MyComponent.ensure_compiled
1717

1818
allocations = (Rails.version.to_f >= 8.0) ?
19-
{"3.5.0" => 117, "3.4.1" => 117, "3.3.7" => 129} :
20-
{"3.3.7" => 120, "3.3.0" => 120, "3.2.6" => 118, "3.1.6" => 118, "3.0.7" => 127}
19+
{"3.5.0" => 117, "3.4.1" => 117, "3.3.7" => 128} :
20+
{"3.3.7" => 119, "3.3.0" => 119, "3.2.6" => 117, "3.1.6" => 117, "3.0.7" => 126}
2121

2222
assert_allocations(**allocations) do
2323
render_inline(MyComponent.new)

test/test_engine/test_helper.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
Rails::Generators.namespace = TestEngine
2424

25-
def with_config_option(option_name, new_value, config_entrypoint: TestEngine::Engine.config.view_component)
25+
def with_config_option(option_name, new_value, config_entrypoint: ViewComponent::GlobalConfig)
2626
old_value = config_entrypoint.public_send(option_name)
2727
config_entrypoint.public_send(:"#{option_name}=", new_value)
2828
yield

0 commit comments

Comments
 (0)