class ViewComponent::Base

def before_render

def before_render
  before_render_check
end

def before_render_check

def before_render_check
  # noop
end

def compile(raise_errors: false)

of work done each time a component is rendered.
Do as much work as possible in this step, as doing so reduces the amount

Compile templates to instance methods, assuming they haven't been compiled already.
def compile(raise_errors: false)
  template_compiler.compile(raise_errors: raise_errors)
end

def compiled?

def compiled?
  template_compiler.compiled?
end

def controller

def controller
  raise ViewContextCalledBeforeRenderError, "`controller` can only be called at render time." if view_context.nil?
  @controller ||= view_context.controller
end

def format

For caching, such as #cache_if
def format
  # Ruby 2.6 throws a warning without checking `defined?`, 2.7 does not
  if defined?(@variant)
    @variant
  end
end

def format

def format
  :html
end

def helpers

Provides a proxy to access helper methods from the context of the current controller
def helpers
  raise ViewContextCalledBeforeRenderError, "`helpers` can only be called at render time." if view_context.nil?
  @helpers ||= controller.view_context
end

def identifier

def identifier
  source_location
end

def inherited(child)

def inherited(child)
  # Compile so child will inherit compiled `call_*` template methods that
  # `compile` defines
  compile
  # 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
  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.
  child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].absolute_path
  # Removes the first part of the path and the extension.
  child.virtual_path = child.source_location.gsub(%r{(.*app/components)|(\.rb)}, "")
  super
end

def initialize(*); end

def initialize(*); end

def initialize_parameters

def initialize_parameters
  instance_method(:initialize).parameters
end

def provided_collection_parameter

def provided_collection_parameter
  @provided_collection_parameter ||= nil
end

def render(options = {}, args = {}, &block)

and not the component's `view_context`.
of the component. This is due to the partials compiled template method existing in the parent `view_context`,
This prevents an exception when rendering a partial inside of a component that has also been rendered outside

Re-use original view_context if we're not rendering a component.
def render(options = {}, args = {}, &block)
  if options.is_a? ViewComponent::Base
    super
  else
    view_context.render(options, args, &block)
  end
end

def render?

def render?
  true
end

def render_in(view_context, &block)


Hello, world!
returns:
<%= render MyComponent.new(title: "greeting") do %>world<% end %>
In use:

Hello, <%= content %>!
app/components/my_component.html.erb

end
end
@title = title
def initialize(title:)
class MyComponent < ViewComponent::Base
app/components/my_component.rb:

Example subclass:

returns HTML that has been escaped by the respective template handler

block: optional block to be captured within the view context
view_context: ActionView context from calling view

Entrypoint for rendering components.
def render_in(view_context, &block)
  self.class.compile(raise_errors: true)
  @view_context = view_context
  @lookup_context ||= view_context.lookup_context
  # required for path helpers in older Rails versions
  @view_renderer ||= view_context.view_renderer
  # For content_for
  @view_flow ||= view_context.view_flow
  # For i18n
  @virtual_path ||= virtual_path
  # For template variants (+phone, +desktop, etc.)
  @variant ||= @lookup_context.variants.first
  # For caching, such as #cache_if
  @current_template = nil unless defined?(@current_template)
  old_current_template = @current_template
  @current_template = self
  # Assign captured content passed to component as a block to @content
  @content = view_context.capture(self, &block) if block_given?
  before_render
  if render?
    render_template_for(@variant)
  else
    ""
  end
ensure
  @current_template = old_current_template
end

def request

that inhibits encapsulation & reuse.
Use sparingly as doing so introduces coupling
Exposes the current request to the component.
def request
  @request ||= controller.request
end

def short_identifier

Provide identifier for ActionView template annotations
def short_identifier
  @short_identifier ||= defined?(Rails.root) ? source_location.sub("#{Rails.root}/", "") : source_location
end

def template_compiler

def template_compiler
  @_template_compiler ||= Compiler.new(self)
end

def type

we'll eventually want to update this to support other types
def type
  "text/html"
end

def validate_collection_parameter!(validate_default: false)

rendering is optional.
is accepted, as support for collection
validate that the default parameter name
collection parameter. By default, we do not
Ensure the component initializer accepts the
def validate_collection_parameter!(validate_default: false)
  parameter = validate_default ? collection_parameter : provided_collection_parameter
  return unless parameter
  return if initialize_parameters.map(&:last).include?(parameter)
  # If Ruby cannot parse the component class, then the initalize
  # parameters will be empty and ViewComponent will not be able to render
  # the component.
  if initialize_parameters.empty?
    raise ArgumentError.new(
      "#{self} initializer is empty or invalid."
    )
  end
  raise ArgumentError.new(
    "#{self} initializer must accept " \
    "`#{parameter}` collection parameter."
  )
end

def view_cache_dependencies

For caching, such as #cache_if
def view_cache_dependencies
  []
end

def virtual_path

Exposes .virutal_path as an instance method
def virtual_path
  self.class.virtual_path
end

def with(area, content = nil, &block)

Assign the provided content to the content area accessor
def with(area, content = nil, &block)
  unless content_areas.include?(area)
    raise ArgumentError.new "Unknown content_area '#{area}' - expected one of '#{content_areas}'"
  end
  if block_given?
    content = view_context.capture(&block)
  end
  instance_variable_set("@#{area}".to_sym, content)
  nil
end

def with_collection(collection, **args)

Render a component collection.
def with_collection(collection, **args)
  Collection.new(self, collection, **args)
end

def with_collection_parameter(param)

Support overriding collection parameter name
def with_collection_parameter(param)
  @provided_collection_parameter = param
end

def with_content_areas(*areas)

def with_content_areas(*areas)
  if areas.include?(:content)
    raise ArgumentError.new ":content is a reserved content area name. Please use another name, such as ':body'"
  end
  attr_reader(*areas)
  self.content_areas = areas
end

def with_variant(variant)

def with_variant(variant)
  @variant = variant
  self
end