---
title: Massage your params
teaser:
tags: web,rails
author: Matt Jankowski
published_on: 2007-12-21
---

![''](http://images.thoughtbot.com/ui/2007-12-21-johnny5isalive.jpg "Johnny Five")

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?

[christmas]: https://www.isitchristmas.com

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.

```ruby
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...

```ruby
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...

```ruby
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.

```ruby
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.
