5 Steps to Building the Basics of a Job Board Rails App with Authentication and Authorization

Building a rails web application can be very simple or very involved, it all depends on what your goal is, however, there are certain steps that can help you do it very efficiently.

1. Have an idea of what you really want to build

I like to start writing code after a have a basic idea of what I want my project to accomplish and what is it going to look like. I am building a job board web application so let’s try to define these two aspects of my app first:

What do I want my app to accomplish?

Be clear about the basic functionality of your app, once you get that working you can extend it as far as you want, but first… get the basics working.

Ok! so a job board … what do I want?:

  • Users ability to log in and log out (maybe through a social network). – (Authentication)
  • Users ability to choose between signing up as employers or as job seekers  – (Roles)
  • Employers can post a job offer but can’t apply to any. – (Authorization)
  • Job seekers can apply to job offers but can’t post any. – (Authorization)
  • Main page should display all job offers.
  • Detailed info about job offers can only be seen if logged in – (Authorization)

Great! now we know how our app is going to work.

What is it going to look like?

For this, I like to use wireframe.cc (It’s free), having an idea of how the end result is gonna look like can be very useful, not only for the visual aspect but rather to better understand how you should be using your models and controllers.

Sign up/Log in Forms
Main page

Perfect! now we know where are we heading.

Note: Remember, we just want to know the basics, we don’t need to wireframe the whole app.

2. Define your Models

Based on step 1, we can start defining some models:

  • Users ability to log in and log out – We need a User Model
  • Employers can post a job offer. – We need a Job Model
  • Job seekers can apply to job offers – We need a Job Application Model

Great, we have a starting point now. It’s important to understand that along the way we may add more models, and extend the functionality of our app, but in order to start, we need to do it based on the basics. Start simple… get crazy later!

3. Pick Your Gems

One of the great things about the ruby language is gems, gems are pieces of code, written by other programmers that save us time and effort during our development process. We don’t need to build the wheel again, I encourage everyone to use gems with caution, don’t get gem crazy, research all gems before using them, are they updated often? do they have documentation? how popular are they? not all gems are great, but using the right ones can make your app awesome in no time!.

Here are the gems I used for this project.

gem 'devise' #User Authentication
gem 'figaro'  #Keeps you API keys secure
gem 'pundit' #Authorization
gem 'kaminari' #Pagination
gem "twitter-bootstrap-rails" #Bootstrap
gem 'bootstrap_form' #Bootstrap Forms
gem "font-awesome-rails" #Icons
gem 'omniauth-facebook' #Facebook Login

I always take time and read the documentation, you want to know how to use these gems, and furthermore, gems like devise do a lot of things behind walls, for me, understanding the functionality of every gem is crucial, since you will be prone to customize, and the only way to do so is by reading the documentation.

4. Start coding (User Authentication & Authorization)

Now that we know what our app is going to look like (sort of), what is supposed to accomplish, and what tools (gems) are we gonna use, it’s time to start coding.

To create a new rails app type rails new <name of your app>in your terminal.

This command will generate the basic structure of your rails app, cool right?

Add all your gems to the Gemfile.

Type bundle install to install all your new gems.

Authentication (Devise Gem)

This gem is extremely powerful, it will get your authentication setup ready in no time.

Run run the installer:

Type rails generate devise:install

This creates a massive initializer in  config/initializers/devise.rb

Now generate your User model by typing rails g devise User

Run rake db:migrate

Run rake routes you should see that Devise has added a bunch of them. Run rails s and take a look at one, maybe /users/sign_in.

Your User model should now look something like this:

class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  devise :omniauthable, :omniauth_providers => [:facebook]
end

Our app now has a basic authentication system, where users can register themselves, and then log in. However, all the pages are still directly accessible. To change this, edit  /app/controllers/application_controller.rb and add authenticate_user!as an action that has to be performed before serving any page.

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :authenticate_user!
end

If you want to, you could start the development server with the rails s command, and check out these newly added pages by visiting http://localhost:3000/.

If you need to leave a page available to be served without authentication, for example, index, you can add the following action to the appropriate controller:

before_action :authenticate_user!, :except => [:index]

Pro-tip: In order to customize your views you have to generate them first, just type rails generate devise:views, and customize at will.

Now we can add Facebook login support with Omniauth, (I will write a tutorial on how to do it).

Before going any further, lets create a Job Model:

rails g model Job

Let’s add some attributes to our job model in our migration generated by rails:

class CreateJobs < ActiveRecord::Migration[5.0]
  def change
    create_table :jobs do |t|
      t.string :title
      t.text :description
      t.string :company
      t.string :url
      t.string :location

      t.timestamps null: false
    end
  end
end

Run  rake db:migrate.

Let’s also add a jobs controller.

rails g controller jobs

Great!

Authorization (Pundit Gem)

People tend to confuse authentication with authorization, so to understand the difference we have to understand what each one is intended for.

  • Authentication – Who are you? (name, username, password…etc)
  • Authorization – What can you do? (post, edit, delete…etc)

Two different things right?. ok, devise checks who you are, pundit will check based on who you are, what are you allowed to do?.

Remember step 1?

  • Employers can post a job offer but can’t apply to any. – (Authorization)
  • Job seekers can apply to job offers but can’t post any. – (Authorization)

Pundit needs to be installed, so just type rails g pundit:install

You’ll have to restart Rails at this point for it to pick up the changes.

Add Pundit to application controller

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  include Pundit
  protect_from_forgery with: :exception
end

 Create user Roles:

I Added three different roles to this app (user, company, admin), this is done by adding  a new column to our user class called role and adding:

enum role:[:user,:company,:admin]

to our User class, like so:

class User < ApplicationRecord
  after_initialize :set_default_role

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  devise :omniauthable, :omniauth_providers => [:facebook]

  enum role: [:user, :company, :admin]

  def set_default_role
    self.role ||= :user
  end
end

I went ahead and created a method called  set_default_role and an anchor after_initialize :set_default_roleto set all my users role to be “user” unless another role is given upon initialization.

Now you have an ApplicationPolicy file app/policies/application_policy.rb

class ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, record)
    @user = user
    @record = record
  end
end

From here on you can create a policy for who can do what in your app, by creating policy files that inherit from ApplicationPolicy, here my job policy:

# app/policies/job_policy.rb
class JobPolicy < ApplicationPolicy

  # User is a company and created this job
  def created_by_company?
    user.company? && record.company_id == user.id 
  end

  # Only admin users or companies can create jobs
  def create?
    user.admin? || user.company?
  end

  # Only admin users or companies that created current job can update it
  def update?
    user.admin? || created_by_company? 
  end

  # Only admin users or companies that created current job can delete it
  def destroy?
    user.admin? || created_by_company? 
  end

end

Finally, you have to add authorization to the controller:

class JobsController < ApplicationController
  before_action :set_job, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!, :except => [:index]
#...

  def create
    @job = Job.new(job_params)
    authorize @job

    if @job.save
      redirect_to @job, alert: "Job offer succesfully created"
    else
      render new_job_path, alert: "Oops!, please try again!"
    end
  end

  def edit
    authorize @job
  end

  def update
    authorize @job
    @job.update(job_params)
    redirect_to @job, alert: "Job offer succesfully updated"
  end

  def destroy
    authorize @job
    @job.destroy
    redirect_to root_path, alert: "Job offer succesfully deleted"
  end

  private
  def set_job
    @job = Job.find(params[:id])
  end

  def job_params
    params.require(:job).permit(:title, :description, :company_name ,:url, :location, :category_id, :company_id, skill_ids:[], skills_attributes:[:name])
  end
end

As you can see it’s fairly simple, to learn more about pundit please read the documentation.

5. Create Model Associations

In Rails, an association is a connection between two Active Record models. Why do we need associations between models? Because they make common operations simpler and easier in your code. – Rails Guides

Associations may sound complicated but they are really not. Let’s think of it this way:

Companies create many job offers so in ruby terms a company has_many :jobs, but wait? we don’t have a company model!!

Not to worry, remember roles? Our User model have a role named company, therefore we can do this:

class User < ApplicationRecord
#...
  enum role: [:user, :company, :admin]
  has_many :jobs, :foreign_key => 'company_id' 
#...
end
class Job < ApplicationRecord
  belongs_to :company, :class_name => 'User', :foreign_key => 'company_id'
end

Now we just need to add a new column to our job model called column_id.

rails g migration AddCompanyIdToJobs company_id:integer 

class AddCompanyIdToJobs < ActiveRecord::Migration
  def change
    add_column :jobs, :company_id, :integer
  end
end

Now run rake db:migrtae

#create_table "jobs", force: :cascade do |t|
  #t.string   "title"
  #t.text     "description"
  #t.string   "company_name"
  #t.string   "url"
  #t.string   "location"
  #t.datetime "created_at",   null: false
  #t.datetime "updated_at",   null: false
  #t.integer  "category_id"
   t.integer  "company_id"
#end

Tadaaah! now those two models are associated.

Now you know, how to prepare before starting a new app, how to build authentication, authorization and model relationships.

Of course, there is much more to do, like adding more models,  controllers and views, but these are the basic steps to start a job board rails app.

This was my final result:

Nice right?

Here you can see all my code for DevJobs App.

You may also like