Skip to content

Commit

Permalink
Add slot methods to a module instead of the component class itself (#…
Browse files Browse the repository at this point in the history
…2040)

* Add distinct GeneratedSlotMethods module to ViewComponents and define `#{slot}` and `#{slot?}` methods on this module, allowing these methods to be overridden with access to `super` implementation.

* Update docs/CONTRIBUTING.md

* Update docs/CHANGELOG.md

---------

Co-authored-by: Joel Hawksley <joelhawksley@github.com>
Co-authored-by: Joel Hawksley <joel@hawksley.org>
  • Loading branch information
3 people authored Aug 27, 2024
1 parent 80c7c8e commit d514f5d
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 8 deletions.
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ nav_order: 5

## main

* Allow overridden slot methods to use `super`.

*Andrew Schwartz*

* Add Rails engine support to generators.

*Tomasz Kowalewski*
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ ViewComponent is built by over a hundred members of the community, including:
<img src="https://avatars.githubusercontent.com/nicolas-brousse?s=64" alt="nicolas-brousse" width="32" />
<img src="https://avatars.githubusercontent.com/nielsslot?s=64" alt="nshki" width="32" />
<img src="https://avatars.githubusercontent.com/nshki?s=64" alt="nshki" width="32" />
<img src="https://avatars.githubusercontent.com/ozydingo?s=64" alt="ozydingo" width="32" />
<img src="https://avatars.githubusercontent.com/patrickarnett?s=64" alt="patrickarnett" width="32" />
<img src="https://avatars.githubusercontent.com/rainerborene?s=64" alt="rainerborene" width="32" />
<img src="https://avatars.githubusercontent.com/rdavid1099?s=64" alt="rdavid1099" width="32" />
Expand Down
25 changes: 17 additions & 8 deletions lib/view_component/slotable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ def renders_one(slot_name, callable = nil)
end
ruby2_keywords(setter_method_name) if respond_to?(:ruby2_keywords, true)

define_method slot_name do
self::GeneratedSlotMethods.define_method slot_name do
get_slot(slot_name)
end

define_method :"#{slot_name}?" do
self::GeneratedSlotMethods.define_method :"#{slot_name}?" do
get_slot(slot_name).present?
end

Expand Down Expand Up @@ -176,11 +176,11 @@ def renders_many(slot_name, callable = nil)
end
end

define_method slot_name do
self::GeneratedSlotMethods.define_method slot_name do
get_slot(slot_name)
end

define_method :"#{slot_name}?" do
self::GeneratedSlotMethods.define_method :"#{slot_name}?" do
get_slot(slot_name).present?
end

Expand All @@ -199,19 +199,28 @@ def slot_type(slot_name)
end
end

# Clone slot configuration into child class
# see #test_slots_pollution
def inherited(child)
# Clone slot configuration into child class
# see #test_slots_pollution
child.registered_slots = registered_slots.clone

# Add a module for slot methods, allowing them to be overriden by the component class
# see #test_slot_name_can_be_overriden
unless child.const_defined?(:GeneratedSlotMethods, false)
generated_slot_methods = Module.new
child.const_set(:GeneratedSlotMethods, generated_slot_methods)
child.include generated_slot_methods
end

super
end

def register_polymorphic_slot(slot_name, types, collection:)
define_method(slot_name) do
self::GeneratedSlotMethods.define_method(slot_name) do
get_slot(slot_name)
end

define_method(:"#{slot_name}?") do
self::GeneratedSlotMethods.define_method(:"#{slot_name}?") do
get_slot(slot_name).present?
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div class="card <%= @classes %>">
<% if title? %>
<div class="title">
<%= title %>
</div>
<% end %>
</div>
27 changes: 27 additions & 0 deletions test/sandbox/app/components/slot_name_override_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

class SlotNameOverrideComponent < ViewComponent::Base
renders_one :title

def initialize(title: nil)
@title = title
end

def title
@title || super
end

def title?
@title.present? || super
end
end

class SlotNameOverrideComponent::OtherComponent < ViewComponent::Base
renders_one :title
end

class SlotNameOverrideComponent::SubComponent < SlotNameOverrideComponent
def title
super.upcase
end
end
34 changes: 34 additions & 0 deletions test/sandbox/test/slotable_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -785,4 +785,38 @@ def test_slotable_default_instance

assert_text "hello,world!", count: 1
end

def test_slot_name_can_be_overriden
# Uses overridden `title` slot method
render_inline(SlotNameOverrideComponent.new(title: "Simple Title"))

assert_selector(".title", text: "Simple Title")
end

def test_slot_name_override_can_use_super
# Uses standard `title` slot method via `super`
render_inline(SlotNameOverrideComponent.new) do |component|
component.with_title do
"Block Title with More Complexity"
end
end

assert_selector(".title", text: "Block Title with More Complexity")
end

def overriden_slot_name_predicate_returns_false_when_not_set
render_inline(SlotNameOverrideComponent.new)

refute_selector(".title")
end

def test_overridden_slot_name_can_be_inherited
render_inline(SlotNameOverrideComponent::SubComponent.new(title: "lowercase"))

assert_selector(".title", text: "LOWERCASE")
end

def test_slot_name_methods_are_not_shared_accross_components
assert_not_equal SlotsComponent.instance_method(:title).owner, SlotNameOverrideComponent::OtherComponent.instance_method(:title).owner
end
end

0 comments on commit d514f5d

Please sign in to comment.