Skip to content

Commit c6bd547

Browse files
authored
Merge pull request rails#50864 from seanpdoyle/strict-loading-documentation
Mention Strict Locals in more documentation
2 parents ce636e9 + 6e1c2f7 commit c6bd547

File tree

5 files changed

+116
-5
lines changed

5 files changed

+116
-5
lines changed

actionview/lib/action_view/base.rb

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,23 @@ module ActionView # :nodoc:
8080
# This is useful in cases where you aren't sure if the local variable has been assigned. Alternatively, you could also use
8181
# <tt>defined? headline</tt> to first check if the variable has been assigned before using it.
8282
#
83+
# By default, templates will accept any <tt>locals</tt> as keyword arguments. To restrict what <tt>locals</tt> a template accepts, add a <tt>locals:</tt> magic comment:
84+
#
85+
# <%# locals: (headline:) %>
86+
#
87+
# Headline: <%= headline %>
88+
#
89+
# In cases where the local variables are optional, declare the keyword argument with a default value:
90+
#
91+
# <%# locals: (headline: nil) %>
92+
#
93+
# <% unless headline.nil? %>
94+
# Headline: <%= headline %>
95+
# <% end %>
96+
#
97+
# Read more about strict locals in {Action View Overview}[https://guides.rubyonrails.org/action_view_overview.html#strict-locals]
98+
# in the guides.
99+
#
83100
# === Template caching
84101
#
85102
# By default, \Rails will compile each template to a method in order to render it. When you alter a template,
@@ -257,7 +274,8 @@ def _run(method, template, locals, buffer, add_to_stack: true, has_strict_locals
257274
message.
258275
gsub("unknown keyword:", "unknown local:").
259276
gsub("missing keyword:", "missing local:").
260-
gsub("no keywords accepted", "no locals accepted")
277+
gsub("no keywords accepted", "no locals accepted").
278+
concat(" for #{@current_template.short_identifier}")
261279
)
262280
end
263281
else

actionview/lib/action_view/template.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,20 @@ class Template
148148
# <p><%= alert %></p>
149149
# <% end %>
150150
#
151+
# By default, templates will accept any <tt>locals</tt> as keyword arguments
152+
# and make them available to <tt>local_assigns</tt>. To restrict what
153+
# <tt>local_assigns</tt> a template will accept, add a <tt>locals:</tt> magic comment:
154+
#
155+
# <%# locals: (headline:, alerts: []) %>
156+
#
157+
# <h1><%= headline %></h1>
158+
#
159+
# <% alerts.each do |alert| %>
160+
# <p><%= alert %></p>
161+
# <% end %>
162+
#
163+
# Read more about strict locals in {Action View Overview}[https://guides.rubyonrails.org/action_view_overview.html#strict-locals]
164+
# in the guides.
151165

152166
eager_autoload do
153167
autoload :Error

actionview/test/template/template_test.rb

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def test_locals_can_be_disabled
155155
render(foo: "bar")
156156
end
157157

158-
assert_match(/no locals accepted/, error.message)
158+
assert_match(/no locals accepted for hello template/, error.message)
159159
end
160160

161161
def test_locals_can_not_be_specified_with_positional_arguments
@@ -172,6 +172,25 @@ def test_locals_can_be_specified_with_splat_arguments
172172
assert_equal "bar", render(foo: "bar")
173173
end
174174

175+
def test_locals_can_be_specified_with_keyword_and_splat_arguments
176+
@template = new_template("<%# locals: (id:, **attributes) -%>\n<%= tag.hr(id: id, **attributes) %>")
177+
assert_equal '<hr id="1" class="h-1">', render(id: 1, class: "h-1")
178+
end
179+
180+
def test_locals_cannot_be_specified_with_positional_arguments
181+
@template = new_template("<%# locals: (argument = 'content') -%>\n<%= argument %>")
182+
assert_raises ActionView::Template::Error, match: "`argument` set as non-keyword argument for hello template. Locals can only be set as keyword arguments." do
183+
render
184+
end
185+
end
186+
187+
def test_locals_cannot_be_specified_with_block_arguments
188+
@template = new_template("<%# locals: (&block) -%>\n<%= tag.div(&block) %>")
189+
assert_raises ActionView::Template::Error, match: "`block` set as non-keyword argument for hello template. Locals can only be set as keyword arguments." do
190+
render { "content" }
191+
end
192+
end
193+
175194
def test_locals_can_be_specified
176195
@template = new_template("<%# locals: (message:) -%>\n<%= message %>")
177196
assert_equal "Hello", render(message: "Hello")
@@ -188,7 +207,7 @@ def test_required_locals_can_be_specified
188207
render
189208
end
190209

191-
assert_match(/missing local: :message/, error.message)
210+
assert_match(/missing local: :message for hello template/, error.message)
192211
end
193212

194213
def test_extra_locals_raises_error
@@ -197,7 +216,7 @@ def test_extra_locals_raises_error
197216
render(message: "Hi", foo: "bar")
198217
end
199218

200-
assert_match(/unknown local: :foo/, error.message)
219+
assert_match(/unknown local: :foo for hello template/, error.message)
201220
end
202221

203222
def test_rails_injected_locals_does_not_raise_error_if_not_passed

guides/source/7_1_release_notes.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,12 +318,23 @@ You can also set default values for these locals:
318318
<%= message %>
319319
```
320320

321+
Optional keyword arguments can be splatted:
322+
323+
```erb
324+
<%# locals: (message: "Hello, world!", **attributes) -%>
325+
<%= tag.p(message, **attributes) %>
326+
```
327+
321328
If you want to disable the use of locals entirely, you can do so like this:
322329

323330
```erb
324331
<%# locals: () %>
325332
```
326333

334+
Action View will process the `locals:` magic comment in any templating engine that supports `#`-prefixed comments, and will read the magic comment from any line in the partial.
335+
336+
CAUTION: Only keyword arguments are supported. Defining positional or block arguments will raise an Action View Error at render-time.
337+
327338
### Add `Rails.application.deprecators`
328339

329340
The new [`Rails.application.deprecators` method](https://github.com/rails/rails/pull/46049) returns a

guides/source/action_view_overview.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,8 @@ Combining Ruby 3.1's pattern matching assignment with calls to [Hash#with_defaul
346346
<% end %>
347347
```
348348

349+
INFO: By default, partials will accept any `locals` as keyword arguments. To define what `locals` a partial accepts, use a `locals:` magic comment. To learn more, read about [Strict Locals](#strict-locals).
350+
349351
[local_assigns]: https://api.rubyonrails.org/classes/ActionView/Template.html#method-i-local_assigns
350352

351353
### `render` without `partial` and `locals` Options
@@ -445,26 +447,73 @@ Rails will render the `_product_ruler` partial (with no data passed to it) betwe
445447

446448
### Strict Locals
447449

448-
By default, templates will accept any `locals` as keyword arguments. To define what `locals` a template accepts, add a `locals` magic comment:
450+
By default, templates will accept any `locals` as keyword arguments. To define what `locals` a template accepts, add a `locals:` magic comment:
449451

450452
```erb
453+
<%# app/views/messages/_message.html.erb %>
454+
451455
<%# locals: (message:) -%>
452456
<%= message %>
453457
```
454458

459+
Rendering the partial without a `:message` local variable argument will raise an exception:
460+
461+
```ruby
462+
render "messages/message"
463+
# => ActionView::Template::Error: missing local: :message for app/views/messages/_message.html.erb
464+
```
465+
455466
Default values can also be provided:
456467

457468
```erb
469+
<%# app/views/messages/_message.html.erb %>
470+
458471
<%# locals: (message: "Hello, world!") -%>
459472
<%= message %>
460473
```
461474

475+
Rendering the partial without a `:message` local variable uses the provided default value:
476+
477+
```ruby
478+
render "messages/message"
479+
# => "Hello, world!"
480+
```
481+
482+
Rendering the partial with additional local variable arguments will raise an exception:
483+
484+
```ruby
485+
render "messages/message", unknown_local: "will raise"
486+
# => ActionView::Template::Error: missing local: :unknown_local for app/views/messages/_message.html.erb
487+
```
488+
489+
Optional local variable arguments can be splatted:
490+
491+
```erb
492+
<%# app/views/messages/_message.html.erb %>
493+
494+
<%# locals: (message: "Hello, world!", **attributes) -%>
495+
<%= tag.p(message, **attributes) %>
496+
```
497+
462498
Or `locals` can be disabled entirely:
463499

464500
```erb
501+
<%# app/views/messages/_message.html.erb %>
502+
465503
<%# locals: () %>
466504
```
467505

506+
Rendering the partial with any local variable arguments will raise an exception:
507+
508+
```ruby
509+
render "messages/message", unknown_local: "will raise"
510+
# => ActionView::Template::Error: no locals accepted for app/views/messages/_message.html.erb
511+
```
512+
513+
Action View will process the `locals:` magic comment in any templating engine that supports `#`-prefixed comments, and will read the magic comment from any line in the partial.
514+
515+
CAUTION: Only keyword arguments are supported. Defining positional or block arguments will raise an Action View Error at render-time.
516+
468517
Layouts
469518
-------
470519

0 commit comments

Comments
 (0)