Well you’ve launched your fancy new app, and the social aspects are flourishing. In fact, comments are rolling in like it’s christmas. I guess the comments are presents and the site is a small child and your users are Santa?
Anyway, there are a lot of naughty boys and girls out there so now your client, the site operator, requests that comments must be approved prior to publication. The admins will review each comment and then submit the form by using one of three buttons - ‘Save’, ‘Accept’, ‘Reject’. The first is just a normal update, the second two are responsible for changing the publication state of the Comment.
Before the harvest
The Comment
model is pretty simple. You should assume that there are query
methods (#accepted?, #rejected?, #pending?) defined somewhere in there.
class Comment < ActiveRecord::Base
STATES = %w{Accepted Rejected Pending}
validates_inclusion_of :publication_state, :in => STATES
# pretty sweet code in here, seriously
def accept
self.update_attribute :publication_state, 'Accepted'
end
def reject
self.update_attribute :publication_state, 'Rejected'
end
end
The admin/comments#update
action is where the admins POST their comment saves,
approvals and rejections to…
class Admin::CommentsController < Admin::BaseController
def update
@comment = Comment.find_by_id params[:id]
if @comment.update_attributes params[:comment]
if save?
flash[:success] = 'The comment was edited successfully.'
redirect_to edit_admin_comment_url(@comment) and return
elsif accept?
@comment.accept
flash[:information] = 'Comment was resaved, accepted and published.'
elsif reject?
@comment.reject
flash[:information] = 'Comment was resaved, but marked as rejected.'
end
redirect_to admin_comments_url and return
else
flash.now[:failure] = 'The comment could not be saved.'
render :action => :edit
end
end
protected
def save?
params[:commit] == 'Save'
end
def accept?
params[:commit] == 'Accept'
end
def reject?
params[:commit] == 'Reject'
end
end
In the Comment
model, I don’t like those methods that just update the
publication_state
column. Why do I need to update that and not anything else?
I’m pretty sure that there’s only one spot in the entire codebase where those
are set.
In the controller, I don’t like how there are two saves in the #update action.
Sure, the extra UPDATE is probably minimal impact in regard to performance, but
it’s ugly. The only reason those extra saves are there is that the information
about the state change is not captured in params[:comment]
on the POST request
from the form - it’s only in params[:commit]
(the form submit button, as it
happens).
So what can I do? I want to get down to one save in the #update action, lose some code in the process if I can, and keep my workflow the same for the users.
After the rain
The model changes are simple. I just pull out those two methods and their
tests. The new Comment
looks like…
class Comment < ActiveRecord::Base
STATES = %w{Accepted Rejected Pending}
validates_inclusion_of :publication_state, :in => STATES
# ...
end
The functional tests don’t need to change - because I want to preserve the same workflow. When a user clicks a button and they know what it’s supposed to do, I want it to keep doing that same thing for them.
But the controller won’t have Comment#accept or Comment#reject available to it anymore, so it’s time to find a new way for the user’s button choice to control the state change.
class Admin::CommentsController < Admin::BaseController
before_filter :check_state_change, :only => :update
def update
@comment = Comment.find_by_id params[:id]
if @comment.update_attributes params[:comment]
if save?
flash[:success] = 'The comment was edited successfully.'
redirect_to edit_admin_comment_url(@comment) and return
elsif accept?
flash[:information] = 'Comment was resaved, accepted and published.'
elsif reject?
flash[:information] = 'Comment was resaved, but marked as rejected.'
end
redirect_to admin_comments_url and return
else
flash.now[:failure] = 'The comment could not be saved.'
render :action => :edit
end
end
protected
# ... #save?, #accept? and #reject? stay as they were
def check_state_change
if accept? or reject?
params[:comment][:publication_state] = accept? ? 'Accepted' : 'Rejected'
end
end
end
Overall, I like this approach better, even though that new method isn’t that pretty.
I couldn’t populate params[:comment][:publication_state]
in my form - because
there’s no telling what the right value is before the admin user chooses which
submit button to use. Now if they just want to ‘Save’, I’ll leave the state
alone - but if they want to ‘Accept’ or ‘Reject’, I’ll massage the params in a
before_filter
on the way in, and then I only have to save once in the #update
action.
It’s not that glamorous, but to me it makes more sense.