Rails DRY Controllers

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

TLTR: 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 on articles for a blog-type site. There are no user accounts, so this is not a robust application. Instead, it shows three ways to write CRUD controllers. Therefore, we are looking at three resources that are basically identical, just coded slightly differently.

Each resource controller is set up 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 resource, I have added validation to the model (Article example):

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

end

Article

So, I set up a basic controller. This controller reflects how I was taught in Bootcamp and provides 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 The block helper method is attached to the Controller class (or rather, its superclass). It is referencing the response that will be sent to the View (i.e., the browser). The block shown above formats data by passing a 'format' parameter to the block, which is sent from the controller to the view whenever a browser requests 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 it 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

For 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 set up 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 is specified :html because it is defined 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 The method is three lines of code. After the post is successfully saved, it redirects or throws errors if the validations fail. This creates a really DRY controller with a lot of flexibility.

You can view the Posts Controller