Skip to content

Linter Rule: Disallow multiple Ruby statements in a single ERB tag

Rule: erb-no-multiple-statements

Description

Disallow multiple Ruby statements separated by semicolons within a single-line ERB tag. Each ERB tag on a single line should contain at most one Ruby statement.

A control-flow tag is reported for every statement it carries, however the tag is written. The keyword already fills the tag, so <% else; raise %> holds one statement too many the same way <% a = 1; b = 2 %> does.

Rationale

Multiple Ruby statements on a single line in an ERB tag reduce readability and make templates harder to maintain. Splitting statements into separate ERB tags makes each statement easier to understand at a glance.

This rule only applies to single-line ERB tags. Multi-line ERB tags are not checked, as they naturally provide visual separation between statements.

Control-flow tags are checked however many lines they span. A branch keyword and the code the branch runs read as one run-on tag, and pulling the statement out lines the branch up with every other branch in the template.

Only the statements the tag itself introduces are counted. A tag holding a single conditional or block is one statement, however many statements its branches or body contain, so <% if admin?; role = "admin"; else; role = "user"; end %> is not reported.

Examples

✅ Good

erb
<% user = User.find(1) %>
<% post = user.posts.first %>
Local variable `post` is assigned but never used. Remove the assignment, or prefix it with an underscore as `_post` to show it is intentionally unused. (erb-no-unused-local-variable)
erb
<%= user.name %>
erb
<%
  user = User.find(1)
  post = user.posts.first
Local variable `post` is assigned but never used. Remove the assignment, or prefix it with an underscore as `_post` to show it is intentionally unused. (erb-no-unused-local-variable)
%>

🚫 Bad

erb
<% user = User.find(1); post = user.posts.first %>
Local variable `post` is assigned but never used. Remove the assignment, or prefix it with an underscore as `_post` to show it is intentionally unused. (erb-no-unused-local-variable)
Avoid multiple Ruby statements in a single-line ERB tag. Move this statement into its own ERB tag for better readability. (erb-no-multiple-statements)
erb
<%= user = User.find(1); user.name %>
Avoid multiple Ruby statements in a single-line ERB tag. Move this statement into its own ERB tag for better readability. (erb-no-multiple-statements)
erb
<% a = 1; b = 2; c = 3 %>
Local variable `c` is assigned but never used. Remove the assignment, or prefix it with an underscore as `_c` to show it is intentionally unused. (erb-no-unused-local-variable)
Local variable `b` is assigned but never used. Remove the assignment, or prefix it with an underscore as `_b` to show it is intentionally unused. (erb-no-unused-local-variable)
Local variable `a` is assigned but never used. Remove the assignment, or prefix it with an underscore as `_a` to show it is intentionally unused. (erb-no-unused-local-variable)
Avoid multiple Ruby statements in a single-line ERB tag. Move this statement into its own ERB tag for better readability. (erb-no-multiple-statements)
Avoid multiple Ruby statements in a single-line ERB tag. Move this statement into its own ERB tag for better readability. (erb-no-multiple-statements)
erb
<% if admin? %>
  <span>Admin</span>
<% else
Empty else block: this control flow statement has no content (erb-no-empty-control-flow)
raise ArgumentError %>
Avoid Ruby statements in a control-flow ERB tag. Move this statement into its own ERB tag for better readability. (erb-no-multiple-statements)
<% end %>
erb
<% case status %>
<% when "ok"
Empty when block: this control flow statement has no content (erb-no-empty-control-flow)
logged = true %>
Local variable `logged` is assigned but never used. Remove the assignment, or prefix it with an underscore as `_logged` to show it is intentionally unused. (erb-no-unused-local-variable)
Avoid Ruby statements in a control-flow ERB tag. Move this statement into its own ERB tag for better readability. (erb-no-multiple-statements)
<% end %>

Autofix

This rule provides an autofix that gives each statement its own ERB tag. A tag that stands alone on its line is split across lines, keeping its indentation:

erb
<% user = User.find(1); post = user.posts.first %>
Local variable `post` is assigned but never used. Remove the assignment, or prefix it with an underscore as `_post` to show it is intentionally unused. (erb-no-unused-local-variable)
Avoid multiple Ruby statements in a single-line ERB tag. Move this statement into its own ERB tag for better readability. (erb-no-multiple-statements)
erb
<% user = User.find(1) %>
<% post = user.posts.first %>
Local variable `post` is assigned but never used. Remove the assignment, or prefix it with an underscore as `_post` to show it is intentionally unused. (erb-no-unused-local-variable)

A tag that shares its line with markup is split in place, since a newline there would land in the rendered output:

erb
<div><% a = 1; b = 2 %><%= a + b %></div>
Avoid multiple Ruby statements in a single-line ERB tag. Move this statement into its own ERB tag for better readability. (erb-no-multiple-statements)
erb
<div><% a = 1 %><% b = 2 %><%= a + b %></div>

A control-flow tag keeps its keyword and hands each statement a tag of its own:

erb
<% if admin? %>
  <span>Admin</span>
<% else
Empty else block: this control flow statement has no content (erb-no-empty-control-flow)
raise ArgumentError %>
Avoid Ruby statements in a control-flow ERB tag. Move this statement into its own ERB tag for better readability. (erb-no-multiple-statements)
<% end %>
erb
<% if admin? %>
  <span>Admin</span>
<% else %>
<% raise ArgumentError %>
Avoid unused expressions in silent ERB tags. `<% raise ArgumentError %>` is evaluated but its return value is discarded. Use `<%= raise ArgumentError %>` to output the value or remove the expression. (erb-no-unused-expressions)
<% end %>

Run the formatter afterwards to indent the statement into its branch. A tag the parser split off one holding more than one control-flow role is left alone until the formatter has restored its delimiters.

An output tag produces the value of its last statement, so the fix keeps <%= on that statement and makes the ones before it silent. <%- a = 1; b = 2 -%> trims on both ends, so the leading trim stays on the first statement and the trailing trim on the last.

Splitting a standalone line changes the whitespace the template emits under an engine that does not drop a line holding a single silent tag, which is why the fix is offered as unsafe inside pre, textarea, script, style and xmp, where that whitespace is visible. The same holds for a control-flow tag, since it is split the same way. A tag that defines a method, a class or a module is not fixed at all, since it belongs outside the template rather than in a tidier tag, and neither is a tag whose statements are followed by a comment, which the fix has no place to put.

References

Released under the MIT License.