class ViewComponent::Base
def __vc_collection_counter_parameter
- Private: -
def __vc_collection_counter_parameter :"#{__vc_collection_parameter}_counter" end
def __vc_collection_iteration_parameter
- Private: -
def __vc_collection_iteration_parameter :"#{__vc_collection_parameter}_iteration" end
def __vc_collection_parameter
- Private: -
def __vc_collection_parameter provided_collection_parameter || name && name.demodulize.underscore.chomp("_component").to_sym end
def __vc_compile(raise_errors: false, force: false)
- Private: -
def __vc_compile(raise_errors: false, force: false) __vc_compiler.compile(raise_errors: raise_errors, force: force) end
def __vc_compiled?
- Private: -
def __vc_compiled? __vc_compiler.compiled? end
def __vc_compiler
- Private: -
def __vc_compiler @__vc_compiler ||= Compiler.new(self) end
def __vc_content_set_by_with_content_defined?
def __vc_content_set_by_with_content_defined? defined?(@__vc_content_set_by_with_content) end
def __vc_counter_argument_present?
- Private: -
def __vc_counter_argument_present? initialize_parameter_names.include?(__vc_collection_counter_parameter) end
def __vc_ensure_compiled
- Private: -
def __vc_ensure_compiled __vc_compile unless __vc_compiled? end
def __vc_iteration_argument_present?
- Private: -
def __vc_iteration_argument_present? initialize_parameter_names.include?(__vc_collection_iteration_parameter) end
def __vc_render_in_block_provided?
def __vc_render_in_block_provided? defined?(@view_context) && @view_context && @__vc_render_in_block end
def __vc_request
- Private: -
def __vc_request # The current request (if present, as mailers/jobs/etc do not have a request) @__vc_request ||= controller.request if controller.respond_to?(:request) end
def __vc_validate_collection_parameter!(validate_default: false)
- Private: -
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? # If Ruby can't parse the component class, then the initialize # parameters will be empty and ViewComponent will not be able to render # the component. if initialize_parameters.empty? raise EmptyOrInvalidInitializerError.new(name, parameter) end raise MissingCollectionArgumentError.new(name, parameter) end
def __vc_validate_initialization_parameters!
- Private: -
def __vc_validate_initialization_parameters! return unless initialize_parameter_names.include?(:content) raise ReservedParameterError.new(name, :content) end
def before_render
-
(void)
-
def before_render # noop end
def config
-
(ActiveSupport::OrderedOptions)
-
def config module_parents.each do |m| config = m.try(:config).try(:view_component) return config if config end ViewComponent::Config.current end
def content
-
(String)
-
def content @__vc_content_evaluated = true return @__vc_content if defined?(@__vc_content) @__vc_content = if __vc_render_in_block_provided? view_context.capture(self, &@__vc_render_in_block) elsif __vc_content_set_by_with_content_defined? @__vc_content_set_by_with_content end end
def content?
-
(Boolean)
-
def content? __vc_render_in_block_provided? || __vc_content_set_by_with_content_defined? end
def content_evaluated?
def content_evaluated? defined?(@__vc_content_evaluated) && @__vc_content_evaluated end
def controller
-
(ActionController::Base)
-
def controller raise ControllerCalledBeforeRenderError if view_context.nil? @__vc_controller ||= view_context.controller end
def helpers
-
(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 # always returns a new instance of the view context class. # # This allows ivars to remain persisted when using the same helper via # `helpers` across multiple components and partials. @__vc_helpers ||= __vc_original_view_context || controller.view_context end
def inherited(child)
- Private: -
def inherited(child) # Compile so child will inherit compiled `call_*` template methods that # `compile` defines __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.__vc_compiled? child.class_eval <<~RUBY, __FILE__, __LINE__ + 1 def render_template_for(requested_details) # Force compilation here so the compiler always redefines render_template_for. # This is mostly a safeguard to prevent infinite recursion. self.class.__vc_compile(raise_errors: true, force: true) # .__vc_compile replaces this method; call the new one render_template_for(requested_details) end RUBY end # 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 < 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. # We need to ignore `inherited` frames here as they indicate that `inherited` # has been re-defined by the consuming application, likely in ApplicationComponent. # We use `base_label` method here instead of `label` to avoid cases where the method # owner is included in a prefix like `ApplicationComponent.inherited`. child.identifier = caller_locations(1, 10).reject { |l| l.base_label == "inherited" }[0].path # If Rails application is loaded, removes the first part of the path and the extension. if defined?(Rails) && Rails.application child.virtual_path = child.identifier.gsub( /(.*#{Regexp.quote(ViewComponent::Base.config.view_component_path)})|(\.rb)/, "" ) end # Set collection parameter to the extended component child.with_collection_parameter provided_collection_parameter if instance_methods(false).include?(:render_template_for) vc_ancestor_calls = defined?(@__vc_ancestor_calls) ? @__vc_ancestor_calls.dup : [] vc_ancestor_calls.unshift(instance_method(:render_template_for)) child.instance_variable_set(:@__vc_ancestor_calls, vc_ancestor_calls) end super end
def initialize_parameter_names
def initialize_parameter_names return attribute_names.map(&:to_sym) if respond_to?(:attribute_names) initialize_parameters.map(&:last) end
def initialize_parameters
def initialize_parameters @initialize_parameters ||= instance_method(:initialize).parameters end
def maybe_escape_html(text)
def maybe_escape_html(text) return text if @current_template && !@current_template.html? return text if text.blank? if text.html_safe? text else yield html_escape(text) end end
def method_missing(method_name, *args) # rubocop:disable Style/MissingRespondToMissing
- Private: -
def method_missing(method_name, *args) # rubocop:disable Style/MissingRespondToMissing 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 raise end
def output_postamble
-
(String)
-
def output_postamble @@default_output_postamble ||= "".html_safe end
def output_preamble
-
(String)
-
def output_preamble @@default_output_preamble ||= "".html_safe end
def provided_collection_parameter
def provided_collection_parameter @provided_collection_parameter ||= nil end
def render(options = {}, args = {}, &block)
- Private: -
def render(options = {}, args = {}, &block) if options.respond_to?(:set_original_view_context) options.set_original_view_context(self.__vc_original_view_context) @view_context.render(options, args, &block) else __vc_original_view_context.render(options, args, &block) end end
def render?
-
(Boolean)
-
def render? true end
def render_in(view_context, &block)
-
(String)
-
def render_in(view_context, &block) self.class.__vc_compile(raise_errors: true) @view_context = view_context self.__vc_original_view_context ||= view_context @output_buffer = view_context.output_buffer @lookup_context ||= view_context.lookup_context # For content_for @view_flow ||= view_context.view_flow # For i18n @virtual_path ||= virtual_path # Describes the inferred request constraints (locales, formats, variants) @__vc_requested_details ||= @lookup_context.vc_requested_details # For caching, such as #cache_if @current_template = nil unless defined?(@current_template) old_current_template = @current_template if block && defined?(@__vc_content_set_by_with_content) raise DuplicateContentError.new(self.class.name) end @__vc_content_evaluated = false @__vc_render_in_block = block before_render if render? value = nil @output_buffer.with_buffer do rendered_template = render_template_for(@__vc_requested_details).to_s # Avoid allocating new string when output_preamble and output_postamble are blank value = if output_preamble.blank? && output_postamble.blank? rendered_template else safe_output_preamble + rendered_template + safe_output_postamble end end value else "" end ensure @current_template = old_current_template end
def render_parent
parent template considering the current variant and emits the result without
`super` also doesn't consider the current variant. `render_parent` renders the
```
<% super %> # doesn't double-render
<%= super %> # double-renders
```erb
double render if they emit the result.
Subclass components that call `super` inside their template code will cause a
def render_parent render_parent_to_string nil end
def render_parent_to_string
```
end
"
#{render_parent_to_string}
"def call
```ruby
to be used inside custom #call methods when a string result is desired, eg.
Renders the parent component to a string and returns it. This method is meant
def render_parent_to_string @__vc_parent_render_level ||= 0 # ensure a good starting value begin target_render = self.class.instance_variable_get(:@__vc_ancestor_calls)[@__vc_parent_render_level] @__vc_parent_render_level += 1 target_render.bind_call(self, @__vc_requested_details) ensure @__vc_parent_render_level -= 1 end end
def request
-
(ActionDispatch::Request)
-
def request __vc_request end
def safe_output_postamble
def safe_output_postamble maybe_escape_html(output_postamble) do Kernel.warn("WARNING: The #{self.class} component was provided an HTML-unsafe postamble. The postamble will be automatically escaped, but you may want to investigate.") end end
def safe_output_preamble
def safe_output_preamble maybe_escape_html(output_preamble) do Kernel.warn("WARNING: The #{self.class} component was provided an HTML-unsafe preamble. The preamble will be automatically escaped, but you may want to investigate.") end end
def set_original_view_context(view_context)
-
(void)
-
Parameters:
-
view_context
(ActionView::Base
) -- The original view context.
def set_original_view_context(view_context) # noop end
def sidecar_files(extensions)
-
extensions
(Array
) -- Extensions of which to return matching sidecar files.
def sidecar_files(extensions) return [] unless identifier extensions = extensions.join(",") # view files in a directory named like the component directory = File.dirname(identifier) filename = File.basename(identifier, ".rb") component_name = name.demodulize.underscore # Add support for nested components defined in the same file. # # for example # # class MyComponent < ViewComponent::Base # class MyOtherComponent < ViewComponent::Base # end # end # # Without this, `MyOtherComponent` will not look for `my_component/my_other_component.html.erb` nested_component_files = if name.include?("::") && component_name != filename Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"] else [] end # view files in the same directory as the component sidecar_files = Dir["#{directory}/#{component_name}.*{#{extensions}}"] sidecar_directory_files = Dir["#{directory}/#{component_name}/#{filename}.*{#{extensions}}"] (sidecar_files - [identifier] + sidecar_directory_files + nested_component_files).uniq end
def splatted_keyword_argument_present?
def splatted_keyword_argument_present? initialize_parameters.flatten.include?(:keyrest) && !initialize_parameters.include?([:keyrest, :**]) # Un-named splatted keyword args don't count! end
def strip_trailing_whitespace(value = true)
-
value
(Boolean
) -- Whether to strip newlines.
Deprecated:
- Use the new component-local configuration option instead.
def strip_trailing_whitespace(value = true) ViewComponent::Deprecation.deprecation_warning( "strip_trailing_whitespace", <<~DOC Use the new component-local configuration option instead: class #{self.class.name} < ViewComponent::Base configure_view_component do |config| config.strip_trailing_whitespace = #{value} end end DOC ) view_component_config.strip_trailing_whitespace = value end
def strip_trailing_whitespace?
-
(Boolean)
-
def strip_trailing_whitespace? view_component_config.strip_trailing_whitespace end
def view_cache_dependencies
- Private: -
def view_cache_dependencies [] end
def virtual_path
- Private: -
def virtual_path self.class.virtual_path end
def with_collection(collection, spacer_component: nil, **args)
-
args
(Arguments
) -- Arguments to pass to the ViewComponent every time. -
spacer_component
(ViewComponent::Base
) -- Component instance to be rendered between items. -
collection
(Enumerable
) -- A list of items to pass the ViewComponent one at a time.
def with_collection(collection, spacer_component: nil, **args) Collection.new(self, collection, spacer_component, **args) end
def with_collection_parameter(parameter)
-
parameter
(Symbol
) -- The parameter name used when rendering elements of a collection.
def with_collection_parameter(parameter) @provided_collection_parameter = parameter @initialize_parameters = nil end