From 44159fd6e23d88d4f8674cedc12ea549ca015af3 Mon Sep 17 00:00:00 2001 From: Joel Hawksley Date: Wed, 25 Sep 2024 16:12:44 -0600 Subject: [PATCH 1/2] Underscore internal methods that are public We have plenty of internal methods that are not meant to be used externally, often tagged @private. In a recent office hours, @camertron, @blakewilliams and I noted that we should better indicate when an internal method should not be dependended on. I've done so here by adding a `__vc_` prefix as is convention in other parts of the codebase already. As a separate exercise, we should probably re-evaluate our public API surface area and see if we can make any public methods private. --- lib/view_component/base.rb | 52 +++++++++++++------------- lib/view_component/collection.rb | 8 ++-- lib/view_component/compiler.rb | 10 ++--- lib/view_component/engine.rb | 2 +- lib/view_component/slotable.rb | 34 ++++++++--------- lib/view_component/slotable_default.rb | 2 +- lib/view_component/translatable.rb | 16 ++++---- test/sandbox/test/integration_test.rb | 2 +- test/sandbox/test/rendering_test.rb | 13 ++----- test/sandbox/test/slotable_test.rb | 2 +- test/sandbox/test/translatable_test.rb | 2 +- 11 files changed, 69 insertions(+), 74 deletions(-) diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 4422c2bd3..3b02d943c 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -72,7 +72,7 @@ def set_original_view_context(view_context) # # @return [String] def render_in(view_context, &block) - self.class.compile(raise_errors: true) + self.class.__vc_compile(raise_errors: true) @view_context = view_context self.__vc_original_view_context ||= view_context @@ -109,7 +109,7 @@ def render_in(view_context, &block) if render? rendered_template = - if compiler.renders_template_for?(@__vc_variant, request&.format&.to_sym) + if __vc_compiler.renders_template_for?(@__vc_variant, request&.format&.to_sym) render_template_for(@__vc_variant, request&.format&.to_sym) else maybe_escape_html(render_template_for(@__vc_variant, request&.format&.to_sym)) do @@ -351,8 +351,8 @@ def safe_output_postamble end end - def compiler - @compiler ||= self.class.compiler + def __vc_compiler + @compiler ||= self.class.__vc_compiler end # Set the controller used for testing components: @@ -503,18 +503,18 @@ def with_collection(collection, **args) def inherited(child) # Compile so child will inherit compiled `call_*` template methods that # `compile` defines - compile + __vc_compile # 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, # meaning it will not be called for any children and thus not compile their templates. - if !child.instance_methods(false).include?(:render_template_for) && !child.compiled? + if !child.instance_methods(false).include?(:render_template_for) && !child.__vc_compiled? child.class_eval <<~RUBY, __FILE__, __LINE__ + 1 def render_template_for(variant = nil, format = nil) # Force compilation here so the compiler always redefines render_template_for. # This is mostly a safeguard to prevent infinite recursion. - self.class.compile(raise_errors: true, force: true) + self.class.__vc_compile(raise_errors: true, force: true) # .compile replaces this method; call the new one render_template_for(variant, format) end @@ -555,22 +555,22 @@ def render_template_for(variant = nil, format = nil) end # @private - def compiled? - compiler.compiled? + def __vc_compiled? + __vc_compiler.compiled? end # @private - def ensure_compiled - compile unless compiled? + def __vc_ensure_compiled + __vc_compile unless __vc_compiled? end # @private - def compile(raise_errors: false, force: false) - compiler.compile(raise_errors: raise_errors, force: force) + def __vc_compile(raise_errors: false, force: false) + __vc_compiler.compile(raise_errors: raise_errors, force: force) end # @private - def compiler + def __vc_compiler @__vc_compiler ||= Compiler.new(self) end @@ -612,8 +612,8 @@ def strip_trailing_whitespace? # is accepted, as support for collection # rendering is optional. # @private TODO: add documentation - def validate_collection_parameter!(validate_default: false) - parameter = validate_default ? collection_parameter : provided_collection_parameter + def __vc_validate_collection_parameter!(validate_default: false) + parameter = validate_default ? __vc_collection_parameter : provided_collection_parameter return unless parameter return if initialize_parameter_names.include?(parameter) || splatted_keyword_argument_present? @@ -632,35 +632,35 @@ def validate_collection_parameter!(validate_default: false) # invalid parameters that could override the framework's # methods. # @private TODO: add documentation - def validate_initialization_parameters! + def __vc_validate_initialization_parameters! return unless initialize_parameter_names.include?(RESERVED_PARAMETER) raise ReservedParameterError.new(name, RESERVED_PARAMETER) end # @private - def collection_parameter + def __vc_collection_parameter provided_collection_parameter || name && name.demodulize.underscore.chomp("_component").to_sym end # @private - def collection_counter_parameter - :"#{collection_parameter}_counter" + def __vc_collection_counter_parameter + :"#{__vc_collection_parameter}_counter" end # @private - def counter_argument_present? - initialize_parameter_names.include?(collection_counter_parameter) + def __vc_counter_argument_present? + initialize_parameter_names.include?(__vc_collection_counter_parameter) end # @private - def collection_iteration_parameter - :"#{collection_parameter}_iteration" + def __vc_collection_iteration_parameter + :"#{__vc_collection_parameter}_iteration" end # @private - def iteration_argument_present? - initialize_parameter_names.include?(collection_iteration_parameter) + def __vc_iteration_argument_present? + initialize_parameter_names.include?(__vc_collection_iteration_parameter) end private diff --git a/lib/view_component/collection.rb b/lib/view_component/collection.rb index e4082dc0d..ccf57ed18 100644 --- a/lib/view_component/collection.rb +++ b/lib/view_component/collection.rb @@ -27,7 +27,7 @@ def components iterator = ActionView::PartialIteration.new(@collection.size) - component.validate_collection_parameter!(validate_default: true) + component.__vc_validate_collection_parameter!(validate_default: true) @components = @collection.map do |item| component.new(**component_options(item, iterator)).tap do |component| @@ -63,9 +63,9 @@ def collection_variable(object) end def component_options(item, iterator) - item_options = {component.collection_parameter => item} - item_options[component.collection_counter_parameter] = iterator.index if component.counter_argument_present? - item_options[component.collection_iteration_parameter] = iterator.dup if component.iteration_argument_present? + item_options = {component.__vc_collection_parameter => item} + item_options[component.__vc_collection_counter_parameter] = iterator.index if component.__vc_counter_argument_present? + item_options[component.__vc_collection_iteration_parameter] = iterator.dup if component.__vc_iteration_argument_present? @options.merge(item_options) end diff --git a/lib/view_component/compiler.rb b/lib/view_component/compiler.rb index e29141a3e..f84204529 100644 --- a/lib/view_component/compiler.rb +++ b/lib/view_component/compiler.rb @@ -27,20 +27,20 @@ def compile(raise_errors: false, force: false) gather_templates if self.class.development_mode && @templates.any?(&:requires_compiled_superclass?) - @component.superclass.compile(raise_errors: raise_errors) + @component.superclass.__vc_compile(raise_errors: raise_errors) end return if gather_template_errors(raise_errors).any? if raise_errors - @component.validate_initialization_parameters! - @component.validate_collection_parameter! + @component.__vc_validate_initialization_parameters! + @component.__vc_validate_collection_parameter! end define_render_template_for - @component.register_default_slots - @component.build_i18n_backend + @component.__vc_register_default_slots + @component.__vc_build_i18n_backend CompileCache.register(@component) end diff --git a/lib/view_component/engine.rb b/lib/view_component/engine.rb index 9a52c661f..67375519c 100644 --- a/lib/view_component/engine.rb +++ b/lib/view_component/engine.rb @@ -81,7 +81,7 @@ class Engine < Rails::Engine # :nodoc: initializer "view_component.eager_load_actions" do ActiveSupport.on_load(:after_initialize) do - ViewComponent::Base.descendants.each(&:compile) if Rails.application.config.eager_load + ViewComponent::Base.descendants.each(&:__vc_compile) if Rails.application.config.eager_load end end diff --git a/lib/view_component/slotable.rb b/lib/view_component/slotable.rb index a8c298965..cdd1dfa55 100644 --- a/lib/view_component/slotable.rb +++ b/lib/view_component/slotable.rb @@ -16,8 +16,8 @@ module Slotable # Setup component slot state included do # Hash of registered Slots - class_attribute :registered_slots - self.registered_slots = {} + class_attribute :__vc_registered_slots + self.__vc_registered_slots = {} end class_methods do @@ -78,7 +78,7 @@ def renders_one(slot_name, callable = nil) validate_singular_slot_name(slot_name) if callable.is_a?(Hash) && callable.key?(:types) - register_polymorphic_slot(slot_name, callable[:types], collection: false) + __vc_register_polymorphic_slot(slot_name, callable[:types], collection: false) else validate_plural_slot_name(ActiveSupport::Inflector.pluralize(slot_name).to_sym) @@ -148,7 +148,7 @@ def renders_many(slot_name, callable = nil) validate_plural_slot_name(slot_name) if callable.is_a?(Hash) && callable.key?(:types) - register_polymorphic_slot(slot_name, callable[:types], collection: true) + __vc_register_polymorphic_slot(slot_name, callable[:types], collection: true) else singular_name = ActiveSupport::Inflector.singularize(slot_name) validate_singular_slot_name(ActiveSupport::Inflector.singularize(slot_name).to_sym) @@ -189,12 +189,12 @@ def renders_many(slot_name, callable = nil) end def slot_type(slot_name) - registered_slot = registered_slots[slot_name] + registered_slot = __vc_registered_slots[slot_name] if registered_slot registered_slot[:collection] ? :collection : :single else plural_slot_name = ActiveSupport::Inflector.pluralize(slot_name).to_sym - plural_registered_slot = registered_slots[plural_slot_name] + plural_registered_slot = __vc_registered_slots[plural_slot_name] plural_registered_slot&.fetch(:collection) ? :collection_item : nil end end @@ -202,7 +202,7 @@ def slot_type(slot_name) def inherited(child) # Clone slot configuration into child class # see #test_slots_pollution - child.registered_slots = registered_slots.clone + child.__vc_registered_slots = __vc_registered_slots.clone # Add a module for slot methods, allowing them to be overriden by the component class # see #test_slot_name_can_be_overriden @@ -215,7 +215,7 @@ def inherited(child) super end - def register_polymorphic_slot(slot_name, types, collection:) + def __vc_register_polymorphic_slot(slot_name, types, collection:) self::GeneratedSlotMethods.define_method(slot_name) do get_slot(slot_name) end @@ -262,25 +262,25 @@ def register_polymorphic_slot(slot_name, types, collection:) end end - registered_slots[slot_name] = { + __vc_registered_slots[slot_name] = { collection: collection, renderable_hash: renderable_hash } end # Called by the compiler, as instance methods are not defined when slots are first registered - def register_default_slots - registered_slots.each do |slot_name, config| + def __vc_register_default_slots + __vc_registered_slots.each do |slot_name, config| config[:default_method] = instance_methods.find { |method_name| method_name == :"default_#{slot_name}" } - registered_slots[slot_name] = config + __vc_registered_slots[slot_name] = config end end private def register_slot(slot_name, **kwargs) - registered_slots[slot_name] = define_slot(slot_name, **kwargs) + __vc_registered_slots[slot_name] = define_slot(slot_name, **kwargs) end def define_slot(slot_name, collection:, callable:) @@ -332,7 +332,7 @@ def validate_singular_slot_name(slot_name) end def raise_if_slot_registered(slot_name) - if registered_slots.key?(slot_name) + if __vc_registered_slots.key?(slot_name) # TODO remove? This breaks overriding slots when slots are inherited raise RedefinedSlotError.new(name, slot_name) end @@ -359,7 +359,7 @@ def raise_if_slot_name_uncountable(slot_name) def get_slot(slot_name) content unless content_evaluated? # ensure content is loaded so slots will be defined - slot = self.class.registered_slots[slot_name] + slot = self.class.__vc_registered_slots[slot_name] @__vc_set_slots ||= {} if @__vc_set_slots[slot_name] @@ -372,7 +372,7 @@ def get_slot(slot_name) end def set_slot(slot_name, slot_definition = nil, *args, &block) - slot_definition ||= self.class.registered_slots[slot_name] + slot_definition ||= self.class.__vc_registered_slots[slot_name] slot = Slot.new(self) # Passing the block to the sub-component wrapper like this has two @@ -430,7 +430,7 @@ def set_slot(slot_name, slot_definition = nil, *args, &block) ruby2_keywords(:set_slot) if respond_to?(:ruby2_keywords, true) def set_polymorphic_slot(slot_name, poly_type = nil, *args, &block) - slot_definition = self.class.registered_slots[slot_name] + slot_definition = self.class.__vc_registered_slots[slot_name] if !slot_definition[:collection] && (defined?(@__vc_set_slots) && @__vc_set_slots[slot_name]) raise ContentAlreadySetForPolymorphicSlotError.new(slot_name) diff --git a/lib/view_component/slotable_default.rb b/lib/view_component/slotable_default.rb index 538622f01..3c923969a 100644 --- a/lib/view_component/slotable_default.rb +++ b/lib/view_component/slotable_default.rb @@ -3,7 +3,7 @@ module SlotableDefault def get_slot(slot_name) @__vc_set_slots ||= {} - return super unless !@__vc_set_slots[slot_name] && (default_method = registered_slots[slot_name][:default_method]) + return super unless !@__vc_set_slots[slot_name] && (default_method = __vc_registered_slots[slot_name][:default_method]) renderable_value = send(default_method) slot = Slot.new(self) diff --git a/lib/view_component/translatable.rb b/lib/view_component/translatable.rb index 422218207..551989e82 100644 --- a/lib/view_component/translatable.rb +++ b/lib/view_component/translatable.rb @@ -13,7 +13,7 @@ module Translatable TRANSLATION_EXTENSIONS = %w[yml yaml].freeze included do - class_attribute :i18n_backend, instance_writer: false, instance_predicate: false + class_attribute :__vc_i18n_backend, instance_writer: false, instance_predicate: false end class_methods do @@ -21,8 +21,8 @@ def i18n_scope @i18n_scope ||= virtual_path.sub(%r{^/}, "").gsub(%r{/_?}, ".") end - def build_i18n_backend - return if compiled? + def __vc_build_i18n_backend + return if __vc_compiled? # We need to load the translations files from the ancestors so a component # can inherit translations from its parent and is able to overwrite them. @@ -33,7 +33,7 @@ def build_i18n_backend end # In development it will become nil if the translations file is removed - self.i18n_backend = if translation_files.any? + self.__vc_i18n_backend = if translation_files.any? I18nBackend.new( i18n_scope: i18n_scope, load_paths: translation_files @@ -52,12 +52,12 @@ def i18n_key(key, scope = nil) def translate(key = nil, **options) return key.map { |k| translate(k, **options) } if key.is_a?(Array) - ensure_compiled + __vc_ensure_compiled locale = options.delete(:locale) || ::I18n.locale key = i18n_key(key, options.delete(:scope)) - i18n_backend.translate(locale, key, options) + __vc_i18n_backend.translate(locale, key, options) end alias_method :t, :translate @@ -91,7 +91,7 @@ def store_translations(locale, data, options = EMPTY_HASH) def translate(key = nil, **options) raise ViewComponent::TranslateCalledBeforeRenderError if view_context.nil? - return super unless i18n_backend + return super unless __vc_i18n_backend return key.map { |k| translate(k, **options) } if key.is_a?(Array) locale = options.delete(:locale) || ::I18n.locale @@ -103,7 +103,7 @@ def translate(key = nil, **options) if key.start_with?(i18n_scope + ".") translated = catch(:exception) do - i18n_backend.translate(locale, key, options) + __vc_i18n_backend.translate(locale, key, options) end # Fallback to the global translations diff --git a/test/sandbox/test/integration_test.rb b/test/sandbox/test/integration_test.rb index 76e65cf4d..371e4006c 100644 --- a/test/sandbox/test/integration_test.rb +++ b/test/sandbox/test/integration_test.rb @@ -111,7 +111,7 @@ def test_inherited_component_with_call_method_does_not_recompile_superclass assert_select "div", "hello world" assert_response :success - compile_method_lines = UncompilableComponent.method(:compile).source.split("\n") + compile_method_lines = UncompilableComponent.method(:__vc_compile).source.split("\n") compile_method_lines.insert(1, 'raise "this should not happen" if self.name == "UncompilableComponent"') UncompilableComponent.instance_eval compile_method_lines.join("\n") diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index 6f8e66b51..440ec4388 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -79,14 +79,12 @@ def test_raises_error_when_with_content_is_called_without_any_values def test_render_without_template render_inline(InlineComponent.new) - assert_predicate InlineComponent, :compiled? assert_selector("input[type='text'][name='name']") end def test_render_child_without_template render_inline(InlineChildComponent.new) - assert_predicate InlineChildComponent, :compiled? assert_selector("input[type='text'][name='name']") end @@ -207,7 +205,6 @@ def test_renders_inline_variant_template_when_variant_template_is_not_present with_variant :inline_variant do render_inline(InlineVariantComponent.new) - assert_predicate InlineVariantComponent, :compiled? assert_selector("input[type='text'][name='inline_variant']") end end @@ -216,7 +213,6 @@ def test_renders_child_inline_variant_when_variant_template_is_not_present with_variant :inline_variant do render_inline(InlineVariantChildComponent.new) - assert_predicate InlineVariantChildComponent, :compiled? assert_selector("input[type='text'][name='inline_variant']") end end @@ -424,7 +420,7 @@ def test_compiles_unrendered_component # but that might have been thrown away if code-reloading is enabled skip unless Rails.env.cache_classes? - assert UnreferencedComponent.compiled? + assert UnreferencedComponent.__vc_compiled? end def test_compiles_components_without_initializers @@ -432,7 +428,7 @@ def test_compiles_components_without_initializers # but that might have been thrown away if code-reloading is enabled skip unless Rails.env.cache_classes? - assert MissingInitializerComponent.compiled? + assert MissingInitializerComponent.__vc_compiled? end def test_renders_when_initializer_is_not_defined @@ -722,7 +718,7 @@ def test_component_with_invalid_parameter_names exception = assert_raises ViewComponent::ReservedParameterError do - InvalidParametersComponent.compile(raise_errors: true) + InvalidParametersComponent.__vc_compile(raise_errors: true) end assert_match(/InvalidParametersComponent initializer can't accept the parameter/, exception.message) @@ -736,7 +732,7 @@ def test_component_with_invalid_named_parameter_names exception = assert_raises ViewComponent::ReservedParameterError do - InvalidNamedParametersComponent.compile(raise_errors: true) + InvalidNamedParametersComponent.__vc_compile(raise_errors: true) end assert_match( @@ -779,7 +775,6 @@ def test_inherited_component_overrides_inherits_template def test_inherited_inline_component_inherits_inline_method render_inline(InlineInheritedComponent.new) - assert_predicate InlineInheritedComponent, :compiled? assert_selector("input[type='text'][name='name']") end diff --git a/test/sandbox/test/slotable_test.rb b/test/sandbox/test/slotable_test.rb index 83f44a8ce..2a73e0f73 100644 --- a/test/sandbox/test/slotable_test.rb +++ b/test/sandbox/test/slotable_test.rb @@ -250,7 +250,7 @@ def test_sub_components_pollution new_component_class = Class.new(ViewComponent::Base) # this returned: # [SlotsComponent::Subtitle, SlotsComponent::Tab...] - assert_empty new_component_class.registered_slots + assert_empty new_component_class.__vc_registered_slots end def test_renders_slots_with_before_render_hook diff --git a/test/sandbox/test/translatable_test.rb b/test/sandbox/test/translatable_test.rb index 3d1b7129b..5dfc04ceb 100644 --- a/test/sandbox/test/translatable_test.rb +++ b/test/sandbox/test/translatable_test.rb @@ -95,7 +95,7 @@ def test_translate_uses_the_helper_when_no_sidecar_file_is_provided ) do assert_equal "MISSING", translate(".hello", default: "MISSING") assert_equal "Hello from Rails translations!", translate("hello") - assert_nil TranslatableComponent.i18n_backend + assert_nil TranslatableComponent.__vc_i18n_backend end ensure ViewComponent::CompileCache.invalidate_class!(TranslatableComponent) From ccb950567f98e1f8f46416e79ed50f350574da4f Mon Sep 17 00:00:00 2001 From: Joel Hawksley Date: Thu, 26 Sep 2024 10:05:09 -0600 Subject: [PATCH 2/2] re-add compiled? checks in tests --- test/sandbox/test/rendering_test.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index 440ec4388..3089ec5c7 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -79,12 +79,14 @@ def test_raises_error_when_with_content_is_called_without_any_values def test_render_without_template render_inline(InlineComponent.new) + assert_predicate InlineComponent, :__vc_compiled? assert_selector("input[type='text'][name='name']") end def test_render_child_without_template render_inline(InlineChildComponent.new) + assert_predicate InlineChildComponent, :__vc_compiled? assert_selector("input[type='text'][name='name']") end @@ -205,6 +207,7 @@ def test_renders_inline_variant_template_when_variant_template_is_not_present with_variant :inline_variant do render_inline(InlineVariantComponent.new) + assert_predicate InlineVariantComponent, :__vc_compiled? assert_selector("input[type='text'][name='inline_variant']") end end @@ -213,6 +216,7 @@ def test_renders_child_inline_variant_when_variant_template_is_not_present with_variant :inline_variant do render_inline(InlineVariantChildComponent.new) + assert_predicate InlineVariantChildComponent, :__vc_compiled? assert_selector("input[type='text'][name='inline_variant']") end end @@ -775,6 +779,7 @@ def test_inherited_component_overrides_inherits_template def test_inherited_inline_component_inherits_inline_method render_inline(InlineInheritedComponent.new) + assert_predicate InlineInheritedComponent, :__vc_compiled? assert_selector("input[type='text'][name='name']") end