1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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
|
# frozen_string_literal: true
require "action_view/renderer/collection_renderer"
module ViewComponent
class Collection
include Enumerable
attr_reader :component
delegate :size, to: :@collection
def render_in(view_context, &block)
components.map do |component|
component.render_in(view_context, &block)
end.join(rendered_spacer(view_context)).html_safe
end
def each(&block)
components.each(&block)
end
if defined?(Rails::VERSION) && Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR == 1
# Rails expects us to define `format` on all renderables,
# but we do not know the `format` of a ViewComponent until runtime.
def format
nil
end
end
private
def components
return @components if defined? @components
iterator = ActionView::PartialIteration.new(@collection.size)
component.__vc_validate_collection_parameter!(validate_default: true)
@components = @collection.map do |item|
component.new(**component_options(item, iterator)).tap do |component|
iterator.iterate!
end
end
end
def initialize(component, object, spacer_component, **options)
@component = component
@collection = collection_variable(object || [])
@spacer_component = spacer_component
@options = options
end
def collection_variable(object)
if object.respond_to?(:to_ary)
object.to_ary
else
raise InvalidCollectionArgumentError
end
end
def component_options(item, iterator)
item_options = {component.__vc_collection_parameter => item}
item_options[component.__vc_collection_counter_parameter] = iterator.index if component.__vc_counter_argument_present?
item_options[component.__vc_collection_iteration_parameter] = iterator.dup if component.__vc_iteration_argument_present?
@options.merge(item_options)
end
def rendered_spacer(view_context)
if @spacer_component
@spacer_component.render_in(view_context)
else
""
end
end
end
end
|