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"andbundle 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