Skip to content

Commit

Permalink
Make accommodations for component-local config
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
boardfish committed Oct 21, 2024
1 parent 3521959 commit 62e0960
Show file tree
Hide file tree
Showing 23 changed files with 58 additions and 44 deletions.
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ namespace :docs do
require "rails"
require "action_controller"
require "view_component"
ViewComponent::Base.config.view_component_path = "view_component"
ViewComponent::GlobalConfig.view_component_path = "view_component"
require "view_component/docs_builder_component"

error_keys = registry.keys.select { |key| key.to_s.include?("Error::MESSAGE") }.map(&:to_s)
Expand Down
6 changes: 3 additions & 3 deletions app/controllers/concerns/view_component/preview_actions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ def previews

# :doc:
def default_preview_layout
ViewComponent::Base.config.default_preview_layout
GlobalConfig.default_preview_layout
end

# :doc:
def show_previews?
ViewComponent::Base.config.show_previews
GlobalConfig.show_previews
end

# :doc:
Expand Down Expand Up @@ -95,7 +95,7 @@ def prepend_application_view_paths
end

def prepend_preview_examples_view_path
prepend_view_path(ViewComponent::Base.preview_paths)
prepend_view_path(GlobalConfig.preview_paths)
end
end
end
4 changes: 2 additions & 2 deletions app/helpers/preview_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def find_template_data(lookup_context:, template_identifier:)
# Fetch template source via finding it through preview paths
# to accomodate source view when exclusively using templates
# for previews for Rails < 6.1.
all_template_paths = ViewComponent::Base.config.preview_paths.map do |preview_path|
all_template_paths = ViewComponent::GlobalConfig.preview_paths.map do |preview_path|
Dir.glob("#{preview_path}/**/*")
end.flatten

Expand Down Expand Up @@ -80,6 +80,6 @@ def prism_language_name_by_template_path(template_file_path:)
# :nocov:

def serve_static_preview_assets?
ViewComponent::Base.config.show_previews && Rails.application.config.public_file_server.enabled
ViewComponent::GlobalConfig.show_previews && Rails.application.config.public_file_server.enabled
end
end
4 changes: 2 additions & 2 deletions app/views/view_components/preview.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<% if @render_args[:component] %>
<% if ViewComponent::Base.config.render_monkey_patch_enabled || Rails.version.to_f >= 6.1 %>
<% if ViewComponent::GlobalConfig.render_monkey_patch_enabled || Rails.version.to_f >= 6.1 %>
<%= render(@render_args[:component], @render_args[:args], &@render_args[:block]) %>
<% else %>
<%= render_component(@render_args[:component], &@render_args[:block]) %>
Expand All @@ -8,6 +8,6 @@
<%= render template: @render_args[:template], locals: @render_args[:locals] || {} %>
<% end %>
<% if ViewComponent::Base.config.show_previews_source %>
<% if ViewComponent::GlobalConfig.show_previews_source %>
<%= preview_source %>
<% end %>
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ nav_order: 5

## main

* Make accommodations for component-local config to be introduced in future.

*Simon Fish*

* Relax Active Support version constraint in gemspec.

*Simon Fish*
Expand Down
8 changes: 4 additions & 4 deletions lib/rails/generators/abstract_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def file_name
end

def component_path
ViewComponent::Base.config.view_component_path
GlobalConfig.view_component_path
end

def stimulus_controller
Expand All @@ -42,15 +42,15 @@ def stimulus_controller
end

def sidecar?
options["sidecar"] || ViewComponent::Base.config.generate.sidecar
options["sidecar"] || GlobalConfig.generate.sidecar
end

def stimulus?
options["stimulus"] || ViewComponent::Base.config.generate.stimulus_controller
options["stimulus"] || GlobalConfig.generate.stimulus_controller
end

def typescript?
options["typescript"] || ViewComponent::Base.config.generate.typescript
options["typescript"] || GlobalConfig.generate.typescript
end
end
end
8 changes: 4 additions & 4 deletions lib/rails/generators/component/component_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ class ComponentGenerator < Rails::Generators::NamedBase
check_class_collision suffix: "Component"

class_option :inline, type: :boolean, default: false
class_option :locale, type: :boolean, default: ViewComponent::Base.config.generate.locale
class_option :locale, type: :boolean, default: ViewComponent::GlobalConfig.generate.locale
class_option :parent, type: :string, desc: "The parent class for the generated component"
class_option :preview, type: :boolean, default: ViewComponent::Base.config.generate.preview
class_option :preview, type: :boolean, default: ViewComponent::GlobalConfig.generate.preview
class_option :sidecar, type: :boolean, default: false
class_option :stimulus, type: :boolean,
default: ViewComponent::Base.config.generate.stimulus_controller
default: ViewComponent::GlobalConfig.generate.stimulus_controller

def create_component_file
template "component.rb", File.join(component_path, class_path, "#{file_name}_component.rb")
Expand All @@ -41,7 +41,7 @@ def create_component_file
def parent_class
return options[:parent] if options[:parent]

ViewComponent::Base.config.component_parent_class || default_parent_class
ViewComponent::GlobalConfig.component_parent_class || default_parent_class
end

def initialize_signature
Expand Down
2 changes: 1 addition & 1 deletion lib/rails/generators/locale/component_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class ComponentGenerator < ::Rails::Generators::NamedBase
class_option :sidecar, type: :boolean, default: false

def create_locale_file
if ViewComponent::Base.config.generate.distinct_locale_files
if ViewComponent::GlobalConfig.generate.distinct_locale_files
I18n.available_locales.each do |locale|
create_file destination(locale), translations_hash([locale]).to_yaml
end
Expand Down
4 changes: 2 additions & 2 deletions lib/rails/generators/preview/component_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ module Preview
module Generators
class ComponentGenerator < ::Rails::Generators::NamedBase
source_root File.expand_path("templates", __dir__)
class_option :preview_path, type: :string, desc: "Path for previews, required when multiple preview paths are configured", default: ViewComponent::Base.config.generate.preview_path
class_option :preview_path, type: :string, desc: "Path for previews, required when multiple preview paths are configured", default: ViewComponent::GlobalConfig.generate.preview_path

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

def create_preview_file
preview_paths = ViewComponent::Base.config.preview_paths
preview_paths = ViewComponent::GlobalConfig.preview_paths
optional_path = options[:preview_path]
return if preview_paths.count > 1 && optional_path.blank?

Expand Down
2 changes: 1 addition & 1 deletion lib/rails/generators/rspec/component_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def create_test_file
private

def spec_component_path
return "spec/components" unless ViewComponent::Base.config.generate.use_component_path_for_rspec_tests
return "spec/components" unless ViewComponent::GlobalConfig.generate.use_component_path_for_rspec_tests

configured_component_path = component_path
if configured_component_path.start_with?("app#{File::SEPARATOR}")
Expand Down
1 change: 1 addition & 0 deletions lib/view_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module ViewComponent
autoload :ComponentError
autoload :Config
autoload :Deprecation
autoload :GlobalConfig
autoload :InlineTemplate
autoload :Instrumentation
autoload :Preview
Expand Down
8 changes: 4 additions & 4 deletions lib/view_component/base.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# frozen_string_literal: true

require "action_view"
require "active_support/configurable"
require "view_component/collection"
require "view_component/compile_cache"
require "view_component/compiler"
Expand All @@ -25,7 +24,7 @@ class << self
#
# @return [ActiveSupport::OrderedOptions]
def config
ViewComponent::Config.current
GlobalConfig
end
end

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

delegate :component_config, to: :class

attr_accessor :__vc_original_view_context

# Components render in their own view context. Helpers and other functionality
Expand Down Expand Up @@ -234,7 +235,6 @@ def controller
# @return [ActionView::Base]
def helpers
raise HelpersCalledBeforeRenderError if view_context.nil?

# Attempt to re-use the original view_context passed to the first
# component rendered in the rendering pipeline. This prevents the
# instantiation of a new view_context via `controller.view_context` which
Expand Down Expand Up @@ -552,7 +552,7 @@ def render_template_for(variant = nil, format = nil)
# If Rails application is loaded, removes the first part of the path and the extension.
if defined?(Rails) && Rails.application
child.virtual_path = child.source_location.gsub(
/(.*#{Regexp.quote(ViewComponent::Base.config.view_component_path)})|(\.rb)/, ""
/(.*#{Regexp.quote(GlobalConfig.view_component_path)})|(\.rb)/, ""
)
end

Expand Down
2 changes: 1 addition & 1 deletion lib/view_component/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class << self
alias_method :default, :new

def defaults
ActiveSupport::OrderedOptions.new.merge!({
ActiveSupport::InheritableOptions.new({
generate: default_generate_options,
preview_controller: "ViewComponentsController",
preview_route: "/rails/view_components",
Expand Down
8 changes: 4 additions & 4 deletions lib/view_component/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

module ViewComponent
class Engine < Rails::Engine # :nodoc:
config.view_component = ViewComponent::Config.current
config.view_component = ViewComponent::Config.defaults

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

if Rails.root.join(ViewComponent::Base.view_component_path).directory?
Rails::CodeStatistics.register_directory("ViewComponents", ViewComponent::Base.view_component_path)
if Rails.root.join(GlobalConfig.view_component_path).directory?
Rails::CodeStatistics.register_directory("ViewComponents", GlobalConfig.view_component_path)
end

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

%i[generate preview_controller preview_route show_previews_source].each do |config_option|
%i[generate preview_controller preview_route].each do |config_option|
options[config_option] ||= ViewComponent::Base.public_send(config_option)
end
options.instrumentation_enabled = false if options.instrumentation_enabled.nil?
Expand Down
3 changes: 3 additions & 0 deletions lib/view_component/global_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module ViewComponent
GlobalConfig = (defined?(Rails) && Rails.application) ? Rails.application.config.view_component : Config.defaults # standard:disable Naming/ConstantName
end
2 changes: 1 addition & 1 deletion lib/view_component/instrumentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def render_in(view_context, &block)
private

def notification_name
return "!render.view_component" if ViewComponent::Base.config.use_deprecated_instrumentation_name
return "!render.view_component" if GlobalConfig.use_deprecated_instrumentation_name

"render.view_component"
end
Expand Down
2 changes: 1 addition & 1 deletion lib/view_component/preview.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def load_previews
private

def preview_paths
Base.preview_paths
ViewComponent::GlobalConfig.preview_paths
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/view_component/rails/tasks/view_component.rake
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ namespace :view_component do
# :nocov:
require "rails/code_statistics"

if Rails.root.join(ViewComponent::Base.view_component_path).directory?
::STATS_DIRECTORIES << ["ViewComponents", ViewComponent::Base.view_component_path]
if Rails.root.join(ViewComponent::GlobalConfig.view_component_path).directory?
::STATS_DIRECTORIES << ["ViewComponents", ViewComponent::GlobalConfig.view_component_path]
end

if Rails.root.join("test/components").directory?
Expand Down
2 changes: 1 addition & 1 deletion lib/view_component/test_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ def with_request_url(full_path, host: nil, method: nil, format: ViewComponent::B
#
# @return [ActionController::Base]
def vc_test_controller
@vc_test_controller ||= __vc_test_helpers_build_controller(Base.test_controller.constantize)
@vc_test_controller ||= __vc_test_helpers_build_controller(GlobalConfig.test_controller.constantize)
end

# Access the request used by `render_inline`:
Expand Down
4 changes: 3 additions & 1 deletion lib/view_component/use_helpers.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require "view_component/errors"

module ViewComponent::UseHelpers
extend ActiveSupport::Concern

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

class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
def #{helper_method_name}(*args, &block)
raise HelpersCalledBeforeRenderError if view_context.nil?
raise ::ViewComponent::HelpersCalledBeforeRenderError if view_context.nil?
#{define_helper(helper_method: helper_method, source: from)}
end
Expand Down
18 changes: 11 additions & 7 deletions test/sandbox/test/integration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ def test_render_component_in_turbo_stream
expected_response_body = <<~TURBOSTREAM
<turbo-stream action="update" target="area1"><template><span>Hello, world!</span></template></turbo-stream>
TURBOSTREAM
if ViewComponent::Base.config.capture_compatibility_patch_enabled
if ViewComponent::GlobalConfig.capture_compatibility_patch_enabled
assert_equal expected_response_body, response.body
else
assert_not_equal expected_response_body, response.body
Expand Down Expand Up @@ -716,22 +716,26 @@ def test_cached_partial
Rails.cache.clear
end

def test_config_options_shared_between_base_and_engine
config_entrypoints = [Rails.application.config.view_component, ViewComponent::Base.config]
2.times do
config_entrypoints.first.yield_self do |config|
def test_globalconfig_is_proxy_for_rails_app_config
config_entrypoints = [
["Rails application config", Rails.application.config.view_component],
["Global config", ViewComponent::GlobalConfig],
["ViewComponent::Base's config", ViewComponent::Base.config]
]
config_entrypoints.permutation(2) do |(first_entrypoint_name, first_entrypoint), (second_entrypoint_name, second_entrypoint)|
first_entrypoint.yield_self do |config|
{
generate: config.generate.dup.tap { |c| c.sidecar = true },
preview_controller: "SomeOtherController",
preview_route: "/some/other/route",
show_previews_source: true
}.each do |option, value|
with_config_option(option, value, config_entrypoint: config) do
assert_equal(config.public_send(option), config_entrypoints.second.public_send(option))
assert_equal(config.public_send(option), second_entrypoint.public_send(option),
"#{first_entrypoint_name} should be the same as #{second_entrypoint_name} for #{option.inspect}")
end
end
end
config_entrypoints.rotate!
end
end

Expand Down
2 changes: 1 addition & 1 deletion test/sandbox/test/rendering_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def test_render_inline_allocations
ViewComponent::CompileCache.cache.delete(MyComponent)
MyComponent.ensure_compiled

assert_allocations("3.4.0" => 110, "3.3.5" => 116, "3.3.0" => 129, "3.2.5" => 115, "3.1.6" => 115, "3.0.7" => 125) do
assert_allocations("3.4.0" => 110, "3.3.5" => 115, "3.3.0" => 128, "3.2.5" => 114, "3.1.6" => 114, "3.0.7" => 124) do
render_inline(MyComponent.new)
end

Expand Down
2 changes: 1 addition & 1 deletion test/test_engine/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

Rails::Generators.namespace = TestEngine

def with_config_option(option_name, new_value, config_entrypoint: TestEngine::Engine.config.view_component)
def with_config_option(option_name, new_value, config_entrypoint: ViewComponent::GlobalConfig)
old_value = config_entrypoint.public_send(option_name)
config_entrypoint.public_send(:"#{option_name}=", new_value)
yield
Expand Down

0 comments on commit 62e0960

Please sign in to comment.