Class: ViewComponent::Base
- Inherits:
-
ActionView::Base
- Object
- ActionView::Base
- ViewComponent::Base
- Includes:
- ActiveSupport::Configurable, Previewable
- Defined in:
- lib/view_component/base.rb
Constant Summary collapse
- ViewContextCalledBeforeRenderError =
Class.new(StandardError)
Class Attribute Summary collapse
-
.source_location ⇒ Object
Returns the value of attribute source_location.
-
.virtual_path ⇒ Object
Returns the value of attribute virtual_path.
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(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 ⇒ Object
- #before_render_check ⇒ Object
- #controller ⇒ Object
-
#format ⇒ Object
For caching, such as #cache_if.
-
#helpers ⇒ Object
Provides a proxy to access helper methods from the context of the current controller.
-
#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
Exposes .virutal_path as an instance method.
-
#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.
100 |
# File 'lib/view_component/base.rb', line 100 def initialize(*); end |
Class Attribute Details
.source_location ⇒ Object
Returns the value of attribute source_location.
173 174 175 |
# File 'lib/view_component/base.rb', line 173 def source_location @source_location end |
.virtual_path ⇒ Object
Returns the value of attribute virtual_path.
173 174 175 |
# File 'lib/view_component/base.rb', line 173 def virtual_path @virtual_path end |
Class Method Details
.call_method_name(variant) ⇒ Object
211 212 213 214 215 216 217 |
# File 'lib/view_component/base.rb', line 211 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.
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 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/view_component/base.rb', line 227 def compile(raise_errors: false) return if compiled? if template_errors.present? raise ViewComponent::TemplateError.new(template_errors) if raise_errors return false end if instance_methods(false).include?(:before_render_check) ActiveSupport::Deprecation.warn( "`before_render_check` will be removed in v3.0.0. Use `before_render` instead." ) 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_rendered_view_with_filenames) && ActionView::Base.annotate_rendered_view_with_filenames -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 CompileCache.register self end |
.compiled? ⇒ Boolean
219 220 221 |
# File 'lib/view_component/base.rb', line 219 def compiled? CompileCache.compiled?(self) end |
.format ⇒ Object
304 305 306 |
# File 'lib/view_component/base.rb', line 304 def format :html end |
.identifier ⇒ Object
308 309 310 |
# File 'lib/view_component/base.rb', line 308 def identifier source_location end |
.inherited(child) ⇒ Object
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
# File 'lib/view_component/base.rb', line 185 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)}, "") # Clone slot configuration into child class # see #test_slots_pollution child.slots = self.slots.clone super end |
.short_identifier ⇒ Object
Provide identifier for ActionView template annotations
181 182 183 |
# File 'lib/view_component/base.rb', line 181 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
300 301 302 |
# File 'lib/view_component/base.rb', line 300 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.
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# File 'lib/view_component/base.rb', line 330 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 |
.with_collection(collection, **args) ⇒ Object
Render a component collection.
176 177 178 |
# File 'lib/view_component/base.rb', line 176 def with_collection(collection, **args) Collection.new(self, collection, **args) end |
.with_collection_parameter(param) ⇒ Object
Support overriding collection parameter name
321 322 323 |
# File 'lib/view_component/base.rb', line 321 def with_collection_parameter(param) @provided_collection_parameter = param end |
.with_content_areas(*areas) ⇒ Object
312 313 314 315 316 317 318 |
# File 'lib/view_component/base.rb', line 312 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 ⇒ Object
88 89 90 |
# File 'lib/view_component/base.rb', line 88 def before_render before_render_check end |
#before_render_check ⇒ Object
92 93 94 |
# File 'lib/view_component/base.rb', line 92 def before_render_check # noop end |
#controller ⇒ Object
112 113 114 115 |
# File 'lib/view_component/base.rb', line 112 def controller raise ViewContextCalledBeforeRenderError, "`controller` can only be called at render time." if view_context.nil? @controller ||= view_context.controller end |
#format ⇒ Object
For caching, such as #cache_if
134 135 136 |
# File 'lib/view_component/base.rb', line 134 def format @variant end |
#helpers ⇒ Object
Provides a proxy to access helper methods from the context of the current controller
118 119 120 121 |
# File 'lib/view_component/base.rb', line 118 def helpers raise ViewContextCalledBeforeRenderError, "`helpers` can only be called at render time." if view_context.nil? @helpers ||= controller.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.
104 105 106 107 108 109 110 |
# File 'lib/view_component/base.rb', line 104 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
96 97 98 |
# File 'lib/view_component/base.rb', line 96 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>
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 79 80 81 82 83 84 85 86 |
# File 'lib/view_component/base.rb', line 51 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? 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
129 130 131 |
# File 'lib/view_component/base.rb', line 129 def view_cache_dependencies [] end |
#virtual_path ⇒ Object
Exposes .virutal_path as an instance method
124 125 126 |
# File 'lib/view_component/base.rb', line 124 def virtual_path self.class.virtual_path end |
#with(area, content = nil, &block) ⇒ Object
Assign the provided content to the content area accessor
139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/view_component/base.rb', line 139 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 |