Class: ViewComponent::Base

Inherits:
ActionView::Base
  • Object
show all
Includes:
ActiveSupport::Configurable, Previewable
Defined in:
lib/view_component/base.rb

Direct Known Subclasses

ActionView::Component::Base

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeBase

Returns a new instance of Base.



74
# File 'lib/view_component/base.rb', line 74

def initialize(*); end

Class Method Details

.call_method_name(variant) ⇒ Object



142
143
144
145
146
147
148
# File 'lib/view_component/base.rb', line 142

def call_method_name(variant)
  if variant.present? && variants.include?(variant)
    "call_#{variant}"
  else
    "call"
  end
end

.compile(raise_template_errors: false) ⇒ Object

Compile templates to instance methods, assuming they haven’t been compiled already. We could in theory do this on app boot, at least in production environments. Right now this just compiles the first time the component is rendered.



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/view_component/base.rb', line 175

def compile(raise_template_errors: false)
  return if compiled? || inlined?

  if template_errors.present?
    raise ViewComponent::TemplateError.new(template_errors) if raise_template_errors
    return false
  end

  templates.each do |template|
    class_eval <<-RUBY, template[:path], -1
      def #{call_method_name(template[:variant])}
        @output_buffer = ActionView::OutputBuffer.new
        #{compiled_template(template[:path])}
      end
    RUBY
  end

  @compiled = true
end

.compile!Object



168
169
170
# File 'lib/view_component/base.rb', line 168

def compile!
  compile(raise_template_errors: true)
end

.compiled?Boolean

Returns:

  • (Boolean)


160
161
162
# File 'lib/view_component/base.rb', line 160

def compiled?
  @compiled && ActionView::Base.cache_template_loading
end

.identifierObject



204
205
206
# File 'lib/view_component/base.rb', line 204

def identifier
  source_location
end

.inherited(child) ⇒ Object



134
135
136
137
138
139
140
# File 'lib/view_component/base.rb', line 134

def inherited(child)
  if defined?(Rails)
    child.include Rails.application.routes.url_helpers unless child < Rails.application.routes.url_helpers
  end

  super
end

.inlined?Boolean

Returns:

  • (Boolean)


164
165
166
# File 'lib/view_component/base.rb', line 164

def inlined?
  instance_methods(false).grep(/^call/).present? && templates.empty?
end

.source_locationObject



150
151
152
153
154
155
156
157
158
# File 'lib/view_component/base.rb', line 150

def source_location
  @source_location ||=
    begin
      # Require `#initialize` to be defined so that we can use `method#source_location`
      # to look up the filename of the component.
      initialize_method = instance_method(:initialize)
      initialize_method.source_location[0] if initialize_method.owner == self
    end
end

.typeObject

we’ll eventually want to update this to support other types



200
201
202
# File 'lib/view_component/base.rb', line 200

def type
  "text/html"
end

.variantsObject



195
196
197
# File 'lib/view_component/base.rb', line 195

def variants
  templates.map { |template| template[:variant] }
end

.with_content_areas(*areas) ⇒ Object



208
209
210
211
212
213
214
# File 'lib/view_component/base.rb', line 208

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

Instance Method Details

#before_render_checkObject



66
67
68
# File 'lib/view_component/base.rb', line 66

def before_render_check
  # noop
end

#controllerObject



84
85
86
# File 'lib/view_component/base.rb', line 84

def controller
  @controller ||= view_context.controller
end

#formatObject

:nodoc:



102
103
104
# File 'lib/view_component/base.rb', line 102

def format # :nodoc:
  @variant
end

#helpersObject

Provides a proxy to access helper methods through



89
90
91
# File 'lib/view_component/base.rb', line 89

def helpers
  @helpers ||= view_context
end

#render(options = {}, args = {}, &block) ⇒ Object



76
77
78
79
80
81
82
# File 'lib/view_component/base.rb', line 76

def render(options = {}, args = {}, &block)
  if options.is_a?(String) || (options.is_a?(Hash) && options.has_key?(:partial))
    view_context.render(options, args, &block)
  else
    super
  end
end

#render?Boolean

Returns:

  • (Boolean)


70
71
72
# File 'lib/view_component/base.rb', line 70

def render?
  true
end

#render_in(view_context, &block) ⇒ Object

Entrypoint for rendering components.

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

returns HTML that has been escaped by the respective template handler

Example subclass:

app/components/my_component.rb: class MyComponent < ViewComponent::Base

def initialize(title:)
  @title = title
end

end

app/components/my_component.html.erb <span title=“<%= @title %>”>Hello, <%= content %>!</span>

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



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/view_component/base.rb', line 41

def render_in(view_context, &block)
  self.class.compile!
  @view_context = view_context
  @view_renderer ||= view_context.view_renderer
  @lookup_context ||= view_context.lookup_context
  @view_flow ||= view_context.view_flow
  @virtual_path ||= virtual_path
  @variant = @lookup_context.variants.first

  old_current_template = @current_template
  @current_template = self

  @content = view_context.capture(self, &block) if block_given?

  before_render_check

  if render?
    send(self.class.call_method_name(@variant))
  else
    ""
  end
ensure
  @current_template = old_current_template
end

#view_cache_dependenciesObject



98
99
100
# File 'lib/view_component/base.rb', line 98

def view_cache_dependencies
  []
end

#virtual_pathObject

Removes the first part of the path and the extension.



94
95
96
# File 'lib/view_component/base.rb', line 94

def virtual_path
  self.class.source_location.gsub(%r{(.*app/components)|(\.rb)}, "")
end

#with(area, content = nil, &block) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/view_component/base.rb', line 106

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