Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add slot methods to a module instead of the component class itself #2040

Merged
merged 5 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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://github.com/avatars/nicolas-brousse?s=64" alt="nicolas-brousse" width="32" />
<img src="https://github.com/avatars/nielsslot?s=64" alt="nshki" width="32" />
<img src="https://github.com/avatars/nshki?s=64" alt="nshki" width="32" />
<img src="https://github.com/avatars/ozydingo?s=64" alt="ozydingo" width="32" />
<img src="https://github.com/avatars/patrickarnett?s=64" alt="patrickarnett" width="32" />
<img src="https://github.com/avatars/rainerborene?s=64" alt="rainerborene" width="32" />
<img src="https://github.com/avatars/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
Loading