Skip to content

Commit 2c19936

Browse files
Add from to use_helpers to add macro like syntax (#2034)
* add: helpers macro * refactor helpers macro * refactor helpers macro * add: kwargs tests * add: documentation * add: changelog entry * add: block test and use_helper singular method * fix: ruby2_keywords warnings * add: singular documentation * fix: linting * fix: linting * fix: lint * refactor: code * fix rails main tests * fix: linting * fix linting * fix: linting * Apply suggestions from code review --------- Co-authored-by: Joel Hawksley <[email protected]> Co-authored-by: Joel Hawksley <[email protected]>
1 parent e0dba0b commit 2c19936

12 files changed

+172
-16
lines changed

docs/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ nav_order: 5
2222

2323
*Reegan Viljoen*
2424

25+
* Add `from:` option to `use_helpers` to allow for more flexible helper inclusion from modules.
26+
27+
*Reegan Viljoen*
28+
2529
* Fixed ruby head matcher issue.
2630

2731
*Reegan Viljoen*

docs/adrs/0003-polymorphic-slot-definitions.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ Here's how the `Item` sub-component of the list example above would be implement
5252

5353
```ruby
5454
class Item < ViewComponent::Base
55-
5655
renders_one :leading_visual, types: {
5756
icon: IconComponent, avatar: AvatarComponent
5857
}

docs/guide/helpers.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,40 @@ By default, ViewComponents don't have access to helper methods defined externall
5959

6060
```ruby
6161
class UseHelpersComponent < ViewComponent::Base
62-
use_helpers :icon
62+
use_helpers :icon, :icon?
6363

6464
erb_template <<-ERB
6565
<div class="icon">
66-
<%= icon :user %>
66+
<%= icon? ? icon(:user) : icon(:guest) %>
6767
</div>
6868
ERB
6969
end
7070
```
7171

72+
Use the `from:` keyword to include individual methods defined in helper modules not available in the component:
73+
74+
```ruby
75+
class UserComponent < ViewComponent::Base
76+
use_helpers :icon, :icon?, from: IconHelper
77+
78+
def profile_icon
79+
icon? ? icon(:user) : icon(:guest)
80+
end
81+
end
82+
```
83+
84+
The singular version `use_helper` is also available:
85+
86+
```ruby
87+
class UserComponent < ViewComponent::Base
88+
use_helper :icon, from: IconHelper
89+
90+
def profile_icon
91+
icon :user
92+
end
93+
end
94+
```
95+
7296
## Nested URL helpers
7397

7498
Rails nested URL helpers implicitly depend on the current `request` in certain cases. Since ViewComponent is built to enable reusing components in different contexts, nested URL helpers should be passed their options explicitly:

lib/view_component/instrumentation.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def render_in(view_context, &block)
1616
identifier: self.class.identifier
1717
}
1818
) do
19-
super(view_context, &block)
19+
super
2020
end
2121
end
2222

lib/view_component/use_helpers.rb

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,27 @@ module ViewComponent::UseHelpers
44
extend ActiveSupport::Concern
55

66
class_methods do
7-
def use_helpers(*args)
8-
args.each do |helper_method|
9-
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
10-
def #{helper_method}(*args, &block)
11-
raise HelpersCalledBeforeRenderError if view_context.nil?
12-
__vc_original_view_context.#{helper_method}(*args, &block)
13-
end
14-
RUBY
7+
def use_helpers(*args, from: nil)
8+
args.each { |helper_method| use_helper(helper_method, from: from) }
9+
end
10+
11+
def use_helper(helper_method, from: nil)
12+
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
13+
def #{helper_method}(*args, &block)
14+
raise HelpersCalledBeforeRenderError if view_context.nil?
15+
16+
#{define_helper(helper_method: helper_method, source: from)}
17+
end
18+
RUBY
19+
ruby2_keywords(helper_method) if respond_to?(:ruby2_keywords, true)
20+
end
21+
22+
private
23+
24+
def define_helper(helper_method:, source:)
25+
return "__vc_original_view_context.#{helper_method}(*args, &block)" unless source.present?
1526

16-
ruby2_keywords(helper_method) if respond_to?(:ruby2_keywords, true)
17-
end
27+
"#{source}.instance_method(:#{helper_method}).bind(self).call(*args, &block)"
1828
end
1929
end
2030
end
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<div class='helper__message'>
2+
<%= message %>
3+
</div>
4+
5+
<div class='helper__args-message'>
6+
<%= message_with_args('macro helper method') %>
7+
</div>
8+
9+
<div class='helper__kwargs-message'>
10+
<%= message_with_kwargs(name: 'macro kwargs helper method') %>
11+
</div>
12+
13+
<div class='helper__block-message'>
14+
<%= block_content %>
15+
</div>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# frozen_string_literal: true
2+
3+
class UseHelperMacroComponent < ViewComponent::Base
4+
use_helper :message, from: MacroHelper
5+
use_helper :message_with_args, from: MacroHelper
6+
use_helper :message_with_kwargs, from: MacroHelper
7+
use_helper :message_with_block, from: MacroHelper
8+
9+
def block_content
10+
message_with_block { "Hello block helper method" }
11+
end
12+
end
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
class UseHelpersComponent < ViewComponent::Base
4-
use_helpers :message
4+
use_helper :message
55
end
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<div class='helper__message'>
2+
<%= message %>
3+
</div>
4+
5+
<div class='helper__args-message'>
6+
<%= message_with_args('macro helper method') %>
7+
</div>
8+
9+
<div class='helper__kwargs-message'>
10+
<%= message_with_kwargs(name: 'macro kwargs helper method') %>
11+
</div>
12+
13+
<div class='helper__block-message'>
14+
<%= block_content %>
15+
</div>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# frozen_string_literal: true
2+
3+
class UseHelpersMacroComponent < ViewComponent::Base
4+
use_helpers :message, :message_with_args, :message_with_kwargs, :message_with_block, from: MacroHelper
5+
6+
def block_content
7+
message_with_block { "Hello block helper method" }
8+
end
9+
end

0 commit comments

Comments
 (0)