Rails 7.1 gives templates more control over the locals they receive
Rails developers can break down their code into smaller,
reusable partials when building complex web pages.
These partials can then be passed locals
,
which are parameters
or
variables the partial needs to access.
By default, templates will accept any locals passed to them as keyword arguments. This meant templates had no control over the list of locals passed to them.
Before Rails 7.1
Before Rails 7.1,
there was no in-built method to define locals
explicitly
or
restrict the locals passed to the templates.
local_assigns
is a Rails view helper method that you can use to check
whether the partial has been provided with local variables
or
not.
It returns a hash of locals passed to the partials,
as shown below:
<%= render partial: "payment_gateways", locals: { paypal: @paypal, braintree: @braintree }
> local_assigns
=> { paypal: @paypal, braintree: @braintree }
> local_assigns[:paypal]
=> #<PaymentGateway id: 1, name: "Paypal"...
> local_assigns.has_key?(:stripe)
=> false
You can write a custom helper method to check if the locals passed to your templates are valid.
# app/views/shared/_payment_gateways.html.erb
<% verify_locals(local_assigns, [:paypal, :braintree]) %>
# code for the rest of the file
You can implement the verify_locals
method in ApplicationHelper
and
add your custom logic to restrict particular locals
or
verify if unnecessary locals get passed to the template.
# app/helpers/application_helper.rb
def verify_locals(local_assigns, expected_locals)
if (local_assigns.keys - expected_locals).any?
# raise error
end
end
Also,
to set a default value in the template file,
you must use the ||
operator.
<% name = (local_assigns[:name] || "Guest User") %>
In Rails 7.1
In Rails 7.1, developers can define or restrict local variables a template can accept. To implement this, you must add a local magic comment inside the template.
# app/views/shared/_payment_gateways.html.erb
<%# locals: (paypal:, braintree:) -%>
Rendering a partial with local variables still works the same way,
but if you pass any local variables that are not expected,
Rails will raise an ActionView::Template::Error
.
# app/views/orders/create.html.erb
<%= render partial: "payment_gateways", locals: { paypal: @paypal, braintree: @braintree }
# Raises error if additional or invalid locals are passed
# app/views/orders/create.html.erb
<%= render partial: "payment_gateways", locals: { paypal: @paypal, braintree: @braintree, stripe: @stripe }
ActionView::Template::Error (unknown local: :stripe):
app/views/orders/create.html.erb:10
Setting default locals
The feature will also raise an error if the expected locals are not passed.
<%= render partial: "payment_gateways", locals: { paypal: @paypal }
ActionView::Template::Error (missing local: :braintree):
app/views/orders/create.html.erb:10
You can avoid the missing local:
error
by setting the default values for the locals in the magic comment.
# app/views/shared/_payment_gateways.html.erb
<%# locals: (paypal: PaymentGateway.find_by_name("paypal"), braintree:...) -%>
Partials with no locals
You might need to render a partial without any locals in a few cases. To ensure the partial does not accept any locals, you need to set the local magic comment below.
# app/views/shared/_payment_gateways.html.erb
<%# locals: () -%>
If you try to pass any local to this partial,
it would throw no locals accepted
error.
<%= render partial: "payment_gateways", locals: { paypal: @paypal }
ActionView::Template::Error (no locals accepted):10
This feature will unlock the ability to pre-compile templates at application boot time instead of at runtime in the future.
To know more about this feature, please refer to this PR.