07 May

Exploiting abstraction while writing rails controllers

abstract-painting3Abstraction is simple and beautiful while writing abstract code is an art.

 

While writing web apps apart from writing business logic most of the time we end up writing CRUD 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

class StudentsController < 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

Sections controller CRUD would look like

class SectionsController < 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

And Course CRUD would be

class CoursesController < 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

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 Student, Course, Section, 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 Student, Course, Teacher, Exam. If we look at our above code we just specified resource name in reach controller for example in StudentsController we used Student, @student, @studentsset_student, or student_params and the same is the case with CoursesController we have used Course, @course, @coursesset_course, or course_params, etc. In short, we are repeating the resource name in each file. We can come up with a  generic controller let say BaseController where we perform all operations on a resource rather than student, course, section, etc. Then, we just inherit StudentsController, CoursesController, TeachersController, ExamsController from a single source of truth BaseController.

Let’s start with the show function and try to write BaseController.

class BaseController < 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

Writing create function is a bit more interesting

  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

update and destroy functions are somewhat similar

  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

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 Student, Course, Section, or Exam so resource.destroy works the same for each resource.

Our final refactoring will look like this




Since controllers are inherited from BaseController we can always override any method in the respective controller. For example, instead of directly creating a Student object we have a service written which takes student params and creates Student 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.

20 Oct 2018

AWS S3 file upload from client side

Last week I pushed a new feature on production which involve file upload on AWS S3. When you are uploading file to S3 from frontend there is always a risk of exposing your AWS secrets to user, so you have following options to avoid this risk

  1. Involve server as middleware and upload via server API, you will have more control over it
  2. Allow users to upload directly to S3 anonymously

I don’t like both of above methods, as involving middleware you are uploading file twice frontend → server, server → s3 bucket and I don’t really want server to do this heavy lifting of uploading files to S3 on user behalf, while allowing user anonymous upload you are giving a blank check to you S3 bucket i.e. anyone in the world can upload to your s3 bucket, this was also not acceptable to me as this could have very swear consequences. I wanted to have bit more control over uploads while not compromising security issues, so I used AWS Security Token Service to generate secrets (access_key, access_secret) which lived temporarily and have limited access as you define it.

Configure an IAM user on AWS console to generate temporary secrets

In order to generate temporary secrets you need following configurations on AWS console

  1. create a new IAM user
  2. create a policy to allow upload on S3 bucket
  3. create a role for you IAM user to assume Security Token Service

Watch this video for configurations

Now, you can generate AWS STS with this ruby code

aws_sts = AWS::STS::Client.new(
          access_key_id: ENV['AWS_ACCESS_KEY_ID'],
          secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']
        ).assume_role(
          role_arn: ENV['AWS_ROLE_ARN'],
          role_session_name: 'session_name',
          duration_seconds: 12*60*60)
// this will generate access_key, access_secret
aws_sts.credentials

AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are credentials you download after creating user and AWS_ROLE_ARN is value which I copied at the end of video

Enable ACL uploading to S3

You will be able to successfully upload files with secrets generated with above code, Since we have used a separate user to generate STS then only this user will be able to access file. To make this file accessible by other user as well you must send ACL: 'public-read' in request body or header while uploading file to S3. you can read more about ACL

I hope this help you improve your file upload as well. If you need any help please do ping me on twitter @alihaider907.

25 Nov 2017

Newbies Introduction to Ruby on Rails

Note: This tutorial is intended to newbies only who want to kick-start web development with Ruby on Rails. I will just share links to different resources and give tips based on my experience which may help you starting development with Ruby on Rails.

Installation and Prerequisites

Just like any other framework ruby on rails framework has its own development environment, before diving into installations of environment I would recommend if you have  planned to use Window operating system for ruby on rails development please read Rails Development on Windows. Seriously and switch to linux/unix based operating system. If you still insist to carry on with Windows go ahead with my Best wishes.

You can find complete installation guide of Ruby on Rails on GoRails website (there are other websites too). I will recommend installing RVM instead of rbenv and installing it from source.  GoRails guides also include installing Git, MySQL/Postgres complete the installation process. You might get errors while installation please do check your OS and version before starting installation process.

Ruby Language

Once you have set up your installations and environment start with ruby language basics I would recommend The Little Book of Ruby by Huw Collingbourne. Don’t memorise anything just practice basic syntax, data structures, condition statements, loops modules and Classes. Since we have already installed ruby as well as created a project of rails (in above GoRails guide) your can either go to project root and open a console or write your program in a .rb file and run it with ruby keyword as mentioned in book.

Ruby on Rails Framework

You are familiar with Ruby language basics now it is time to start getting your hands on Ruby on Rails. You will find numerous tutorial on getting started with Rails, but RUBY ON RAILS TUTORIAL by Michael Hartl is the most comprehensive guide for newbies. It covers details at granular level starting from Zero to Deployment it includes everything. There are other good resources as well like RailsCastsDriftingRuby, RailsTutorial, but if you are newbie I would highly recommend finishing Ruby on Rails Tutorial book by Micheal Hartl thoroughly first and then look elsewhere like RailsCasts (very good for topic specific tutorials) as it will make your Rails framework understanding in-depth.

Development Environment

Development environment contains your machine, tools, settings, configurations etc. Make your development environment as smooth as possible use .bashrc, tumx, terminal settings aliases to amplify your concentration. Have strong grip over terminal commands. Choose any suitable text editor I personally use Sublime Text in development and vim or emacs in production mode. Look at StackOverFlow’s survey for Most Popular Developer Environments by Occupation.

Be Open Source 

Ruby on Rails is an open source framework and it has very large open source community, as a beginner you will get lots of help from open source community in term of different gems, git, gist, stack overflow etc. Don’t forget to contribute back once you have a good grip.

Follow Tech Talks

Ruby and Ruby on Rails framework has large community of developers across the world. There are many conferences being held across the world on Ruby language and Ruby on Rails framework both. RubyConf and RailsConf are two of the most popular conferences in Ruby on Rails world. Do follow tech talks in these conferences particularly from speakers David Heinemeier, Sean Griffin, Mike Perham, Aaron Patterson and people who are at the forefront of Ruby language and Ruby on Rails framework development. Look at 11 TOP INTERNATIONAL RUBY ON RAILS CONFERENCES AND EVENTS 2017. Also one particularly article I really like every Rails developer to read is The Rails Doctrine by DHH.

Never Stop Learning

Once you have develop basics of ruby on rails I would recommend having some DevOps and Front end frameworks like ReactJS, VueJS  skills as well. Do explore topics like High Availability, Consistency, Scaling RDBS, NoSQL etc and  regularly read engineering blogs of tech giants like Google, Twitter, Microsoft and Amazon etc.

If you are newbie to Ruby on Rails development and want any suggestions or help do ping me on twitter @alihaider907.

Happy Learning 😛