๐Ÿšซ

Why We Avoid Helpers

My Convention โ€” data logic in Controller, minimal Helpers

Rails Helpers are modules that define methods usable in views. Adding a method to ApplicationHelper makes it callable from all views.

While seemingly convenient, actively using Helpers in real projects accumulates these problems.

Why Avoid Helpers?

1. Global Scope Pollution

Rails Helpers are included in all views by default. A method in PostsHelper is callable from UsersController views too. Result: as Helper files grow, namespace collision risk increases and method origins become hard to trace.

2. Data Logic in the Wrong Place

When DB queries, array transforms, and conditional data processing enter Helpers, MVC boundaries break down.

# โŒ Bad โ€” data processing in Helper
def recent_posts_for_sidebar
  Post.published.order(created_at: :desc).limit(5)
end

# โœ… Good โ€” prepare in Controller
# controller
@recent_posts = Post.published.order(created_at: :desc).limit(5)

Passing via @instance_variables from Controller makes the flow visible at a glance: request โ†’ Controller(data prep) โ†’ View(display).

3. Testing Difficulty

Helper methods run in view context (self is ActionView). The moment they depend on url_for, content_tag, t(), you must reproduce this context in unit tests.

4. Bloating Helper Files

Helpers easily become "junk drawers" โ€” classification criteria are vague and unrelated methods pile up.

5. Accidental Deletion Risk

Helper method names are often generic: format_date, status_label, badge_color. Since Helpers are global, the definition file and actual usage can be in completely different places. During refactoring, someone might delete a method thinking it's unused, causing undefined method errors in completely unrelated views.


Alternatives

Recommended: Decorator / Presenter Pattern

The fundamental problem with Helpers is that display logic belonging to a model floats around as global functions. Decorators solve this by wrapping the model object.

# app/decorators/user_decorator.rb
class UserDecorator < SimpleDelegator
  def badge_color
    active? ? 'green' : 'gray'
  end

  def display_name
    name.presence || email.split('@').first
  end
end

# Controller
def show
  @user = UserDecorator.new(User.find(params[:id]))
end

# View โ€” use like a regular model
<span class="bg-<%= @user.badge_color %>-500"><%= @user.display_name %></span>

Other Alternatives

  • Controller @variables: for data processing/queries

  • View inline: ternary-level display logic used in 1-2 places

  • Partial: reusable UI fragments

  • Service Object: complex business logic

Architecture Diagram

โŒ Anti-Pattern: Data Logic in Helper
Controller
(empty)
Helper
DB query + processing
Global pollution!
View
helper_method()
View Helper (DB access) Controller skipped!
MVC flow broken โ€” View fetches data directly
โœ… Good: Pass via @variables from Controller
Controller
@data = processed result
Handles data preparation
Helper
SVG icons only
(minimal)
View
&lt;%= @data %&gt;
Controller @variable View (display only)
MVC flow maintained โ€” data in Controller, display in View
๐Ÿ’ฅ Real Incident: Common name + unknown origin = accidental deletion
posts_helper.rb
def badge_color(s)
Defined here
posts/index.erb
badge_color(post)
Usage A
admin/users/index.erb
badge_color(user)
Usage B (completely different place!)
๐Ÿ—‘๏ธ Cleaning up posts_helper.rb during refactoring
"badge_color? Thought it was Posts-only and unused, so deleted"
๐Ÿ’ฅ Error in admin/users/index.erb!
NoMethodError: undefined method 'badge_color'
Helper is global โ€” breaks in places unrelated to Posts
โœจ Recommended: Decorator / Presenter
Decorator
badge_color
display_name
Bound to model!
Controller
@user =
UserDecorator.new(user)
View
@user.badge_color
@user.display_name
Controller Decorator(wrap model) View (@user.method)
Display logic bound to model object โ€” clear tracking, no global pollution
โš–๏ธ Helper vs Decorator Comparison
Helper
Decorator
Call style
badge_color(user)
@user.badge_color
Defined in
Global (where?)
UserDecorator
Scope
Exposed to all views
Model-specific only
Delete safety
Unknown where it breaks
Clear impact scope
Testing
Needs view context
Plain Ruby test
๐Ÿค” Decision Criteria
When about to add a method to Helper โ†’
Model display logic?
badge_color,
display_name, etc.
→ Decorator
Data query/processing?
DB access, array transforms, conditional data
→ Controller @variable
Pure display utility?
SVG icons, date format, reused in 5+ places
→ Helper (last resort)
Key: Model display logic โ†’ Decorator, data preparation โ†’ Controller โ€” Helper is the last resort

Key Points

1

Before adding a Helper method โ†’ ask "Is this model display logic?"

2

If model display logic โ†’ create Decorator (SimpleDelegator) wrapping the model

3

If DB query/data processing โ†’ pass via @variables from Controller

4

If complex business logic โ†’ extract to Service Object

5

If reusable UI fragment โ†’ extract to Partial

6

If simple display used in 1-2 places โ†’ inline in view (ternary)

7

If Helper still needed โ†’ pure display functions only (SVG, date format), verify 5+ reuse points

Pros

  • Clear MVC flow: Controller(data) โ†’ View(display)
  • Naturally testable in Request Spec
  • Easy @variable tracking in IDE
  • Prevents global namespace pollution
  • Helper files stay lean
  • New team members quickly understand code flow

Cons

  • Controller may get slightly longer
  • Common display functions without Helpers may cause duplication
  • Somewhat different from official Rails guide (many resources actively recommend Helpers)
  • Pure display functions like render_pattern_icon are natural as Helpers

Use Cases

user.badge_color, user.display_name โ†’ Decorator post.reading_time, post.status_label โ†’ Decorator Pass DB query results to view โ†’ Controller @variables Array processing/mapping โ†’ handle in Controller Reused UI cards/badges โ†’ Partial Inline conditional CSS classes โ†’ ternary in view SVG icon rendering โ†’ Helper (only exception)