Class: RuboCop::Cop::DevDoc::Auth::CurrentUserBranching

Inherits:
Base
  • Object
show all
Defined in:
lib/rubocop/cop/dev_doc/auth/current_user_branching.rb

Overview

Forbid branching on authentication state in page-specific code.

## Rationale In a Rails app using Pundit + Devise, ‘current_user` is guaranteed non-nil inside any controller action or view that requires auth —the policy has already denied anonymous visitors. Branching on `if current_user` or `if user_signed_in?` inside that code is therefore either dead code (the branch for nil can never fire) or a signal that the developer was unsure whether the page requires auth.

If the page genuinely serves both anonymous and signed-in visitors, the branching should be explicit and kept in shared/layout code, not sprinkled through action bodies and page views.

❌ Authenticated page branching on auth state (branch is dead code)
# app/views/posts/show.json.jbuilder
if current_user
  json.actions [:edit, :delete]
end

✔️ Shared layout — branching here is the right place
# app/views/layouts/_nav_bar.html.erb
<% if user_signed_in? %>
  <%= render 'profile_menu' %>
<% else %>
  <%= link_to 'Log in', new_user_session_path %>
<% end %>

✔️ Genuinely dual-state page — suppress the cop with a reason comment
def new
  # rubocop:disable DevDoc/Auth/CurrentUserBranching
  # Reason: contact form is intentionally dual-state; pre-fills for signed-in users.
  if current_user
    @form.name = current_user.full_name
  end
  # rubocop:enable DevDoc/Auth/CurrentUserBranching
end

## Patterns flagged

  • ‘if current_user` / `unless current_user` (block or modifier) where the body is non-empty and is not a bare `return` (bare returns are the nil-guard pattern covered by LoadResourceCurrentUserGuard).

  • ‘if user_signed_in?` / `unless user_signed_in?` (any form).

  • ‘if current_user&.foo` and other safe-nav uses of `current_user` as a condition. The `&.` is itself a confession that the dev expects `current_user` to be nil sometimes — i.e., it’s the same anti-pattern as ‘if current_user`, just in disguise: when `current_user` is nil the csend returns nil → branch is dead for anonymous visitors, exactly what the cop prevents.

  • Ternaries: ‘current_user ? a : b`, `user_signed_in? ? a : b`.

  • Hash/argument values: ‘authenticated: user_signed_in?`.

## Allowed paths (Exclude:) By default the cop is silent in:

app/policies/**/*.rb
app/helpers/**/*.rb
app/controllers/concerns/**/*.rb
app/views/layouts/**/*
app/controllers/application_controller.rb

Override via ‘Exclude:` in your `.rubocop.yml`.

NOTE: The cop does not autocorrect — there is no mechanical fix. The right response depends on developer intent: drop the branch (if auth is required), restructure into a shared layout (if dual-state), or add an inline disable with a reason.

Examples:

# bad — inside an authenticated action
def show
  if current_user
    @data = current_user.private_data
  end
end

# bad — safe-nav predicate, same anti-pattern in disguise
def show
  if current_user&.admin?
    admin_thing
  end
end

# bad — inside a page view
# user_signed_in? ? render_private : render_public

# good — bare nil-guard return (LoadResourceCurrentUserGuard rule)
return unless current_user

# good — inside a shared layout file (excluded by default)
# app/views/layouts/_nav.html.erb
if user_signed_in?
  ...
end

Constant Summary collapse

MSG =
'Avoid branching on auth state in page code. ' \
'If this page serves both anonymous and signed-in users, ' \
'add an inline disable with a reason.'.freeze
AUTH_METHODS =
%i[current_user user_signed_in?].freeze

Instance Method Summary collapse

Instance Method Details

#on_if(node) ⇒ Object



105
106
107
108
109
110
# File 'lib/rubocop/cop/dev_doc/auth/current_user_branching.rb', line 105

def on_if(node)
  return unless auth_branch?(node)
  return if bare_return_guard?(node)

  add_offense(if_offense_location(node))
end

#on_send(node) ⇒ Object

‘value: user_signed_in?` — condition passed as a value.



113
114
115
116
117
118
# File 'lib/rubocop/cop/dev_doc/auth/current_user_branching.rb', line 113

def on_send(node)
  return unless auth_method_call?(node)
  return unless used_as_value?(node)

  add_offense(node.loc.selector)
end