---
title: Let it REST
teaser:
tags: web,good code,rails
author: Harold Giménez
published_on: 2010-12-22
---

You are building a boring blog application. A blog has many comments. Your
client gives you this user story:

> So that I can report inappropriate comments, as a user, I can mark a comment
> as spam.

So you add a spam boolean attribute to the Comment model, and use the same `PUT
to comments` to handle the update:

    class CommentsController < ApplicationController
      def update
        @comment = Comment.find(params[:id])
        if @comment.update_attributes(params[:comments])
          flash[:notice] = "Comment updated successfully"
          # redirect_to ...
        else
          flash[:error] = "Something broke"
          # redirect_to ...
        end
      end
    end

![spaaaaam](http://images.thoughtbot.com/ui/spam.jpg)

By sending the right params down from the view, this can still work. But a day
passes by and people start abusing the darn spam button. This _is_ the internet
after all. So your client adds a couple of stories:

> So that I'm aware I screwed up, as a user, I receive an email notification
> when my comments get spammed.  So that I can moderate spammed comments, as an
> admin, I receive an email notification when comments are spammed.

There's now more going on when we spam a comment. A first shot at implementing
these stories could make use of [ActiveRecord's dirty
attributes](http://ar.rubyonrails.org/classes/ActiveRecord/Dirty.html) to figure
out if the spam boolean changed from false to true, in which case we deliver the
two notifications:

    class Comment < ActiveRecord::Base
      before_save :send_spam_notifications, :if => marking_as_spam?
      def send_spam_notifications
        Mailer.comment_owner_spam_notification(self).deliver
        Mailer.admin_spam_notification(self).deliver
      end

      def marking_as_spam?
        self.spam_changed? && self.spam?
      end
    end

Nice and clean, right? _Wrong_.I think there's a couple of problems with this.
First of all, we are cluttering our comment class with conditional callbacks.
Sometimes you have no choice, but I'd rather avoid them when possible. Secondly,
we're using the same HTTP endpoint and controller action for two very different
things. One is that of editing my comment, and the other is for marking a
comment as spam &#8212; which is arguably an [SRP
violation](http://www.objectmentor.com/resources/articles/srp.pdf) (PDF link).

We have a couple of options for cleaning up. We could create a
`#update_spam_status` action on the comments controller, but this deviates from
our otherwise RESTful API. Instead, let's break it out to it's own resource
called `Spams`, and instead of a `PUT to comment`, let's `POST to spam`:

    class SpamsController < ApplicationController
      def create
        @comment = Comment.find(params[:id])
        if @comment.mark_as_spam
          flash[:notice] = "Comment marked as spam"
          # redirect_to ...
        else
          # ...
        end
      end
    end

    class Comment < ActiveRecord::Base
      def mark_as_spam
        self.spam = true
        send_spam_notifications if valid?
        save
      end
    end

Our view can make use of a `button_to` that POSTs to the `spams` resource,
instead of relying on updating via the comments resource. Note how a RESTful
resource does not need to match 1-to-1 with a model, and this creates a much
cleaner interface overall &#8212; with a few advantages:

* You clean up your model by creating a `#mark_as_spam` method that is clear and
  to the point, untangling some of the callback soup that can start to build up
  on models.
* You are creating a level of abstraction that allows you to code to an
  interface, making it trivial to change `#mark_as_spam`'s implementation to
  anything else, like an Akismet call, or a more complex Spam class.
* Splitting it out allows us to properly scope the CommentsController#update's
  finder to the current user (`@comment =
  current_user.comments.find(params[:id])`), allowing any user to mark comments
  as spam, and only commenters to update their own comments.
* You can easily specify a more meaningful flash message for each case.

The question then arises, when should we split out to a new resource? As always,
it depends. For the most part it's whenever it will help clean up your models
and controllers. I find that if the update triggers any logic at all, it's worth
breaking out to its own resource and corresponding model method that
encapsulates that logic.
