Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
71c23c6
Use ActiveSupport::Configurable to cascade config
boardfish Sep 8, 2024
4b4ba76
Source test controller from Rails config if available
boardfish Oct 6, 2024
0ba800b
Make GlobalConfig a proxy for Rails app config or ViewComponent base …
boardfish Oct 6, 2024
a655192
Ensure Rails.application is defined, use in preview source feature
boardfish Oct 6, 2024
f56832e
Use GlobalConfig as config entrypoint in test engine
boardfish Oct 8, 2024
2531907
Don't attempt to write to show_previews_source in engine initializer
boardfish Oct 8, 2024
1f40e96
Bump expected allocations for Ruby 3.4
boardfish Oct 8, 2024
4c89cbc
Remove component-local config for now
boardfish Oct 8, 2024
2c0daf6
Lint
boardfish Oct 8, 2024
161cee3
Update changelog
boardfish Oct 8, 2024
43e1373
add: use_helper to base
Jan 21, 2024
5357491
add: changelog entry
Jan 21, 2024
65d8b93
add: strict helpers
Jan 22, 2024
f257c84
Merge branch 'main' into rv-add-strict-helpers
Feb 12, 2024
6511a91
revert "fix: linting errors"
Feb 12, 2024
da9a3e7
fix tests
Feb 12, 2024
b76beaa
replace helper delegation with use_helpers
Feb 24, 2024
fa526d1
replace strict_helpers_enabled with helpers_enabled
Feb 24, 2024
29f888a
add: test for missing coverage
Feb 24, 2024
d3fa703
fix: comment location for csrf and csp nonces
Feb 26, 2024
5a5727a
update changelog
Feb 26, 2024
c4a0d94
WIP: Extract strict_helpers_enabled? to component-local config
boardfish Sep 8, 2024
4e5301b
Use ActiveSupport::Configurable to cascade config
boardfish Sep 8, 2024
e8bc4ef
Make GlobalConfig a proxy for Rails app config or ViewComponent base …
boardfish Oct 6, 2024
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
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 %>
14 changes: 14 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ nav_order: 5

## main

* Add `helpers_enabled` config.

*Reegan Viljoen*

* Include ViewComponent::UseHelpers by default.

*Reegan Viljoen*

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

BREAKING: Assigning to `ViewComponent::Base.config` will no longer work. Instead, assign to `Rails.application.config`.

*Simon Fish*

* Use struct instead openstruct in lib code.

*Oleksii Vasyliev*
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
43 changes: 30 additions & 13 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 Down Expand Up @@ -38,16 +37,15 @@ def config
RESERVED_PARAMETER = :content
VC_INTERNAL_DEFAULT_FORMAT = :html

# For CSRF authenticity tokens in forms
delegate :form_authenticity_token, :protect_against_forgery?, :config, to: :helpers

# For Content Security Policy nonces
delegate :content_security_policy_nonce, to: :helpers
# For CSRF authenticity tokens in forms and Content Security Policy nonces
use_helpers :form_authenticity_token, :protect_against_forgery?, :config, :content_security_policy_nonce

# Config option that strips trailing whitespace in templates before compiling them.
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 +232,7 @@ def controller
# @return [ActionView::Base]
def helpers
raise HelpersCalledBeforeRenderError if view_context.nil?

raise StrictHelperError unless GlobalConfig.helpers_enabled
# 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 All @@ -251,11 +249,19 @@ def method_missing(method_name, *args) # rubocop:disable Style/MissingRespondToM
super
rescue => e # rubocop:disable Style/RescueStandardError
e.set_backtrace e.backtrace.tap(&:shift)
raise e, <<~MESSAGE.chomp if view_context && e.is_a?(NameError) && helpers.respond_to?(method_name)
#{e.message}

You may be trying to call a method provided as a view helper. Did you mean `helpers.#{method_name}'?
MESSAGE
if !GlobalConfig.helpers_enabled
raise e, <<~MESSAGE.chomp if view_context && e.is_a?(NameError) && (__vc_original_view_context.respond_to?(method_name) || controller.view_context.respond_to?(method_name))
#{e.message}

You may be trying to call a method provided as a view helper. To use it try decalring it using use_helpers :#{method_name}'?
MESSAGE
elsif view_context && e.is_a?(NameError) && helpers.respond_to?(method_name)
raise e, <<~MESSAGE.chomp
#{e.message}

You may be trying to call a method provided as a view helper. Did you mean `helpers.#{method_name}'?
MESSAGE
end

raise
end
Expand Down Expand Up @@ -468,6 +474,7 @@ def sidecar_files(extensions)
# view files in a directory named like the component
directory = File.dirname(source_location)
filename = File.basename(source_location, ".rb")
return [] if name.blank?
component_name = name.demodulize.underscore

# Add support for nested components defined in the same file.
Expand Down Expand Up @@ -513,6 +520,16 @@ def inherited(child)
# `compile` defines
compile

if child.superclass == ViewComponent::Base
child.define_singleton_method(:component_config) do
@@component_config ||= Rails.application.config.view_component.inheritable_copy
end
else
child.define_singleton_method(:component_config) do
@@component_config ||= superclass.component_config.inheritable_copy
end
end

# Give the child its own personal #render_template_for to protect against the case when
# eager loading is disabled and the parent component is rendered before the child. In
# such a scenario, the parent will override ViewComponent::Base#render_template_for,
Expand Down Expand Up @@ -545,7 +562,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
11 changes: 9 additions & 2 deletions 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 All @@ -25,7 +25,9 @@ def defaults
preview_paths: default_preview_paths,
test_controller: "ApplicationController",
default_preview_layout: nil,
capture_compatibility_patch_enabled: false
capture_compatibility_patch_enabled: false,
helpers_enabled: true,
strict_helpers_enabled?: false
})
end

Expand Down Expand Up @@ -173,6 +175,11 @@ def defaults
# previews.
# Defaults to `false`.

# @!attribute helpers_enabled
# @return [Boolean]
# Enables the use of #helpers
# Defaults to `True`.

def default_preview_paths
(default_rails_preview_paths + default_rails_engines_preview_paths).uniq
end
Expand Down
6 changes: 3 additions & 3 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 @@ -15,15 +15,15 @@ class Engine < Rails::Engine # :nodoc:
else
initializer "view_component.stats_directories" do |app|
require "rails/code_statistics"
dir = ViewComponent::Base.view_component_path
dir = GlobalConfig.view_component_path
Rails::CodeStatistics.register_directory("ViewComponents", dir)
end
end

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
4 changes: 4 additions & 0 deletions lib/view_component/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ class SystemTestControllerNefariousPathError < BaseError
MESSAGE = "ViewComponent SystemTest controller attempted to load a file outside of the expected directory."
end

class StrictHelperError < BaseError
MESSAGE = "ViewComponent strict helper mode is enbaled so #helpers is disabled."
end

class AlreadyDefinedPolymorphicSlotSetterError < StandardError
MESSAGE =
"A method called 'SETTER_METHOD_NAME' already exists and would be overwritten by the 'SETTER_NAME' polymorphic " \
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 : Base.config # 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
2 changes: 1 addition & 1 deletion lib/view_component/rails/tasks/view_component.rake
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace :view_component do
# :nocov:
require "rails/code_statistics"

dir = ViewComponent::Base.view_component_path
dir = ViewComponent::GlobalConfig.view_component_path
::STATS_DIRECTORIES << ["ViewComponents", dir] if File.directory?(Rails.root + dir)
# :nocov:
end
Expand Down
3 changes: 2 additions & 1 deletion lib/view_component/test_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ 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)
config_source = defined?(Rails) ? Rails.application.config.view_component : ViewComponent::Base
@vc_test_controller ||= __vc_test_helpers_build_controller(config_source.test_controller.constantize)
end

# Access the request used by `render_inline`:
Expand Down
Loading