Exploiting abstraction while writing rails controllers
Tags: ruby, rails, ror
Published: 2021-05-07 01:58:46

---
title: Exploiting abstraction while writing rails controllers
published: true
description: this post was originally posted at http://haidrali.com/exploiting-abstraction-while-writing-rails-controllers/
tags: ruby, rails, ROR,
//cover_image: https://direct_url_to_image.jpg
---

<p style="text-align: center;"><a href="http://haidrali.com/wp-content/uploads/2021/05/abstract-painting3.jpeg"><img class="alignnone size-full wp-image-656" src="http://haidrali.com/wp-content/uploads/2021/05/abstract-painting3.jpeg" alt="abstract-painting3" width="900" height="711" /></a><strong><em>Abstraction is simple and beautiful while writing abstract code is an art.</em></strong></p>
&nbsp;

While writing web apps apart from writing business logic most of the time we end up writing <strong>CRUD</strong> operations (Create, Read, Update Delete). There are a bunch of methods available to generate boilerplate code i.e. scaffolding, custom generators, etc. which have their own dos and don'ts.

I personally have been exploiting abstraction to write CRUD operations. Consider an example of developing a school management system where we have controllers like Student, Course, Section, Teacher, Exam, and many others. A typical student controller CRUD would be
<pre>class StudentsController &lt; ApplicationController
  before_action :set_student, only: %i[ show edit update destroy ]

  def index
    @students = Student.all
  end

  def show
  end

  def new
    @student = Student.new
  end

  def edit
  end

  def create
    @student = Student.new(student_params)

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

  def update
    respond_to do |format|
      if @student.update(student_params)
        format.html { redirect_to @student, notice: "Student was successfully updated." }
        format.json { render :show, status: :ok, location: @student }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @student.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @student.destroy
    respond_to do |format|
      format.html { redirect_to students_url, notice: "Student was successfully destroyed." }
      format.json { head :no_content }
    end
  end

  private
    def set_student
      @student = Student.find(params[:id])
    end

    def student_params
      params.require(:student).permit(:first_name, :last_name, :roll_no, :date_of_birth)
    end
end
</pre>
Sections controller CRUD would look like
<pre>class SectionsController &lt; ApplicationController
  before_action :set_section, only: %i[ show edit update destroy ]

  def index
    @sections = Section.all
  end

  def show
  end

  def new
    @section = Section.new
  end

  def edit
  end

  def create
    @section = Section.new(section_params)

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

  def update
    respond_to do |format|
      if @section.update(section_params)
        format.html { redirect_to @section, notice: "Section was successfully updated." }
        format.json { render :show, status: :ok, location: @section }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @section.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @section.destroy
    respond_to do |format|
      format.html { redirect_to sections_url, notice: "Section was successfully destroyed." }
      format.json { head :no_content }
    end
  end

  private
    def set_section
      @section = Section.find(params[:id])
    end

    def section_params
      params.require(:section).permit(:name)
    end
end
</pre>
And Course CRUD would be
<pre>class CoursesController &lt; ApplicationController
  before_action :set_course, only: %i[ show edit update destroy ]

  def index
    @courses = Course.all
  end

  def show
  end

  def new
    @course = Course.new
  end

  def edit
  end

  def create
    @course = Course.new(course_params)

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

  def update
    respond_to do |format|
      if @course.update(course_params)
        format.html { redirect_to @course, notice: "Course was successfully updated." }
        format.json { render :show, status: :ok, location: @course }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @course.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @course.destroy
    respond_to do |format|
      format.html { redirect_to courses_url, notice: "Course was successfully destroyed." }
      format.json { head :no_content }
    end
  end

  private
    def set_course
      @course = Course.find(params[:id])
    end

    def course_params
      params.require(:course).permit(:title, :credit_hours, :code, :year)
    end
end
</pre>
If you notice most of the time we are doing same kind of operation i.e. in the case of the show we first set the resource which could be <strong>Student</strong>, <strong>Course</strong>, <strong>Section</strong>, etc. In general, we are dealing with a resource or resources everywhere in the controllers.

In the Ruby on Rails world, we often talk about resources, we model every real-world object into Rails model as we did with the school management system <strong>Student</strong>, <strong>Course</strong>, <strong>Teacher</strong>, <strong>Exam</strong>. If we look at our above code we just specified resource name in reach controller for example in <strong>StudentsController</strong> we used <strong>Student</strong>, <strong>@student</strong>, <strong>@students</strong>, <strong>set_student</strong>, or <strong>student_params</strong> and the same is the case with <strong>CoursesController</strong> we have used <strong>Course</strong>, <strong>@course</strong>, <strong>@courses</strong>, <strong>set_course</strong>, or <strong>course_params</strong>, etc. In short, we are repeating the resource name in each file. We can come up with a  generic controller let say <strong>BaseController</strong> where we perform all operations on a resource rather than student, course, section, etc. Then, we just inherit <strong>StudentsController</strong>, <strong>CoursesController</strong>, <strong>TeachersController</strong>, <strong>ExamsController</strong> from a single source of truth <strong>BaseController</strong>.

Let's start with the show function and try to write <strong>BaseController</strong>.
<pre>class BaseController &lt; ApplicationController
  before_action :set_resource, only: %i[ show ]

  def show
  end

  def set_resource
    resource ||= resource_class.find(params[:id])
    instance_variable_set("@#{resource_name}", resource)
  end

  def resource_class
    @resource_class ||= resource_name.classify.constantize
  end

  def resource_name
    @resource_name ||= controller_name.singularize
  end
end
</pre>

Writing create function is a bit more interesting
<pre>  def create
    @resource = resource_class.new(resource_params)

    respond_to do |format|
      if @resource.save
        format.html { redirect_to @resource, notice: "#{resource_name} was successfully created." }
        format.json { render :show, status: :created, location: @resource }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @resource.errors, status: :unprocessable_entity }
      end
    end
  end
</pre>
update and destroy functions are somewhat similar
<pre>  def update
    @resource = resource_class.find(params[:id])

    respond_to do |format|
      if @resource.update(resource_params)
        format.html { redirect_to @resource, notice: "#{resource_name} was successfully updated." }
        format.json { render :show, status: :ok, location: @resource }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @resource.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @resource = resource_class.find(params[:id])

    @resource.destroy
    respond_to do |format|
      format.html { redirect_to "#{controller_name}_url", notice: "#{resource_name} was successfully destroyed." }
      format.json { head :no_content }
    end
  end
</pre>
In all of the above rewrites, we just simply used resources as a generic variable attribute. destroying an object is the same no matter its <strong>Student</strong>, <strong>Course</strong>, <strong>Section</strong>, or <strong>Exam</strong> so resource.destroy works the same for each resource.

Our final refactoring will look like this

{% gist https://gist.github.com/wonderer007/a10b43e7fb4868893082558b3b536184 %}



Since controllers are inherited from <strong>BaseController</strong> we can always override any method in the respective controller. For example, instead of directly creating a <strong>Student</strong> object we have a service written which takes student params and creates <strong>Student</strong> object after applying a bunch of business logic and pre-checks. For such scenarios, we can override create the function in StudentsController.

We can use the same pattern while writing our APIs and all other scenarios where we have a similar pattern repetition.

Thank You so much for the read.