4. Separate Slot Getters and Setters
Date: 2022/03/22
Author
Blake Williams
Status
Accepted
Context
Currently, slots implement a single method for both getting and setting a slot. For example, given a slot named header
:
class MyComponent < ViewComponent::Base
renders_one :header
end
c = MyComponent.new
c.header { "Hello world!" } # sets the slot
c.header # gets the slot
This API was built with the assumption that a slot will always be set by passing an argument and/or passing a block.
This assumption hasn’t remained valid. Specifically, with_content
breaks the assumption when passing static content to a slot:
class MyComponent < ViewComponent::Base
renders_one :header
end
c = MyComponent.new
# c.header returns nil because the getter path is being executed due to having
# no arguments and no block passed: https://github.com/ViewComponent/view_component/blob/18c27adc7ec715ca05d7ad0299efcbff9f03544b/lib/view_component/slotable_v2.rb#L70-L74
#
c.header.with_content("Hello world!") # undefined method `with_content' for nil:NilClass (NoMethodError)
The above example shows off the gap in the slots API via with_content
, but it’s likely that as the library continues to grow this gap will appear in other valid use-cases.
Decision
Split the slots API into a getter and setter. Keeping the slot name as the getter makes the most sense, but the setter can be renamed to with_#{slot_name}
.
For example, the above would become:
class MyComponent < ViewComponent::Base
renders_one :header
end
c = MyComponent.new
# New API for setting slots
c.with_header { "hello world" }
# Now `with_content` is valid when defining slots
c.with_header.with_content("Hello world!")
Alternatives Considered
We’ve spoken about a few alternatives:
- Making a special
NilSlot
class that responds towith_content
.- Results in extra allocations
- Can’t treat the
NilSlot
as falsy. Soif header
would no longer work even though you would expect it to.
- Introducing an API for the
header.with_content("Hello world!")
pattern as explained above (like:c.with_header_content("Hello world!")
):- The API gap still exists and requires a specific work around for
with_content
, leaving the gap for future API’s. - This API doesn’t allow arguments to be passed 1-to-1 like the current setter API.
- The API gap still exists and requires a specific work around for
Consequences
The largest consequence of this change is that we’ll need to deprecate the old setter usage (header { "Hello world!"}
) in favor of the new setter API (with_header { "Hello world!" }
).
I propose that we make at least one release with the new API and no deprecation warning followed by another release that includes the deprecation warning. This will give teams some time to migrate before running into deprecation warnings.