Rails DRY Controllers

Cover image

In any programming language, it is important to create DRY (Don't Repeat Yourself) code. In this article we will explore three methods of writing Rails CRUD controllers, and use a gem to abstract controllers even more.

TL;TR: The completed repository if you would like to jump straight to the code.

Setup

Create a new rails project: rails new rails_dry --database=postgresql and run the migrations: rails db:migrate.

We will create three resources to look at standard CRUD operations :

  • Article
  • Blog
  • Post

All three resources work the same and are centered around articles for a Blog type site. There are no user accounts, so this is not an example of a robust application. Instead it is to show three ways to write CRUD controllers. Therefore, we are looking at three resources which are basically identical to each other, just coded a little differently.

Each resource controller is set up with a before_filter for the resource, and strong_params. For instance with the Articles controller:

class ArticlesController < ApplicationController
  before_action :set_article, only: %i[show edit update destroy]
  ...
    
  private

  # Use callbacks to share common setup or constraints between actions.
  def set_article
    @article = Article.find(params[:id])
  end

  # Only allow a list of trusted parameters through.
  def article_params
    params.require(:article).permit(:title, :content)
  end
end

Also, in each resources, I have added validation to the model (Article example):

class Article < ApplicationRecord
  validates :title, :content, presence: true

end

Article

So, I setup a basic controller. This controller represents how I was taught in Bootcamp, and represents a simple way to code a controller. We will review the create method:

def new
    @article = Article.new
end

def create
  @article = Article.create(article_params)
  if @article.save
    flash[:notice] = "Article was successfully created."
    redirect_to articles_path
  else
    render :new
  end
end

In this example, if the article is successfully created, you are redirected to the articles index page. If there is an error, the user is directed to the create page. You can view the Articles Controller

Blog

For the Blog resource we will use the rails scaffold generator:

rails g scaffold Blog title content:text

Remember to run your migrations: rails db:migrate.

# GET /blogs/new
def new
  @blog = Blog.new
end

# POST /blogs
# POST /blogs.json
def create
  @blog = Blog.new(blog_params)

  respond_to do |format|
    if @blog.save
      format.html { redirect_to @blog, notice: 'Blog was successfully created.' }
      format.json { render :show, status: :created, location: @blog }
    else
      format.html { render :new }
      format.json { render json: @blog.errors, status: :unprocessable_entity }
    end
  end
end

The scaffold generates code using meta programming. The respond_to block helper method is attached to the Controller class (or rather, its super class). It is referencing the response that will be sent to the View (which is going to the browser). The block shown above is formatting data - by passing in a 'format' parameter in the block - to be sent from the controller to the view whenever a browser makes a request for html or json data.

Notice the similarities:

  • In one line of code, after the successful save, it redirects and sends a message.
  • The redirect on the error is basically the same as the Article resource.

Problems - Well, not really a problem, but is gets verbose in a hurry. However, the respond_to block is somewhat easier to read than the Articles.

You can view the Blogs Controller

Post

This resource we are going to take a different approach. As we noted in the Blog resource, the controllers are cleaner, but are quite verbose. We are going to install the responders gem. This gem adds support for respond_with which was removed in Rails 4.2, and allows us to do a lot of cool stuff. Let's setup the responders gem:

  • Add to your Gemfile: gem "responder" and bundle install
  • Install responders: rails g responsers:install
  • Remember to restart your rails server

The gem installation adds the following configuration to the application_controller:

require "application_responder"

class ApplicationController < ActionController::Base
  self.responder = ApplicationResponder
  respond_to :html

end

Now generate the Post resource using rails scaffold:

rails g scaffold Post title content:text

Remember to run your migrations: rails db:migrate.

Notice how much cleaner the controller methods are? We can define the respond_to format for the controller under the before_filter. In this case, the generator specified :html because it is fined in the application_controller.

class PostsController < ApplicationController
  before_action :set_post, only: %i[show edit update destroy]

  respond_to :html

  ...

  def new
    @post = Post.new
    respond_with(@post)
  end


  def create
    @post = Post.new(post_params)
    @post.save
    respond_with(@post)
  end
  
  ...
    
end

What if you want the :json format in only the index view? It is so simple!

class PostsController < ApplicationController
  before_action :set_post, only: %i[show edit update destroy]

  respond_to :html
  respond_to :json, only: :index

The create method is three lines of code. After the post is successfully saved it redirects, or throws errors if the validations are not successful. This creates a really DRY controller with a lot of flexibility.

You can view the Posts Controller

Footnote

This has been fun. Leave a comment or send me a DM on Twitter.

Shameless Plug: If you work at a great company and you are in the market for a Software Developer with a varied skill set and life experiences, send me a message on Twitter and check out my LinkedIn.