Skip to content

Instantly share code, notes, and snippets.

@bloudermilk
Last active June 20, 2024 23:35
Show Gist options
  • Save bloudermilk/8345597 to your computer and use it in GitHub Desktop.
Save bloudermilk/8345597 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
@bloudermilk
Copy link
Author

That is a very good point! I'm guilty of having written methods like the long #posts method from your example in the early days of this technique. I'm actually a fan of the second example (smaller appropriately named methods) but I can see how this could get out of hand with a less CRUD-y controller. In that case I think your last example could be appropriate, but I'd probably still gravitate toward the second.

Thank you for your feedback!

@patbenatar
Copy link

Pedro's comment "it gets harder to follow what actually are we working on at the view" rings really true to me.. Although IMO the assigns magic isn't a much better approach to an explicit interface. Looking forward to Brendan's views gem! (Ask him about what he's been working on in his spare time...)

@kevinmanurip
Copy link

That was really good explaination and thanks for all the code. You've show many unpredictable things..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment