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.
-
.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.
98 |
# File 'lib/view_component/base.rb', line 98 def initialize(*); end |
Class Attribute Details
.source_location ⇒ Object
Returns the value of attribute source_location.
169 170 171 |
# File 'lib/view_component/base.rb', line 169 def source_location @source_location end |
.virtual_path ⇒ Object
Returns the value of attribute virtual_path.
169 170 171 |
# File 'lib/view_component/base.rb', line 169 def virtual_path @virtual_path end |
Class Method Details
.call_method_name(variant) ⇒ Object
202 203 204 205 206 207 208 |
# File 'lib/view_component/base.rb', line 202 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.
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 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/view_component/base.rb', line 218 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
210 211 212 |
# File 'lib/view_component/base.rb', line 210 def compiled? CompileCache.compiled?(self) end |
.format ⇒ Object
295 296 297 |
# File 'lib/view_component/base.rb', line 295 def format :html end |
.identifier ⇒ Object
299 300 301 |
# File 'lib/view_component/base.rb', line 299 def identifier source_location end |
.inherited(child) ⇒ Object
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/view_component/base.rb', line 181 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 # 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
177 178 179 |
# File 'lib/view_component/base.rb', line 177 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
291 292 293 |
# File 'lib/view_component/base.rb', line 291 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.
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 |
# File 'lib/view_component/base.rb', line 321 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.
172 173 174 |
# File 'lib/view_component/base.rb', line 172 def with_collection(collection, **args) Collection.new(self, collection, **args) end |
.with_collection_parameter(param) ⇒ Object
Support overriding collection parameter name
312 313 314 |
# File 'lib/view_component/base.rb', line 312 def with_collection_parameter(param) @provided_collection_parameter = param end |
.with_content_areas(*areas) ⇒ Object
303 304 305 306 307 308 309 |
# File 'lib/view_component/base.rb', line 303 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
86 87 88 |
# File 'lib/view_component/base.rb', line 86 def before_render before_render_check end |
#before_render_check ⇒ Object
90 91 92 |
# File 'lib/view_component/base.rb', line 90 def before_render_check # noop end |
#controller ⇒ Object
110 111 112 |
# File 'lib/view_component/base.rb', line 110 def controller @controller ||= view_context.controller end |
#format ⇒ Object
For caching, such as #cache_if
130 131 132 |
# File 'lib/view_component/base.rb', line 130 def format @variant end |
#helpers ⇒ Object
Provides a proxy to access helper methods from the context of the current controller
115 116 117 |
# File 'lib/view_component/base.rb', line 115 def helpers @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.
102 103 104 105 106 107 108 |
# File 'lib/view_component/base.rb', line 102 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
94 95 96 |
# File 'lib/view_component/base.rb', line 94 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>
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 79 80 81 82 83 84 |
# File 'lib/view_component/base.rb', line 49 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
125 126 127 |
# File 'lib/view_component/base.rb', line 125 def view_cache_dependencies [] end |
#virtual_path ⇒ Object
Exposes .virutal_path as an instance method
120 121 122 |
# File 'lib/view_component/base.rb', line 120 def virtual_path self.class.virtual_path end |
#with(area, content = nil, &block) ⇒ Object
Assign the provided content to the content area accessor
135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/view_component/base.rb', line 135 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 |