Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ArthurZheng/7637f211d484ae2c1395d0c33a26021a to your computer and use it in GitHub Desktop.
Save ArthurZheng/7637f211d484ae2c1395d0c33a26021a to your computer and use it in GitHub Desktop.
Explaining the rationale behind using memoized helper methods for controller resources

Last year I started playing around with using memoized private helper methods in my controllers instead of the traditional instance variable assigns we see in RoR controllers. Here's an example:

class PostsController < ApplicationController
  helper_method :new_post, :post, :posts
  
  def new; end
  def show; end
  def edit; end
  def index; end
  
  def create
    new_post.attributes = post_params
    
    if new_post.save
      redirect_to post
    else
      render :new
    end
  end
  
  def update
    if post.update_attributes(post_params)
      redirect_to post
    else
      render :edit
    end
  end
  
  def destroy
    post.destroy!
    redirect_to :index
  end
  
  private
  
  def new_post
    @new_post ||= Post.new
  end
  
  def post
    @post ||= posts.find(params[:id])
  end
  
  def posts
    @posts ||= current_user.posts
  end
  
  def post_params
    @post_params ||= params.require(:post).permit(:title, :body)
  end
end

Here's what I like about it:

  • Actions #new, #show, #edit, #index have no unique logic and as such, nothing in their methods (in fact, we don't even have to define methods here but I usually define stub methods to make it clear to readers that those actions do exist)
  • Actions #update, #create, and #destroy contain only the logic that is unique and important to them
  • Avoids hard-to-maintain before_filter :only and :except lists or repetative resource instantiation code
  • No more "called method on nil" errors. If you misspell something, you'll get an "undefined method" exception which includes your misspelling.
  • No more weird ass Rails magic copying ivars from one context to another

I also got into the habit of abstracting the logic in #update and #create into another private method (not helper) but I've since stopped doing that because I think it adds more indirection than value. Here's an example of that:

class PostsController < ApplicationController
  helper_method :new_post, :post, :posts
  
  def new; end
  def show; end
  def edit; end
  def index; end
  
  def create
    save_or_render(:new)
  end
  
  def update
    save_or_render(:edit)
  end
  
  def destroy
    post.destroy!
    redirect_to :index
  end
  
  private
  
  def save_or_render(action)
    if post.update_attributes(post_params)
      redirect_to post
    else
      render action
    end
  end
  
  def new_post
    @new_post ||= Post.new
  end
  
  def post
    @post ||= posts.find(params[:id])
  end
  
  def posts
    @posts ||= current_user.posts
  end
  
  def post_params
    @post_params ||= params.require(:post).permit(:title, :body)
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment