Centralize ViewComponent::Base config (#1291)
* Update changelog * Clean up mirrored config options from ViewComponent::Base * Refactor generate test helper * Test that app config and ViewComponent::Base config are equivalent * Update documentation on defaults * Update lib/view_component/base.rb Co-authored-by: Hans Lemuet <Spone@users.noreply.github.com> * Create dedicated config object * Move default config to constant * Test to ensure preview_path alias is maintained * Use OrderedOptions for generate config option to maintain behavior * Update some defaults * Remove duplicate config definitions These are specified in ViewComponent::Config now. * Clarify necessity of specifying preview paths * Delegate the majority of config options back to application config * Move remainder of Previewable accessors to app config * Extract out default preview paths * Make all config accessors accessible on ViewComponent::Base by default * Remove the Previewable module ALl it did was make preview-related config accessible on ViewComponent::Base * Add default_preview_layout to global config * Run `bundle update` * Make ViewComponent::Base the source of truth for config * Add backwards compatibility for config_accessor defaults * Restore documentation for config options * Improve test coverage * Directly require ViewComponent::Base to fix Primer CI * Directly require ViewComponent::Config to fix Primer CI * Move config require to ViewComponent::Base * Require ViewComponent::Deprecation in Config * Add missing defaults * Reset default_preview_layout to `nil` * Remove all changes to docs/api.md * Add code ownership * s/with_generate/with_generate_option/ * Explain alias between .default and .new * Draft test to ensure all config options are documented The pieces are in place, but I think the final aim is that everything for which there's a default and/or a reader is documented * Ensure accessors defined in public methods are considered too * Coerce to string for backwards compatibility * Lint * Use Ruby 3.0.3 * Add more detailed changelog message Co-authored-by: Joel Hawksley <joel@hawksley.org> * Manually restore the Previewable changes after rebase * Lint * Remove global output buffer option * Remove previewable module Config options for preview are now under ViewComponent::Config. * Make Rails.application.config.view_component the source of truth ViewComponent::Base should continue to work but will delegate back to that. * Update missed stub * Update Gemfile.lock * Compose around ActiveSupport::OrderedOptions Co-authored-by: Hans Lemuet <Spone@users.noreply.github.com> Co-authored-by: Joel Hawksley <joel@hawksley.org>
This commit is contained in:
Родитель
8ed02f923b
Коммит
c271c5ac06
|
@ -26,7 +26,6 @@
|
|||
/app/**/* @juanmanuelramallo
|
||||
/docs/guide/previews.md @juanmanuelramallo
|
||||
/lib/view_component/preview.rb @juanmanuelramallo
|
||||
/lib/view_component/previewable.rb @juanmanuelramallo
|
||||
/lib/view_component/preview_template_error.rb @juanmanuelramallo
|
||||
/test/sandbox/test/components/previews/preview_source_from_layout_component_preview.rb @juanmanuelramallo
|
||||
/test/view_component/default_preview_layout_integration_test.rb @juanmanuelramallo
|
||||
|
@ -46,3 +45,5 @@
|
|||
/script/replicate-bug @boardfish
|
||||
/lib/view_component/docs_builder_component.rb @boardfish
|
||||
/lib/view_component/docs_builder_component.html.erb @boardfish
|
||||
/lib/view_component/config.rb @boardfish
|
||||
/test/view_component/config_test.rb @boardfish
|
||||
|
|
4
Rakefile
4
Rakefile
|
@ -70,9 +70,7 @@ namespace :docs do
|
|||
|
||||
instance_methods_to_document = meths.select { |method| method.scope != :class }
|
||||
class_methods_to_document = meths.select { |method| method.scope == :class }
|
||||
configuration_methods_to_document = registry.get("ViewComponent::Base").meths.select { |method|
|
||||
method[:mattr_accessor]
|
||||
}
|
||||
configuration_methods_to_document = registry.get("ViewComponent::Config").meths.select(&:reader?)
|
||||
test_helper_methods_to_document = registry
|
||||
.get("ViewComponent::TestHelpers")
|
||||
.meths
|
||||
|
|
|
@ -10,9 +10,7 @@ module ViewComponent
|
|||
around_action :set_locale, only: :previews
|
||||
before_action :require_local!, unless: :show_previews?
|
||||
|
||||
if respond_to?(:content_security_policy)
|
||||
content_security_policy(false)
|
||||
end
|
||||
content_security_policy(false) if respond_to?(:content_security_policy)
|
||||
end
|
||||
|
||||
def index
|
||||
|
@ -45,18 +43,18 @@ module ViewComponent
|
|||
|
||||
# :doc:
|
||||
def default_preview_layout
|
||||
ViewComponent::Base.default_preview_layout
|
||||
Rails.application.config.view_component.default_preview_layout
|
||||
end
|
||||
|
||||
# :doc:
|
||||
def show_previews?
|
||||
ViewComponent::Base.show_previews
|
||||
Rails.application.config.view_component.show_previews
|
||||
end
|
||||
|
||||
# :doc:
|
||||
def find_preview
|
||||
candidates = []
|
||||
params[:path].to_s.scan(%r{/|$}) { candidates << $` }
|
||||
params[:path].to_s.scan(%r{/|$}) { candidates << Regexp.last_match.pre_match }
|
||||
preview = candidates.detect { |candidate| ViewComponent::Preview.exists?(candidate) }
|
||||
|
||||
if preview
|
||||
|
@ -66,10 +64,8 @@ module ViewComponent
|
|||
end
|
||||
end
|
||||
|
||||
def set_locale
|
||||
I18n.with_locale(params[:locale] || I18n.default_locale) do
|
||||
yield
|
||||
end
|
||||
def set_locale(&block)
|
||||
I18n.with_locale(params[:locale] || I18n.default_locale, &block)
|
||||
end
|
||||
|
||||
# Returns either {} or {layout: value} depending on configuration
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module PreviewHelper
|
||||
AVAILABLE_PRISM_LANGUAGES = ["ruby", "erb", "haml"]
|
||||
AVAILABLE_PRISM_LANGUAGES = %w[ruby erb haml]
|
||||
FALLBACK_LANGUAGE = "ruby"
|
||||
|
||||
def preview_source
|
||||
|
@ -22,7 +22,7 @@ module PreviewHelper
|
|||
# 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.preview_paths.map do |preview_path|
|
||||
all_template_paths = Rails.application.config.view_component.preview_paths.map do |preview_path|
|
||||
Dir.glob("#{preview_path}/**/*")
|
||||
end.flatten
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<% if @render_args[:component] %>
|
||||
<% if ViewComponent::Base.render_monkey_patch_enabled || Rails.version.to_f >= 6.1 %>
|
||||
<% if Rails.application.config.view_component.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]) %>
|
||||
|
@ -8,6 +8,6 @@
|
|||
<%= render template: @render_args[:template], locals: @render_args[:locals] || {} %>
|
||||
<% end %>
|
||||
|
||||
<% if ViewComponent::Base.show_previews_source %>
|
||||
<% if Rails.application.config.view_component.show_previews_source %>
|
||||
<%= preview_source %>
|
||||
<% end %>
|
||||
|
|
|
@ -9,6 +9,10 @@ title: Changelog
|
|||
|
||||
## main
|
||||
|
||||
* Add missing `generate.sidecar`, `generate.stimulus_controller`, `generate.locale`, `generate.distinct_locale_files`, `generate.preview` config options to `config.view_component`.
|
||||
|
||||
*Simon Fish*
|
||||
|
||||
## 2.65.0
|
||||
|
||||
* Raise `ArgumentError` when conflicting Slots are defined.
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
module ViewComponent
|
||||
module AbstractGenerator
|
||||
def copy_view_file
|
||||
unless options["inline"]
|
||||
template "component.html.#{engine_name}", destination
|
||||
end
|
||||
template "component.html.#{engine_name}", destination unless options["inline"]
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -31,7 +29,7 @@ module ViewComponent
|
|||
end
|
||||
|
||||
def component_path
|
||||
ViewComponent::Base.view_component_path
|
||||
Rails.application.config.view_component.view_component_path
|
||||
end
|
||||
|
||||
def stimulus_controller
|
||||
|
@ -44,7 +42,7 @@ module ViewComponent
|
|||
end
|
||||
|
||||
def sidecar?
|
||||
options["sidecar"] || ViewComponent::Base.generate.sidecar
|
||||
options["sidecar"] || Rails.application.config.view_component.generate.sidecar
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,11 +13,12 @@ module Rails
|
|||
check_class_collision suffix: "Component"
|
||||
|
||||
class_option :inline, type: :boolean, default: false
|
||||
class_option :locale, type: :boolean, default: ViewComponent::Base.generate.locale
|
||||
class_option :locale, type: :boolean, default: Rails.application.config.view_component.generate.locale
|
||||
class_option :parent, type: :string, desc: "The parent class for the generated component"
|
||||
class_option :preview, type: :boolean, default: ViewComponent::Base.generate.preview
|
||||
class_option :preview, type: :boolean, default: Rails.application.config.view_component.generate.preview
|
||||
class_option :sidecar, type: :boolean, default: false
|
||||
class_option :stimulus, type: :boolean, default: ViewComponent::Base.generate.stimulus_controller
|
||||
class_option :stimulus, type: :boolean,
|
||||
default: Rails.application.config.view_component.generate.stimulus_controller
|
||||
|
||||
def create_component_file
|
||||
template "component.rb", File.join(component_path, class_path, "#{file_name}_component.rb")
|
||||
|
@ -40,7 +41,7 @@ module Rails
|
|||
def parent_class
|
||||
return options[:parent] if options[:parent]
|
||||
|
||||
ViewComponent::Base.component_parent_class || default_parent_class
|
||||
Rails.application.config.view_component.component_parent_class || default_parent_class
|
||||
end
|
||||
|
||||
def initialize_signature
|
||||
|
|
|
@ -12,7 +12,7 @@ module Locale
|
|||
class_option :sidecar, type: :boolean, default: false
|
||||
|
||||
def create_locale_file
|
||||
if ViewComponent::Base.generate.distinct_locale_files
|
||||
if Rails.application.config.view_component.generate.distinct_locale_files
|
||||
I18n.available_locales.each do |locale|
|
||||
create_file destination(locale), translations_hash([locale]).to_yaml
|
||||
end
|
||||
|
|
|
@ -10,6 +10,7 @@ module ViewComponent
|
|||
autoload :Compiler
|
||||
autoload :CompileCache
|
||||
autoload :ComponentError
|
||||
autoload :Config
|
||||
autoload :Deprecation
|
||||
autoload :Instrumentation
|
||||
autoload :Preview
|
||||
|
|
|
@ -4,9 +4,9 @@ require "action_view"
|
|||
require "active_support/configurable"
|
||||
require "view_component/collection"
|
||||
require "view_component/compile_cache"
|
||||
require "view_component/config"
|
||||
require "view_component/content_areas"
|
||||
require "view_component/polymorphic_slots"
|
||||
require "view_component/previewable"
|
||||
require "view_component/slotable"
|
||||
require "view_component/slotable_v2"
|
||||
require "view_component/translatable"
|
||||
|
@ -14,10 +14,16 @@ require "view_component/with_content_helper"
|
|||
|
||||
module ViewComponent
|
||||
class Base < ActionView::Base
|
||||
include ActiveSupport::Configurable
|
||||
class << self
|
||||
delegate(*ViewComponent::Config.defaults.keys, to: :config)
|
||||
|
||||
def config
|
||||
Rails.application.config.view_component
|
||||
end
|
||||
end
|
||||
|
||||
include ViewComponent::ContentAreas
|
||||
include ViewComponent::PolymorphicSlots
|
||||
include ViewComponent::Previewable
|
||||
include ViewComponent::SlotableV2
|
||||
include ViewComponent::Translatable
|
||||
include ViewComponent::WithContentHelper
|
||||
|
@ -113,11 +119,9 @@ module ViewComponent
|
|||
@current_template = self
|
||||
|
||||
if block && defined?(@__vc_content_set_by_with_content)
|
||||
raise ArgumentError.new(
|
||||
"It looks like a block was provided after calling `with_content` on #{self.class.name}, " \
|
||||
raise ArgumentError, "It looks like a block was provided after calling `with_content` on #{self.class.name}, " \
|
||||
"which means that ViewComponent doesn't know which content to use.\n\n" \
|
||||
"To fix this issue, use either `with_content` or a block."
|
||||
)
|
||||
end
|
||||
|
||||
@__vc_content_evaluated = false
|
||||
|
@ -262,9 +266,7 @@ module ViewComponent
|
|||
# @private
|
||||
def format
|
||||
# Ruby 2.6 throws a warning without checking `defined?`, 2.7 doesn't
|
||||
if defined?(@__vc_variant)
|
||||
@__vc_variant
|
||||
end
|
||||
@__vc_variant if defined?(@__vc_variant)
|
||||
end
|
||||
|
||||
# Use the provided variant instead of the one determined by the current request.
|
||||
|
@ -313,11 +315,9 @@ module ViewComponent
|
|||
# config.view_component.test_controller = "MyTestController"
|
||||
# ```
|
||||
#
|
||||
# Defaults to ApplicationController. Can also be configured on a per-test
|
||||
# basis using `with_controller_class`.
|
||||
# Defaults to `nil`. If this is falsy, `"ApplicationController"` is used. Can also be
|
||||
# configured on a per-test basis using `with_controller_class`.
|
||||
#
|
||||
mattr_accessor :test_controller
|
||||
@@test_controller = "ApplicationController"
|
||||
|
||||
# Set if render monkey patches should be included or not in Rails <6.1:
|
||||
#
|
||||
|
@ -325,7 +325,6 @@ module ViewComponent
|
|||
# config.view_component.render_monkey_patch_enabled = false
|
||||
# ```
|
||||
#
|
||||
mattr_accessor :render_monkey_patch_enabled, instance_writer: false, default: true
|
||||
|
||||
# Path for component files
|
||||
#
|
||||
|
@ -333,9 +332,8 @@ module ViewComponent
|
|||
# config.view_component.view_component_path = "app/my_components"
|
||||
# ```
|
||||
#
|
||||
# Defaults to `app/components`.
|
||||
# Defaults to `nil`. If this is falsy, `app/components` is used.
|
||||
#
|
||||
mattr_accessor :view_component_path, instance_writer: false, default: "app/components"
|
||||
|
||||
# Parent class for generated components
|
||||
#
|
||||
|
@ -346,7 +344,6 @@ module ViewComponent
|
|||
# Defaults to nil. If this is falsy, generators will use
|
||||
# "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
|
||||
#
|
||||
mattr_accessor :component_parent_class, instance_writer: false
|
||||
|
||||
# Configuration for generators.
|
||||
#
|
||||
|
@ -397,7 +394,6 @@ module ViewComponent
|
|||
# ```
|
||||
#
|
||||
# Defaults to `false`.
|
||||
mattr_accessor :generate, instance_writer: false, default: ActiveSupport::OrderedOptions.new(false)
|
||||
|
||||
class << self
|
||||
# @private
|
||||
|
@ -489,8 +485,8 @@ module ViewComponent
|
|||
|
||||
# If Rails application is loaded, add application url_helpers to the component context
|
||||
# we need to check this to use this gem as a dependency
|
||||
if defined?(Rails) && Rails.application
|
||||
child.include Rails.application.routes.url_helpers unless child < Rails.application.routes.url_helpers
|
||||
if defined?(Rails) && Rails.application && !(child < Rails.application.routes.url_helpers)
|
||||
child.include Rails.application.routes.url_helpers
|
||||
end
|
||||
|
||||
# Derive the source location of the component Ruby file from the call stack.
|
||||
|
@ -500,7 +496,7 @@ module ViewComponent
|
|||
|
||||
# Removes the first part of the path and the extension.
|
||||
child.virtual_path = child.source_location.gsub(
|
||||
%r{(.*#{Regexp.quote(ViewComponent::Base.view_component_path)})|(\.rb)}, ""
|
||||
/(.*#{Regexp.quote(Rails.application.config.view_component.view_component_path)})|(\.rb)/, ""
|
||||
)
|
||||
|
||||
# Set collection parameter to the extended component
|
||||
|
@ -591,20 +587,16 @@ module ViewComponent
|
|||
# parameters will be empty and ViewComponent will not be able to render
|
||||
# the component.
|
||||
if initialize_parameters.empty?
|
||||
raise ArgumentError.new(
|
||||
"The #{self} initializer is empty or invalid." \
|
||||
raise ArgumentError, "The #{self} initializer is empty or invalid." \
|
||||
"It must accept the parameter `#{parameter}` to render it as a collection.\n\n" \
|
||||
"To fix this issue, update the initializer to accept `#{parameter}`.\n\n" \
|
||||
"See https://viewcomponent.org/guide/collections.html for more information on rendering collections."
|
||||
)
|
||||
end
|
||||
|
||||
raise ArgumentError.new(
|
||||
"The initializer for #{self} doesn't accept the parameter `#{parameter}`, " \
|
||||
raise ArgumentError, "The initializer for #{self} doesn't accept the parameter `#{parameter}`, " \
|
||||
"which is required in order to render it as a collection.\n\n" \
|
||||
"To fix this issue, update the initializer to accept `#{parameter}`.\n\n" \
|
||||
"See https://viewcomponent.org/guide/collections.html for more information on rendering collections."
|
||||
)
|
||||
end
|
||||
|
||||
# Ensure the component initializer doesn't define
|
||||
|
@ -614,10 +606,8 @@ module ViewComponent
|
|||
def validate_initialization_parameters!
|
||||
return unless initialize_parameter_names.include?(RESERVED_PARAMETER)
|
||||
|
||||
raise ViewComponent::ComponentError.new(
|
||||
"#{self} initializer can't accept the parameter `#{RESERVED_PARAMETER}`, as it will override a " \
|
||||
raise ViewComponent::ComponentError, "#{self} initializer can't accept the parameter `#{RESERVED_PARAMETER}`, as it will override a " \
|
||||
"public ViewComponent method. To fix this issue, rename the parameter."
|
||||
)
|
||||
end
|
||||
|
||||
# @private
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "view_component/deprecation"
|
||||
|
||||
module ViewComponent
|
||||
class Config
|
||||
class << self
|
||||
# `new` without any arguments initializes the default configuration, but
|
||||
# it's important to differentiate in case that's no longer the case in
|
||||
# future.
|
||||
alias_method :default, :new
|
||||
|
||||
def defaults
|
||||
ActiveSupport::OrderedOptions.new.merge!({
|
||||
generate: ActiveSupport::OrderedOptions.new(false),
|
||||
preview_controller: "ViewComponentsController",
|
||||
preview_route: "/rails/view_components",
|
||||
show_previews_source: false,
|
||||
instrumentation_enabled: false,
|
||||
render_monkey_patch_enabled: true,
|
||||
view_component_path: "app/components",
|
||||
component_parent_class: nil,
|
||||
show_previews: Rails.env.development? || Rails.env.test?,
|
||||
preview_paths: default_preview_paths,
|
||||
test_controller: "ApplicationController",
|
||||
default_preview_layout: nil
|
||||
})
|
||||
end
|
||||
|
||||
# @!attribute generate
|
||||
# @return [ActiveSupport::OrderedOptions]
|
||||
# The subset of configuration options relating to generators.
|
||||
#
|
||||
# All options under this namespace default to `false` unless otherwise
|
||||
# stated.
|
||||
#
|
||||
# #### #sidecar
|
||||
#
|
||||
# Always generate a component with a sidecar directory:
|
||||
#
|
||||
# config.view_component.generate.sidecar = true
|
||||
#
|
||||
# #### #stimulus_controller
|
||||
#
|
||||
# Always generate a Stimulus controller alongside the component:
|
||||
#
|
||||
# config.view_component.generate.stimulus_controller = true
|
||||
#
|
||||
# #### #locale
|
||||
#
|
||||
# Always generate translations file alongside the component:
|
||||
#
|
||||
# config.view_component.generate.locale = true
|
||||
#
|
||||
# #### #distinct_locale_files
|
||||
#
|
||||
# Always generate as many translations files as available locales:
|
||||
#
|
||||
# config.view_component.generate.distinct_locale_files = true
|
||||
#
|
||||
# One file will be generated for each configured `I18n.available_locales`,
|
||||
# falling back to `[:en]` when no `available_locales` is defined.
|
||||
#
|
||||
# #### #preview
|
||||
#
|
||||
# Always generate a preview alongside the component:
|
||||
#
|
||||
# config.view_component.generate.preview = true
|
||||
|
||||
# @!attribute preview_controller
|
||||
# @return [String]
|
||||
# The controller used for previewing components.
|
||||
# Defaults to `ViewComponentsController`.
|
||||
|
||||
# @!attribute preview_route
|
||||
# @return [String]
|
||||
# The entry route for component previews.
|
||||
# Defaults to `"/rails/view_components"`.
|
||||
|
||||
# @!attribute show_previews_source
|
||||
# @return [Boolean]
|
||||
# Whether to display source code previews in component previews.
|
||||
# Defaults to `false`.
|
||||
|
||||
# @!attribute instrumentation_enabled
|
||||
# @return [Boolean]
|
||||
# Whether ActiveSupport notifications are enabled.
|
||||
# Defaults to `false`.
|
||||
|
||||
# @!attribute render_monkey_patch_enabled
|
||||
# @return [Boolean] Whether the #render method should be monkey patched.
|
||||
# If this is disabled, use `#render_component` or
|
||||
# `#render_component_to_string` instead.
|
||||
# Defaults to `true`.
|
||||
|
||||
# @!attribute view_component_path
|
||||
# @return [String]
|
||||
# The path in which components, their templates, and their sidecars should
|
||||
# be stored.
|
||||
# Defaults to `"app/components"`.
|
||||
|
||||
# @!attribute component_parent_class
|
||||
# @return [String]
|
||||
# The parent class from which generated components will inherit.
|
||||
# Defaults to `nil`. If this is falsy, generators will use
|
||||
# `"ApplicationComponent"` if defined, `"ViewComponent::Base"` otherwise.
|
||||
|
||||
# @!attribute show_previews
|
||||
# @return [Boolean]
|
||||
# Whether component previews are enabled.
|
||||
# Defaults to `true` in development and test environments.
|
||||
|
||||
# @!attribute preview_paths
|
||||
# @return [Array<String>]
|
||||
# The locations in which component previews will be looked up.
|
||||
# Defaults to `['test/component/previews']` relative to your Rails root.
|
||||
|
||||
# @!attribute preview_path
|
||||
# @deprecated Use #preview_paths instead. Will be removed in v3.0.0.
|
||||
|
||||
# @!attribute test_controller
|
||||
# @return [String]
|
||||
# The controller used for testing components.
|
||||
# Can also be configured on a per-test basis using `#with_controller_class`.
|
||||
# Defaults to `ApplicationController`.
|
||||
|
||||
# @!attribute default_preview_layout
|
||||
# @return [String]
|
||||
# A custom default layout used for the previews index page and individual
|
||||
# previews.
|
||||
# Defaults to `nil`. If this is falsy, `"component_preview"` is used.
|
||||
|
||||
def default_preview_paths
|
||||
return [] unless defined?(Rails.root) && Dir.exist?("#{Rails.root}/test/components/previews")
|
||||
|
||||
["#{Rails.root}/test/components/previews"]
|
||||
end
|
||||
end
|
||||
|
||||
def initialize
|
||||
@config = self.class.defaults
|
||||
end
|
||||
|
||||
def preview_path
|
||||
preview_paths
|
||||
end
|
||||
|
||||
def preview_path=(new_value)
|
||||
ViewComponent::Deprecation.warn("`preview_path` will be removed in v3.0.0. Use `preview_paths` instead.")
|
||||
self.preview_paths = Array.wrap(new_value)
|
||||
end
|
||||
|
||||
delegate_missing_to :config
|
||||
|
||||
private
|
||||
|
||||
attr_reader :config
|
||||
end
|
||||
end
|
|
@ -1,11 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rails"
|
||||
require "view_component/base"
|
||||
|
||||
module ViewComponent
|
||||
class Engine < Rails::Engine # :nodoc:
|
||||
config.view_component = ActiveSupport::OrderedOptions.new
|
||||
config.view_component.preview_paths ||= []
|
||||
config.view_component = ViewComponent::Config.default
|
||||
|
||||
rake_tasks do
|
||||
load "view_component/rails/tasks/view_component.rake"
|
||||
|
@ -14,25 +14,20 @@ module ViewComponent
|
|||
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|
|
||||
options[config_option] ||= ViewComponent::Base.public_send(config_option)
|
||||
end
|
||||
options.instrumentation_enabled = false if options.instrumentation_enabled.nil?
|
||||
options.render_monkey_patch_enabled = true if options.render_monkey_patch_enabled.nil?
|
||||
options.show_previews = Rails.env.development? || Rails.env.test? if options.show_previews.nil?
|
||||
options.show_previews_source ||= ViewComponent::Base.show_previews_source
|
||||
options.instrumentation_enabled = false if options.instrumentation_enabled.nil?
|
||||
options.preview_route ||= ViewComponent::Base.preview_route
|
||||
options.preview_controller ||= ViewComponent::Base.preview_controller
|
||||
|
||||
if options.show_previews
|
||||
# This is still necessary because when `config.view_component` is declared, `Rails.root` is unspecified.
|
||||
options.preview_paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root) && Dir.exist?(
|
||||
"#{Rails.root}/test/components/previews"
|
||||
)
|
||||
|
||||
if options.preview_path.present?
|
||||
ViewComponent::Deprecation.warn(
|
||||
"`preview_path` will be removed in v3.0.0. Use `preview_paths` instead."
|
||||
)
|
||||
options.preview_paths << options.preview_path
|
||||
end
|
||||
|
||||
if options.show_previews_source
|
||||
require "method_source"
|
||||
|
||||
|
@ -41,10 +36,6 @@ module ViewComponent
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
ActiveSupport.on_load(:view_component) do
|
||||
options.each { |k, v| send("#{k}=", v) if respond_to?("#{k}=") }
|
||||
end
|
||||
end
|
||||
|
||||
initializer "view_component.enable_instrumentation" do |app|
|
||||
|
@ -72,12 +63,6 @@ module ViewComponent
|
|||
end
|
||||
end
|
||||
|
||||
initializer "view_component.compile_config_methods" do
|
||||
ActiveSupport.on_load(:view_component) do
|
||||
config.compile_methods! if config.respond_to?(:compile_methods!)
|
||||
end
|
||||
end
|
||||
|
||||
initializer "view_component.monkey_patch_render" do |app|
|
||||
next if Rails.version.to_f >= 6.1 || !app.config.view_component.render_monkey_patch_enabled
|
||||
|
||||
|
@ -94,7 +79,7 @@ module ViewComponent
|
|||
end
|
||||
end
|
||||
|
||||
initializer "view_component.include_render_component" do |app|
|
||||
initializer "view_component.include_render_component" do |_app|
|
||||
next if Rails.version.to_f >= 6.1
|
||||
|
||||
ActiveSupport.on_load(:action_view) do
|
||||
|
@ -116,7 +101,7 @@ module ViewComponent
|
|||
end
|
||||
end
|
||||
|
||||
initializer "compiler mode" do |app|
|
||||
initializer "compiler mode" do |_app|
|
||||
ViewComponent::Compiler.mode = if Rails.env.development? || Rails.env.test?
|
||||
ViewComponent::Compiler::DEVELOPMENT_MODE
|
||||
else
|
||||
|
@ -160,7 +145,7 @@ unless defined?(ViewComponent::Base)
|
|||
|
||||
ViewComponent::Deprecation.warn(
|
||||
"This manually engine loading is deprecated and will be removed in v3.0.0. " \
|
||||
"Remove `require \"view_component/engine\"`."
|
||||
'Remove `require "view_component/engine"`.'
|
||||
)
|
||||
|
||||
require "view_component"
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/concern"
|
||||
|
||||
module ViewComponent
|
||||
module Previewable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# Enable or disable component previews:
|
||||
#
|
||||
# config.view_component.show_previews = true
|
||||
#
|
||||
# Defaults to `true` in development.
|
||||
#
|
||||
mattr_accessor :show_previews, instance_writer: false
|
||||
|
||||
# Enable or disable source code previews in component previews:
|
||||
#
|
||||
# config.view_component.show_previews_source = true
|
||||
#
|
||||
# Defaults to `false`.
|
||||
#
|
||||
mattr_accessor :show_previews_source, instance_writer: false, default: false
|
||||
|
||||
# Set a custom default layout used for preview index and individual previews:
|
||||
#
|
||||
# config.view_component.default_preview_layout = "component_preview"
|
||||
#
|
||||
mattr_accessor :default_preview_layout, instance_writer: false
|
||||
|
||||
# Set the location of component previews:
|
||||
#
|
||||
# config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
|
||||
#
|
||||
mattr_accessor :preview_paths, instance_writer: false
|
||||
|
||||
# @deprecated Use `preview_paths` instead. Will be removed in v3.0.0.
|
||||
mattr_accessor :preview_path, instance_writer: false
|
||||
|
||||
# Set the entry route for component previews:
|
||||
#
|
||||
# config.view_component.preview_route = "/previews"
|
||||
#
|
||||
# Defaults to `/rails/view_components` when `show_previews` is enabled.
|
||||
#
|
||||
mattr_accessor :preview_route, instance_writer: false do
|
||||
"/rails/view_components"
|
||||
end
|
||||
|
||||
# Set the controller used for previewing components:
|
||||
#
|
||||
# config.view_component.preview_controller = "MyPreviewController"
|
||||
#
|
||||
# Defaults to `ViewComponentsController`.
|
||||
#
|
||||
mattr_accessor :preview_controller, instance_writer: false do
|
||||
"ViewComponentsController"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -22,9 +22,7 @@ module ViewComponent
|
|||
def render_preview(name)
|
||||
begin
|
||||
preview_klass = if respond_to?(:described_class)
|
||||
if described_class.nil?
|
||||
raise "`render_preview` expected a described_class, but it is nil."
|
||||
end
|
||||
raise "`render_preview` expected a described_class, but it is nil." if described_class.nil?
|
||||
|
||||
"#{described_class}Preview"
|
||||
else
|
||||
|
@ -32,12 +30,10 @@ module ViewComponent
|
|||
end
|
||||
preview_klass = preview_klass.constantize
|
||||
rescue NameError
|
||||
raise NameError.new(
|
||||
"`render_preview` expected to find #{preview_klass}, but it does not exist."
|
||||
)
|
||||
raise NameError, "`render_preview` expected to find #{preview_klass}, but it does not exist."
|
||||
end
|
||||
|
||||
previews_controller = build_controller(ViewComponent::Base.preview_controller.constantize)
|
||||
previews_controller = build_controller(Rails.application.config.view_component.preview_controller.constantize)
|
||||
previews_controller.request.params[:path] = "#{preview_klass.preview_name}/#{name}"
|
||||
previews_controller.response = ActionDispatch::Response.new
|
||||
result = previews_controller.previews
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
class ContainerComponent < ViewComponent::Base
|
||||
def call
|
||||
if ViewComponent::Base.render_monkey_patch_enabled || Rails.version.to_f >= 6.1
|
||||
if Rails.application.config.view_component.render_monkey_patch_enabled || Rails.version.to_f >= 6.1
|
||||
render HelpersProxyComponent.new
|
||||
else
|
||||
render_component HelpersProxyComponent.new
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require File.expand_path("../boot", __FILE__)
|
||||
require File.expand_path("boot", __dir__)
|
||||
|
||||
require "active_model/railtie"
|
||||
require "action_controller/railtie"
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
module ViewComponent
|
||||
class ConfigTest < TestCase
|
||||
def setup
|
||||
@config = ViewComponent::Config.new
|
||||
end
|
||||
|
||||
def test_defaults_are_correct
|
||||
assert_equal @config.generate, {}
|
||||
assert_equal @config.preview_controller, "ViewComponentsController"
|
||||
assert_equal @config.preview_route, "/rails/view_components"
|
||||
assert_equal @config.show_previews_source, false
|
||||
assert_equal @config.instrumentation_enabled, false
|
||||
assert_equal @config.render_monkey_patch_enabled, true
|
||||
assert_equal @config.show_previews, true
|
||||
assert_equal @config.preview_paths, ["#{Rails.root}/test/components/previews"]
|
||||
end
|
||||
|
||||
def test_preview_path_alias
|
||||
@config.preview_path << "some/new/path"
|
||||
assert_equal @config.preview_paths, @config.preview_path
|
||||
end
|
||||
|
||||
def test_preview_path_setter_alias
|
||||
old_value = @config.preview_path
|
||||
@config.preview_path = "some/new/path"
|
||||
assert_equal @config.preview_path, ["some/new/path"]
|
||||
@config.preview_path = old_value
|
||||
end
|
||||
|
||||
def test_all_methods_are_documented
|
||||
require "yard"
|
||||
require "rake"
|
||||
YARD::Rake::YardocTask.new
|
||||
Rake::Task["yard"].execute
|
||||
configuration_methods_to_document = YARD::RegistryStore.new.tap do |store|
|
||||
store.load!(".yardoc")
|
||||
end.get("ViewComponent::Config").meths.select(&:reader?).reject { |meth| meth.name == :config }
|
||||
default_options = ViewComponent::Config.defaults.keys
|
||||
accessors = ViewComponent::Config.instance_methods(false).reject do |method_name|
|
||||
method_name.to_s.end_with?("=") || method_name == :method_missing
|
||||
end
|
||||
options_defined_on_instance = Set[*default_options, *accessors]
|
||||
assert options_defined_on_instance.subset?(Set[*configuration_methods_to_document.map(&:name)]),
|
||||
"Not all configuration options are documented: #{configuration_methods_to_document.map(&:name) - options_defined_on_instance.to_a}"
|
||||
assert configuration_methods_to_document.map(&:docstring).all?(&:present?),
|
||||
"Configuration options are missing docstrings."
|
||||
end
|
||||
end
|
||||
end
|
|
@ -130,9 +130,7 @@ class LocaleGeneratorTest < Rails::Generators::TestCase
|
|||
|
||||
private
|
||||
|
||||
def with_generate_distinct_locale_files
|
||||
ViewComponent::Base.generate.distinct_locale_files = true
|
||||
yield
|
||||
ViewComponent::Base.generate.distinct_locale_files = false
|
||||
def with_generate_distinct_locale_files(&block)
|
||||
with_generate_option(:distinct_locale_files, true, &block)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -666,4 +666,19 @@ class IntegrationTest < ActionDispatch::IntegrationTest
|
|||
ActionController::Base.perform_caching = false
|
||||
Rails.cache.clear
|
||||
end
|
||||
|
||||
def test_config_options_shared_between_base_and_engine
|
||||
Rails.application.config.view_component.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) do
|
||||
assert_equal(config.public_send(option), ViewComponent::Base.public_send(option))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -94,7 +94,7 @@ class PreviewHelperTest < ActiveSupport::TestCase
|
|||
|
||||
mock = Minitest::Mock.new
|
||||
mock.expect :map, [expected_template_path]
|
||||
ViewComponent::Base.stub(:preview_paths, mock) do
|
||||
Rails.application.config.view_component.stub(:preview_paths, mock) do
|
||||
File.stub(:read, expected_source, [expected_template_path]) do
|
||||
template_data = PreviewHelper.find_template_data(
|
||||
lookup_context: lookup_context,
|
||||
|
@ -120,7 +120,7 @@ class PreviewHelperTest < ActiveSupport::TestCase
|
|||
|
||||
mock = Minitest::Mock.new
|
||||
mock.expect :map, []
|
||||
ViewComponent::Base.stub :preview_paths, mock do
|
||||
Rails.application.config.view_component.stub :preview_paths, mock do
|
||||
exception = assert_raises RuntimeError do
|
||||
PreviewHelper.find_template_data(
|
||||
lookup_context: lookup_context,
|
||||
|
@ -144,7 +144,7 @@ class PreviewHelperTest < ActiveSupport::TestCase
|
|||
|
||||
mock = Minitest::Mock.new
|
||||
mock.expect :map, [template_identifier + ".html.haml", template_identifier + ".html.erb"]
|
||||
ViewComponent::Base.stub :preview_paths, mock do
|
||||
Rails.application.config.view_component.stub :preview_paths, mock do
|
||||
exception = assert_raises RuntimeError do
|
||||
PreviewHelper.find_template_data(
|
||||
lookup_context: lookup_context,
|
||||
|
|
|
@ -35,19 +35,24 @@ ENV["RAILS_ENV"] = "test"
|
|||
require "view_component/deprecation"
|
||||
ViewComponent::Deprecation.behavior = :silence
|
||||
|
||||
require File.expand_path("../sandbox/config/environment.rb", __FILE__)
|
||||
require File.expand_path("sandbox/config/environment.rb", __dir__)
|
||||
require "rails/test_help"
|
||||
|
||||
def with_config_option(option_name, new_value)
|
||||
old_value = Rails.application.config.view_component.public_send(option_name)
|
||||
Rails.application.config.view_component.public_send("#{option_name}=", new_value)
|
||||
yield
|
||||
ensure
|
||||
Rails.application.config.view_component.public_send("#{option_name}=", old_value)
|
||||
end
|
||||
|
||||
# Sets custom preview paths in tests.
|
||||
#
|
||||
# @param new_value [Array<String>] List of preview paths
|
||||
# @yield Test code to run
|
||||
# @return [void]
|
||||
def with_preview_paths(new_value)
|
||||
old_value = Rails.application.config.view_component.preview_paths
|
||||
Rails.application.config.view_component.preview_paths = new_value
|
||||
yield
|
||||
Rails.application.config.view_component.preview_paths = old_value
|
||||
def with_preview_paths(new_value, &block)
|
||||
with_config_option(:preview_paths, new_value, &block)
|
||||
end
|
||||
|
||||
def with_preview_route(new_value)
|
||||
|
@ -55,6 +60,7 @@ def with_preview_route(new_value)
|
|||
Rails.application.config.view_component.preview_route = new_value
|
||||
app.reloader.reload!
|
||||
yield
|
||||
ensure
|
||||
Rails.application.config.view_component.preview_route = old_value
|
||||
app.reloader.reload!
|
||||
end
|
||||
|
@ -64,24 +70,17 @@ def with_preview_controller(new_value)
|
|||
Rails.application.config.view_component.preview_controller = new_value
|
||||
app.reloader.reload!
|
||||
yield
|
||||
ensure
|
||||
Rails.application.config.view_component.preview_controller = old_value
|
||||
app.reloader.reload!
|
||||
end
|
||||
|
||||
def with_custom_component_path(new_value)
|
||||
old_value = ViewComponent::Base.view_component_path
|
||||
ViewComponent::Base.view_component_path = new_value
|
||||
yield
|
||||
ensure
|
||||
ViewComponent::Base.view_component_path = old_value
|
||||
def with_custom_component_path(new_value, &block)
|
||||
with_config_option(:view_component_path, new_value, &block)
|
||||
end
|
||||
|
||||
def with_custom_component_parent_class(new_value)
|
||||
old_value = ViewComponent::Base.component_parent_class
|
||||
ViewComponent::Base.component_parent_class = new_value
|
||||
yield
|
||||
ensure
|
||||
ViewComponent::Base.component_parent_class = old_value
|
||||
def with_custom_component_parent_class(new_value, &block)
|
||||
with_config_option(:component_parent_class, new_value, &block)
|
||||
end
|
||||
|
||||
def with_application_component_class
|
||||
|
@ -91,12 +90,16 @@ ensure
|
|||
Object.send(:remove_const, :ApplicationComponent)
|
||||
end
|
||||
|
||||
def with_generate_sidecar(enabled)
|
||||
old_value = ViewComponent::Base.generate.sidecar
|
||||
ViewComponent::Base.generate.sidecar = enabled
|
||||
def with_generate_option(config_option, value)
|
||||
old_value = Rails.application.config.view_component.generate[config_option]
|
||||
Rails.application.config.view_component.generate[config_option] = value
|
||||
yield
|
||||
ensure
|
||||
ViewComponent::Base.generate.sidecar = old_value
|
||||
Rails.application.config.view_component.generate[config_option] = old_value
|
||||
end
|
||||
|
||||
def with_generate_sidecar(enabled, &block)
|
||||
with_generate_option(:sidecar, enabled, &block)
|
||||
end
|
||||
|
||||
def with_new_cache
|
||||
|
@ -111,15 +114,13 @@ ensure
|
|||
ViewComponent::CompileCache.cache = old_cache
|
||||
end
|
||||
|
||||
def without_template_annotations
|
||||
def without_template_annotations(&block)
|
||||
if ActionView::Base.respond_to?(:annotate_rendered_view_with_filenames)
|
||||
old_value = ActionView::Base.annotate_rendered_view_with_filenames
|
||||
ActionView::Base.annotate_rendered_view_with_filenames = false
|
||||
app.reloader.reload! if defined?(app)
|
||||
|
||||
with_new_cache do
|
||||
yield
|
||||
end
|
||||
with_new_cache(&block)
|
||||
|
||||
ActionView::Base.annotate_rendered_view_with_filenames = old_value
|
||||
app.reloader.reload! if defined?(app)
|
||||
|
@ -139,19 +140,12 @@ def modify_file(file, content)
|
|||
end
|
||||
end
|
||||
|
||||
def with_default_preview_layout(layout)
|
||||
old_value = ViewComponent::Base.default_preview_layout
|
||||
ViewComponent::Base.default_preview_layout = layout
|
||||
yield
|
||||
ViewComponent::Base.default_preview_layout = old_value
|
||||
def with_default_preview_layout(layout, &block)
|
||||
with_config_option(:default_preview_layout, layout, &block)
|
||||
end
|
||||
|
||||
def with_render_monkey_patch_config(enabled)
|
||||
old_default = ViewComponent::Base.render_monkey_patch_enabled
|
||||
ViewComponent::Base.render_monkey_patch_enabled = enabled
|
||||
yield
|
||||
ensure
|
||||
ViewComponent::Base.render_monkey_patch_enabled = old_default
|
||||
def with_render_monkey_patch_config(enabled, &block)
|
||||
with_config_option(:render_monkey_patch_enabled, enabled, &block)
|
||||
end
|
||||
|
||||
def with_compiler_mode(mode)
|
||||
|
|
Загрузка…
Ссылка в новой задаче