Christian Fazzini
Christian Fazzini

Reputation: 19713

Refactor this controller?

class ArticlesController < ApplicationController

  def index
    @articles = Article.by_popularity

    if params[:category] == 'popular'
      @articles = @articles.by_popularity
    end

    if params[:category] == 'recent'
      @articles = @articles.by_recent
    end

    if params[:category] == 'local'
      index_by_local and return
    end

    if params[:genre]
      index_by_genre and return
    end

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @articles }
    end
  end

  def index_by_local
    # 10 lines of code here

    render :template => 'articles/index_by_local'
  end

  def index_by_genre
    # ANOTHER 10 lines of code here

    render :template => 'articles/index_by_genre'
  end
end

As you can see from above. My controller is not exactly thin. What its doing is, depending on the params that were passed, it interacts with the model to filter out records.

And if params[:local] or params[:genre] was passed. Then it calls its own methods respectively (def index_by_local and def index_by_genre) to do further processing. These methods also load their own template, instead of index.html.erb.

Is this quite typical for a controller to look? Or should I be refactoring this somehow?

Upvotes: 0

Views: 135

Answers (2)

Midwire
Midwire

Reputation: 1100

I would define scopes for each of the collections you want to use.

class Article < ActiveRecord::Base
  ...
  scope :popular, where("articles.popular = ?", true) # or whatever you need
  scope :recent, where(...)
  scope :by_genre, where(...)
  scope :local, where(...)
  ...

  def self.filtered(filter)
    case filter
    when 'popular'
      Article.popular, 'articles/index'
    when 'recent'
      Article.recent, 'articles/index'
    when 'genre'
      Article.by_genre, 'articles/index_by_genre'
    when 'local'
      Article.local, 'articles/index_by_local'
    else
      raise "Unknown Filter"
    end
  end
end

Then in your controller action, something like this:

def index
  @articles, template = Article.filtered(params[:category] || params[:genre])
  respond_to do |format|
    format.html { render :template => template }
    format.xml  { render :xml => @articles }
  end
end

Upvotes: 0

Arun Kumar Arjunan
Arun Kumar Arjunan

Reputation: 6857

We can move the first few lines into the model(article.rb):

def get_by_category(category)
  # Return articles based on the category.
end

In this way we can completely test the article fetching logic using unit tests.

In general move all the code related to fetching records inside model. Controllers in general

  1. should authorize the user
  2. get records using the params and assign them to instance variables [These must typically be function calls to model]
  3. Render or redirect

Upvotes: 0

Related Questions