Replies: 1 comment
-
|
I would love some sort of "best practice" showcase or style guide for the more advanced use cases like this. The docs are great but they don't get particularly complex or as edge-casey as some of the test scenarios. That said, maybe that's intentional. One thing the Best Practice docs mention:
As soon as you start using composition (not necessarily at the expense of Inheritance here but there are probably other OOPy ways of drying this up), the complexity melts into flexibility. All you strictly need is this: class CardComponent < ViewComponent::Base
renders_one :badge
# ...
end<%# Usage: %>
<% card.with_badge { render AnyBadgeComponent.new(foo: :bar) } %>So what you need is definitely possible, just not quite as convenient. Introduce a trivial view helper though and suddenly: <% card.with_badge { status_badge_for(@order) } %>That's expressive enough for my tastes but I thought of a few more options depending on style (though I haven't tested any of them) See more...
# Not all that polymorphic really, just two types:
class CardComponent < ViewComponent::Base
renders_one :badge, types: {
default: { renders: BadgeComponent, as: :default_badge },
custom: { renders: ->(component) { component }, as: :custom_badge },
}
# Or if you're not bothered about the slot names:
renders_one :badge, types: {
default: BadgeComponent,
custom: ->(component) { component },
}
end(Though I'll concede it'd be nice to get some syntactic sugar or even a style guide for this. I'm constantly setting # Probably violates Principle of Least Surprise but if you're good with names (I'm not), this could work:
class CardComponent < ViewComponent::Base
renders_one :bodge
def with_badge(as: BadgeComponent, **, &)
with_badge(badge_component_class.new(**, &))
end
end<%# Usage: %>
<% card.with_badge(text: "foo", color: :green) %>
<%# -- and somewhere else -- %>
<% card.with_badge(status: @order.status, as: StatusBadgeComponent) %># Or this, though you lose the self-documenting option keys:
class CardComponent < ViewComponent::Base
renders_one :badge, ->(as: BadgeComponent, **options) { as.new(**options) }
endPersonally, I don't think we need this pattern when composition is available. However I am very keen to hear how others deal with the whole "model-component" interface thing. Page and Form objects sound great, but they're a hard sell on an established project. Surely there's a simple convention out there for handling common transformations from Model to Component without violating MVC (and without going back to partials). |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
The viewcomponent best practices mention Application-specific ViewComponents that can be used to translate a domain object into general-purpose components. This is very usefull to make your code more DRY but it is currently not possible to pass inherited ViewComponents into slots.
Context
renders_oneandrenders_manyslots in ViewComponent today only accept:They do not accept a pre-built component instance.
This creates friction when you want to extend a base component (e.g.
BadgeComponent) into a semantic wrapper (e.g.StatusBadgeComponent) and then slot that wrapper into a parent component (e.g.CardComponent).Right now, developers must either:
with_badge_status,with_badge_plainmethods.A more natural developer experience would allow a wrapper component to be passed directly into a slot.
Proposed solution
Allow
renders_oneandrenders_manyslots to accept either:ViewComponent::Baseinstance (that inherits from the slot class).Example
Usage with current slots (works today)
Desired usage with application specific component (proposed)
Under the hood, the
badgeslot would detect if the argument is a BadgeComponent instance:Beta Was this translation helpful? Give feedback.
All reactions