Class: ViewComponent::Base
- Inherits:
-
ActionView::Base
- Object
- ActionView::Base
- ViewComponent::Base
- Includes:
- ActiveSupport::Configurable, Previewable
- Defined in:
- lib/view_component/base.rb
Class Attribute Summary collapse
-
.source_location ⇒ Object
Returns the value of attribute source_location.
Class Method Summary collapse
- .call_method_name(variant) ⇒ Object
-
.compile(raise_errors: false) ⇒ Object
Compile templates to instance methods, assuming they haven’t been compiled already.
- .compiled? ⇒ Boolean
- .format ⇒ Object
- .identifier ⇒ Object
- .inherited(child) ⇒ Object
-
.short_identifier ⇒ Object
Provide identifier for ActionView template annotations.
-
.type ⇒ Object
we’ll eventually want to update this to support other types.
-
.validate_collection_parameter!(validate_default: false) ⇒ Object
Ensure the component initializer accepts the collection parameter.
-
.with_collection(*args) ⇒ Object
Render a component collection.
-
.with_collection_parameter(param) ⇒ Object
Support overriding collection parameter name.
- .with_content_areas(*areas) ⇒ Object
Instance Method Summary collapse
- #before_render_check ⇒ Object
- #controller ⇒ Object
-
#format ⇒ Object
For caching, such as #cache_if.
-
#helpers ⇒ Object
Provides a proxy to access helper methods.
-
#initialize ⇒ Base
constructor
A new instance of Base.
-
#render(options = {}, args = {}, &block) ⇒ Object
If trying to render a partial or template inside a component, pass the render call to the parent view_context.
- #render? ⇒ Boolean
-
#render_in(view_context, &block) ⇒ Object
Entrypoint for rendering components.
-
#view_cache_dependencies ⇒ Object
For caching, such as #cache_if.
-
#virtual_path ⇒ Object
Removes the first part of the path and the extension.
-
#with(area, content = nil, &block) ⇒ Object
Assign the provided content to the content area accessor.
Constructor Details
#initialize ⇒ Base
Returns a new instance of Base.
88 |
# File 'lib/view_component/base.rb', line 88 def initialize(*); end |
Class Attribute Details
.source_location ⇒ Object
Returns the value of attribute source_location.
156 157 158 |
# File 'lib/view_component/base.rb', line 156 def source_location @source_location end |
Class Method Details
.call_method_name(variant) ⇒ Object
182 183 184 185 186 187 188 |
# File 'lib/view_component/base.rb', line 182 def call_method_name(variant) if variant.present? && variants.include?(variant) "call_#{variant}" else "call" end end |
.compile(raise_errors: false) ⇒ Object
Compile templates to instance methods, assuming they haven’t been compiled already.
Do as much work as possible in this step, as doing so reduces the amount of work done each time a component is rendered.
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/view_component/base.rb', line 200 def compile(raise_errors: false) return if compiled? if template_errors.present? raise ViewComponent::TemplateError.new(template_errors) if raise_errors return false end # Remove any existing singleton methods, # as Ruby warns when redefining a method. remove_possible_singleton_method(:variants) remove_possible_singleton_method(:collection_parameter) remove_possible_singleton_method(:collection_counter_parameter) remove_possible_singleton_method(:counter_argument_present?) define_singleton_method(:variants) do templates.map { |template| template[:variant] } + variants_from_inline_calls(inline_calls) end define_singleton_method(:collection_parameter) do if provided_collection_parameter provided_collection_parameter else name.demodulize.underscore.chomp("_component").to_sym end end define_singleton_method(:collection_counter_parameter) do "#{collection_parameter}_counter".to_sym end define_singleton_method(:counter_argument_present?) do instance_method(:initialize).parameters.map(&:second).include?(collection_counter_parameter) end validate_collection_parameter! if raise_errors # If template name annotations are turned on, a line is dynamically # added with a comment. In this case, we want to return a different # starting line number so errors that are raised will point to the # correct line in the component template. line_number = if ActionView::Base.respond_to?(:annotate_template_file_names) && ActionView::Base.annotate_template_file_names -2 else -1 end templates.each do |template| # Remove existing compiled template methods, # as Ruby warns when redefining a method. method_name = call_method_name(template[:variant]) undef_method(method_name.to_sym) if instance_methods.include?(method_name.to_sym) class_eval <<-RUBY, template[:path], line_number def #{method_name} @output_buffer = ActionView::OutputBuffer.new #{compiled_template(template[:path])} end RUBY end @compiled = true end |
.compiled? ⇒ Boolean
190 191 192 193 194 |
# File 'lib/view_component/base.rb', line 190 def compiled? @compiled ||= false @compiled && ActionView::Base.cache_template_loading end |
.format ⇒ Object
271 272 273 |
# File 'lib/view_component/base.rb', line 271 def format :html end |
.identifier ⇒ Object
275 276 277 |
# File 'lib/view_component/base.rb', line 275 def identifier source_location end |
.inherited(child) ⇒ Object
168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/view_component/base.rb', line 168 def inherited(child) # If we're in Rails, add application url_helpers to the component context if defined?(Rails) 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 super end |
.short_identifier ⇒ Object
Provide identifier for ActionView template annotations
164 165 166 |
# File 'lib/view_component/base.rb', line 164 def short_identifier @short_identifier ||= defined?(Rails.root) ? source_location.sub("#{Rails.root}/", "") : source_location end |
.type ⇒ Object
we’ll eventually want to update this to support other types
267 268 269 |
# File 'lib/view_component/base.rb', line 267 def type "text/html" end |
.validate_collection_parameter!(validate_default: false) ⇒ Object
Ensure the component initializer accepts the collection parameter. By default, we do not validate that the default parameter name is accepted, as support for collection rendering is optional.
297 298 299 300 301 302 303 304 305 306 307 |
# File 'lib/view_component/base.rb', line 297 def validate_collection_parameter!(validate_default: false) parameter = validate_default ? collection_parameter : provided_collection_parameter return unless parameter return if instance_method(:initialize).parameters.map(&:last).include?(parameter) raise ArgumentError.new( "#{self} initializer must accept " \ "`#{parameter}` collection parameter." ) end |
.with_collection(*args) ⇒ Object
Render a component collection.
159 160 161 |
# File 'lib/view_component/base.rb', line 159 def with_collection(*args) Collection.new(self, *args) end |
.with_collection_parameter(param) ⇒ Object
Support overriding collection parameter name
288 289 290 |
# File 'lib/view_component/base.rb', line 288 def with_collection_parameter(param) @provided_collection_parameter = param end |
.with_content_areas(*areas) ⇒ Object
279 280 281 282 283 284 285 |
# File 'lib/view_component/base.rb', line 279 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_check ⇒ Object
80 81 82 |
# File 'lib/view_component/base.rb', line 80 def before_render_check # noop end |
#controller ⇒ Object
100 101 102 |
# File 'lib/view_component/base.rb', line 100 def controller @controller ||= view_context.controller end |
#format ⇒ Object
For caching, such as #cache_if
120 121 122 |
# File 'lib/view_component/base.rb', line 120 def format @variant end |
#helpers ⇒ Object
Provides a proxy to access helper methods
105 106 107 |
# File 'lib/view_component/base.rb', line 105 def helpers @helpers ||= view_context end |
#render(options = {}, args = {}, &block) ⇒ Object
If trying to render a partial or template inside a component, pass the render call to the parent view_context.
92 93 94 95 96 97 98 |
# File 'lib/view_component/base.rb', line 92 def render( = {}, args = {}, &block) if .is_a?(String) || (.is_a?(Hash) && .has_key?(:partial)) view_context.render(, args, &block) else super end end |
#render? ⇒ Boolean
84 85 86 |
# File 'lib/view_component/base.rb', line 84 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>
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/view_component/base.rb', line 43 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_check if render? send(self.class.call_method_name(@variant)) else "" end ensure @current_template = old_current_template end |
#view_cache_dependencies ⇒ Object
For caching, such as #cache_if
115 116 117 |
# File 'lib/view_component/base.rb', line 115 def view_cache_dependencies [] end |
#virtual_path ⇒ Object
Removes the first part of the path and the extension.
110 111 112 |
# File 'lib/view_component/base.rb', line 110 def virtual_path self.class.source_location.gsub(%r{(.*app/components)|(\.rb)}, "") end |
#with(area, content = nil, &block) ⇒ Object
Assign the provided content to the content area accessor
125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/view_component/base.rb', line 125 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 |