Skip to content

Commit

Permalink
Mention Strict Locals in more documentation
Browse files Browse the repository at this point in the history
Motivation / Background
---

Strict Locals support was introduced in [#45727][] and announced as part
of the [7.1 Release][]. There are several mentions across the Guides,
but support is rarely mentioned in the API documentation.

Detail
----

Mention the template short identifier (the pathname, in most cases) as
part of the `ArgumentError` message.

This commit adds two test cases to ensure support for splatting
additional arguments, and for forbidding block and positional arguments.

It also makes mention of strict locals in more places, and links to the
guides.

[#45727]: #45727
[7.1 Release]: https://edgeguides.rubyonrails.org/7_1_release_notes.html#allow-templates-to-set-strict-locals
  • Loading branch information
seanpdoyle committed Jan 24, 2024
1 parent ee88f41 commit c8fb226
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 5 deletions.
20 changes: 19 additions & 1 deletion actionview/lib/action_view/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,23 @@ module ActionView # :nodoc:
# This is useful in cases where you aren't sure if the local variable has been assigned. Alternatively, you could also use
# <tt>defined? headline</tt> to first check if the variable has been assigned before using it.
#
# By default, templates will accept any <tt>locals</tt> as keyword arguments. To define what <tt>locals</tt> a template accepts, add a <tt>locals:</tt> magic comment:
#
# <%# locals: (headline:) %>
#
# Headline: <%= headline %>
#
# In cases where the local variables are optional, declare the keyword argument with a default value:
#
# <%# locals: (headline: nil) %>
#
# <% unless headline.nil? %>
# Headline: <%= headline %>
# <% end %>
#
# Read more about strict locals in {Action View Overview}[https://guides.rubyonrails.org/action_view_overview.html#strict-locals]
# in the guides.
#
# === Template caching
#
# By default, \Rails will compile each template to a method in order to render it. When you alter a template,
Expand Down Expand Up @@ -257,7 +274,8 @@ def _run(method, template, locals, buffer, add_to_stack: true, has_strict_locals
message.
gsub("unknown keyword:", "unknown local:").
gsub("missing keyword:", "missing local:").
gsub("no keywords accepted", "no locals accepted")
gsub("no keywords accepted", "no locals accepted").
concat(" for #{@current_template.short_identifier}")
)
end
else
Expand Down
14 changes: 14 additions & 0 deletions actionview/lib/action_view/template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,20 @@ class Template
# <p><%= alert %></p>
# <% end %>
#
# By default, templates will accept any <tt>locals</tt> as keyword arguments
# and make them available to <tt>local_assigns</tt>. To define what
# <tt>local_assigns</tt> a template will accept, add a <tt>locals:</tt> magic comment:
#
# <%# locals: (headline:, alerts: []) %>
#
# <h1><%= headline %></h1>
#
# <% alerts.each do |alert| %>
# <p><%= alert %></p>
# <% end %>
#
# Read more about strict locals in {Action View Overview}[https://guides.rubyonrails.org/action_view_overview.html#strict-locals]
# in the guides.

eager_autoload do
autoload :Error
Expand Down
25 changes: 22 additions & 3 deletions actionview/test/template/template_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def test_locals_can_be_disabled
render(foo: "bar")
end

assert_match(/no locals accepted/, error.message)
assert_match(/no locals accepted for hello template/, error.message)
end

def test_locals_can_not_be_specified_with_positional_arguments
Expand All @@ -172,6 +172,25 @@ def test_locals_can_be_specified_with_splat_arguments
assert_equal "bar", render(foo: "bar")
end

def test_locals_can_be_specified_with_keyword_and_splat_arguments
@template = new_template("<%# locals: (id:, **attributes) -%>\n<%= tag.hr(id: id, **attributes) %>")
assert_equal '<hr id="1" class="h-1">', render(id: 1, class: "h-1")
end

def test_locals_cannot_be_specified_with_positional_arguments
@template = new_template("<%# locals: (argument = 'content') -%>\n<%= argument %>")
assert_raises ActionView::Template::Error, match: "`argument` set as non-keyword argument for hello template. Locals can only be set as keyword arguments." do
render
end
end

def test_locals_cannot_be_specified_with_block_arguments
@template = new_template("<%# locals: (&block) -%>\n<%= tag.div(&block) %>")
assert_raises ActionView::Template::Error, match: "`block` set as non-keyword argument for hello template. Locals can only be set as keyword arguments." do
render { "content" }
end
end

def test_locals_can_be_specified
@template = new_template("<%# locals: (message:) -%>\n<%= message %>")
assert_equal "Hello", render(message: "Hello")
Expand All @@ -188,7 +207,7 @@ def test_required_locals_can_be_specified
render
end

assert_match(/missing local: :message/, error.message)
assert_match(/missing local: :message for hello template/, error.message)
end

def test_extra_locals_raises_error
Expand All @@ -197,7 +216,7 @@ def test_extra_locals_raises_error
render(message: "Hi", foo: "bar")
end

assert_match(/unknown local: :foo/, error.message)
assert_match(/unknown local: :foo for hello template/, error.message)
end

def test_rails_injected_locals_does_not_raise_error_if_not_passed
Expand Down
19 changes: 19 additions & 0 deletions guides/source/7_1_release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,12 +318,31 @@ You can also set default values for these locals:
<%= message %>
```

Optional keyword arguments can be splatted:

```erb
<%# locals: (message: "Hello, world!", **attributes) -%>
<%= tag.p(message, **attributes) %>
```

If you want to disable the use of locals entirely, you can do so like this:

```erb
<%# locals: () %>
```

All templating engines that support comments are supported:

```ruby
# locals: (json:, message:)
json.message message
```

Action View supports reading the magic comment from any line in the partial.

CAUTION: Only keyword arguments are supported. Defining positional or block arguments
will raise an Action View Error at render-time.

### Add `Rails.application.deprecators`

The new [`Rails.application.deprecators` method](https://github.com/rails/rails/pull/46049) returns a
Expand Down
23 changes: 22 additions & 1 deletion guides/source/action_view_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,8 @@ Combining Ruby 3.1's pattern matching assignment with calls to [Hash#with_defaul
<% end %>
```

INFO: By default, partials will accept any `locals` as keyword arguments. To define what `locals` a partials accepts, use a `locals:` magic comment. To learn more, read about [Strict Locals](#strict-locals).

[local_assigns]: https://api.rubyonrails.org/classes/ActionView/Template.html#method-i-local_assigns

### `render` without `partial` and `locals` Options
Expand Down Expand Up @@ -445,7 +447,7 @@ Rails will render the `_product_ruler` partial (with no data passed to it) betwe

### Strict Locals

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

```erb
<%# locals: (message:) -%>
Expand All @@ -459,12 +461,31 @@ Default values can also be provided:
<%= message %>
```

Optional keyword arguments can be splatted:

```erb
<%# locals: (message: "Hello, world!", **attributes) -%>
<%= tag.p(message, **attributes) %>
```

Or `locals` can be disabled entirely:

```erb
<%# locals: () %>
```

All templating engines that support comments are supported:

```ruby
# locals: (json:, message:)
json.message message
```

Action View supports reading the magic comment from any line in the partial.

CAUTION: Only keyword arguments are supported. Defining positional or block arguments
will raise an Action View Error at render-time.

Layouts
-------

Expand Down

0 comments on commit c8fb226

Please sign in to comment.