Prefer CRUD actions in controllers

In a Rails application, controllers typically represent a resource type. In a RESTful design, the only actions an API consumer should be able to perform on a resource are: reading, creation, modification, and deletion. Rails usually will map these actions to HTTP verb -> method name pairs:

If you find yourself needing to do anything else to a resource, consider two things:

  1. Does this action actually fit into one of the other actions?
  2. Am I in fact working with a different type of resource altogether?

Example 1 πŸ”—

Bad πŸ”—

class PostsController < ApplicationController
  # PUT /posts/1/archive
  def archive
    @post = Post.find(params[:id])
    @post.update(archive: true)
  end
end

Good πŸ”—

class PostsController < ApplicationController
  # PUT /posts/1
  # { post: { archived: true } }
  def update
    @post = Post.find(params[:id])
    @post.update(post_params)
  end
end

Example 2 πŸ”—

Bad πŸ”—

class UsersController < ApplicationController
  # POST /users/reset_password
  def reset_password
    current_user.reset_password
  end
end

Good πŸ”—

class PasswordResetsController < ApplicationController
  # POST /password_resets
  def create
    current_user.reset_password
  end
end

When applying this rule, consider the mental overhead of extracting a new resource out of a controller. For example, if you want to give the user the ability to delete everything in a collection, it may make more sense to keep similar logic near the other actions. In this case, it’s OK to declare colletion resource routes such as create_all, update_all, and delete_all.

Bad πŸ”—

class NotificationsController < ApplicationController
  def index
    # ...
  end
end

class AllNotificationsController < ApplicationController
  def destroy
    # operation to destroy all notifications
  end
end

Good πŸ”—

class NotificationsController < ApplicationController
  def index
    # ...
  end

  def destroy_all
    # operation to destroy all notifications
  end
end

This does not mean that you should willy-nilly add related routes that are not resourceful, even if the route would affect the same resource:

Bad πŸ”—

class NotificationsController < ApplicationController
  # POST /notifications/:id/read
  def read
    # mark a notification as read
  end
end

Good πŸ”—

It’s probably best to simply handle this via the #update action, but if it absolutely has to be a separate route, here’s what should be done:

class Notifications::ReadsController < ApplicationController
  # POST /notifications/:id/read
  def create
     # mark a notification as read
  end
end