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
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!