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.
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_role
to 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.
- Installing phpcs on a Mac ft. VSCode & WordPress, The Really Simple Guide - May 8, 2021
- How To Set Up Your WordPress Development Environment with a Large Database ft. MAMP & Mac, The Really Simple Guide - April 24, 2020
- Next.js ▲ + Typescript + Storybook The Really Simple Guide 2019 - November 25, 2019